La caché de página almacena en caché las páginas a las que se accedió recientemente desde el disco duro, lo que reduce los tiempos de búsqueda para los accesos posteriores a los mismos datos. La caché de página no mejora el rendimiento la primera vez que se accede a una página desde el disco duro. Entonces, si una aplicación va a leer un archivo una vez y solo una vez, entonces omitir el caché de la página es la mejor manera de hacerlo. Esto es posible mediante el uso de la marca O_DIRECT. Esto significa que el kernel no tiene en cuenta estos datos concretos para la caché de página. Reducir la contención de la caché significa que otras páginas (a las que se accede repetidamente) tienen más posibilidades de conservarse en la caché de la página. Esto mejora la relación de aciertos de caché, lo que proporciona un mejor rendimiento.
void ioReadOnceFile()
{
/* El uso de direct_fd y direct_f omite la caché de página del kernel.
* - direct_fd es un descriptor
de archivos de bajo nivel* - direct_f es una secuencia de archivos similar a la devuelta por fopen()
* NOTA: Utilice getpagesize() para determinar los buffers de tamaño óptimo.
*/
int direct_fd = open("nombre de archivo", O_DIRECT | O_RDWR);
ARCHIVO *direct_f = fdopen(direct_fd, "w+");
/* Direct Disk-I/O hecho AQUÍ*/
fclose(f);
close(fd);
}
Considere el caso de una lectura en un archivo grande (es decir, una base de datos) compuesta por una gran cantidad de páginas. Cada página posterior a la que se accede va a la caché de página solo para ser eliminada más tarde a medida que se leen más y más páginas. Esto reduce drásticamente la relación de aciertos de caché. En este caso, la caché de página no proporciona ninguna mejora de rendimiento. Por lo tanto, sería mejor omitir el caché de página cuando se accede a archivos grandes.
void ioLargeFile()
{
/* El uso de direct_fd y direct_f omite la caché de página del kernel.
* - direct_fd es un descriptor
de archivos de bajo nivel* - direct_f es una secuencia de archivos similar a la devuelta por fopen()
* NOTA: Utilice getpagesize() para determinar los buffers de tamaño óptimo.
*/
int direct_fd = open("largefile.bin", O_DIRECT | O_RDWR | O_LARGEFILE);
ARCHIVO *direct_f = fdopen(direct_fd, "w+");
/* Direct Disk-I/O hecho AQUÍ*/
fclose(f);
close(fd);
}
El programador io optimiza el orden de las operaciones de I/O que se pondrán en cola en el disco duro. Dado que el tiempo de búsqueda es la penalización más alta en un disco duro, la mayoría de los programadores de I/O intentan minimizar el tiempo de búsqueda. Esto se implementa como una variante del algoritmo de ascensor, es decir, reordenar las solicitudes ordenadas aleatoriamente de numerosos procesos al orden en que los datos están presentes en el disco duro, requiere una cantidad significativa de tiempo de CPU.
Ciertas tareas que implican operaciones complejas tienden a estar limitadas por la rapidez con la que la CPU puede procesar grandes cantidades de datos. Un programador de I/O complejo que se ejecuta en segundo plano puede consumir valiosos ciclos de CPU, lo que reduce el rendimiento del sistema. En este caso, cambiar a un algoritmo más simple, como no-op, reduce la carga de la CPU y puede mejorar el rendimiento del sistema.
echo noop > /sys/block//queue/scheduler
Si bien esto eventualmente hará el trabajo, definitivamente no es la forma más óptima. Desde la perspectiva del kernel, el tamaño más óptimo para las solicitudes de I/O es el tamaño de bloque del sistema de archivos (es decir, el tamaño de página). Como toda la E/S en el sistema de archivos (y la caché de página del kernel) está en términos de páginas, tiene sentido que la aplicación también realice transferencias en múltiplos del tamaño de página. Además, con las cachés multisegmentadas que se abren camino en los discos duros ahora, uno se beneficiaría enormemente al realizar E/S en múltiplos de tamaño de bloque.
El siguiente comando se puede utilizar para determinar el tamaño
de bloque óptimostat --printf="bs=%s optimal-bs=%S\n" --file-system /dev/
Cuando una app inicia una lectura de I/O de SYNC, el kernel pone en cola una operación de lectura para los datos y regresa solo después de que se vuelve a leer todo el bloque de datos solicitados. Durante este período, el kernel marcará el proceso de la aplicación como bloqueado para I/O. Otros procesos pueden utilizar la CPU, lo que da como resultado un mejor rendimiento general para el sistema.
Cuando una app inicia una escritura de I/O de SYNC, el kernel pone en cola una operación de escritura para los datos y pone el proceso de la aplicación en una I/O bloqueada. Desafortunadamente, lo que esto significa es que el proceso de la aplicación actual está bloqueado y no puede realizar ningún otro procesamiento (o I/O) hasta que se complete esta operación de escritura.
Cuando una app inicia una lectura de E/S asíncrona, la función read() suele aparecer después de leer un subconjunto del bloque grande de datos. La app debe llamar repetidamente a read() con el tamaño de los datos que quedan por leer, hasta que se lean todos los datos necesarios. Cada llamada adicional de lectura introduce cierta sobrecarga, ya que introduce un cambio de contexto entre el espacio de usuario y el kernel. La implementación de un bucle estrecho para llamar repetidamente a read() desperdicia ciclos de CPU que otros procesos podrían haber usado. Por lo tanto, generalmente se implementa el bloqueo usando select() hasta que el siguiente read() devuelva una lectura de bytes distintos de cero. es decir, el ASYNC está hecho para bloquearse al igual que lo hace la lectura SYNC.
Cuando una aplicación inicia una escritura de E/S asíncrona, el kernel actualiza las páginas correspondientes en la caché de página y las marca como desfasadas. Luego, el control regresa rápidamente a la aplicación, que puede continuar ejecutándose. Los datos se vacían en el disco duro más adelante en un momento más óptimo (carga baja de CPU) de una manera más óptima (escrituras agrupadas secuencialmente).
Por lo tanto, las lecturas síncronas y las escriturasasíncronas suelen ser una buena opción, ya que permiten que el kernel optimice el orden y el tiempo de las solicitudes de I/O subyacentes.