O cache de páginas armazena em cache as páginas acessadas recentemente a partir do disco rígido, reduzindo assim os tempos de busca para acessos subsequentes aos mesmos dados. O cache de página não melhora o desempenho na primeira vez que uma página é acessada a partir do disco rígido. Portanto, se um aplicativo vai ler um arquivo uma vez e apenas uma vez, ignorar o cache de página é o melhor caminho a seguir. Isso é possível usando o indicador O_DIRECT. Isso significa que o kernel não considera esses dados específicos para o cache de página. Reduzir o contenção de cache significa que outras páginas (que seriam acessadas repetidamente) têm mais chances de serem retidas no cache da página. Isso melhora a taxa de acesso ao cache, proporcionando melhor desempenho.
void ioReadOnceFile()
{
/* Usando direct_fd e direct_f ignora o cache de página do kernel.
* - direct_fd é um descritor
de arquivo de baixo nível* - direct_f é um fluxo de arquivos semelhante ao retornado pelo fopen()
* NOTA: Use getpagesize() para determinar buffers de tamanho ideal.
*/
int direct_fd = open("nome do arquivo", O_DIRECT | O_RDWR);
ARQUIVO *direct_f = fdopen(direct_fd, "w+");
/* E/S direta de disco concluída AQUI*/
fclose(f);
fechar (fd);
}
Considere o caso de uma leitura em um arquivo grande (ou seja, um banco de dados) feita de um grande número de páginas. Cada página subsequente acessada vai para o cache da página apenas para ser retirada mais tarde à medida que mais e mais páginas são lidas. Isso reduz drasticamente a taxa de acesso ao cache. Nesse caso, o cache de página não fornece nenhum ganho de desempenho. Portanto, seria melhor ignorar o cache de página ao acessar arquivos grandes.
void ioLargeFile()
{
/* O uso de direct_fd e direct_f ignora o cache de página do kernel.
* - direct_fd é um descritor
de arquivo de baixo nível* - direct_f é um fluxo de arquivos semelhante ao retornado pelo fopen()
* NOTA: Use getpagesize() para determinar buffers de tamanho ideal.
*/
int direct_fd = aberto("largefile.bin", O_DIRECT | O_RDWR | O_LARGEFILE);
ARQUIVO *direct_f = fdopen(direct_fd, "w+");
/* E/S direta de disco concluída AQUI*/
fclose(f);
fechar (fd);
}
O agendador de E/S otimiza a ordem das operações de E/S a serem colocadas em fila no disco rígido. Como o tempo de busca é a penalidade mais pesada em um disco rígido, a maioria dos agendadores de E/S tenta minimizar o tempo de busca. Isso é implementado como uma variante do algoritmo de elevador, ou seja, reordenar as solicitações ordenadas aleatoriamente de vários processos para a ordem em que os dados estão presentes no disco rígido, requer uma quantidade significativa de tempo de CPU.
Certas tarefas que envolvem operações complexas tendem a ser limitadas pela rapidez com que a CPU processa grandes quantidades de dados. Um agendador de E/S complexo em execução em segundo plano pode consumir ciclos preciosos da CPU, reduzindo assim o desempenho do sistema. Nesse caso, mudar para um algoritmo mais simples, como o no-op, reduz a carga da CPU e pode melhorar o desempenho do sistema.
echo noop > /sys/block//fila/agendador
Embora isso acabe fazendo o trabalho, definitivamente não é a maneira mais ideal. Da perspectiva do kernel, o tamanho ideal para solicitações de E/S é o tamanho do bloco do file system (ou seja, o tamanho da página). Como toda E/S no sistema de arquivos (e o cache de páginas do kernel) é em termos de páginas, faz sentido para o aplicativo fazer transferências em múltiplos de tamanho de página também. Além disso, com caches multissegmentados chegando aos discos rígidos agora, você se beneficiaria enormemente fazendo E/S em múltiplos de tamanho de bloco.
O seguinte comando pode ser usado para determinar a estatística de tamanho
de bloco ideal--printf="bs=%s optimal-bs=%S\n" --file-system /dev/
Quando um aplicativo inicia uma leitura de E/S de sincronização, o kernel enfileira uma operação de leitura para os dados e retorna somente depois que todo o bloco de dados solicitados é lido de volta. Durante esse período, o Kernel marcará o processo do aplicativo como bloqueado para E/S. Outros processos podem utilizar a CPU, resultando em um desempenho geral melhor para o sistema.
Quando um aplicativo inicia uma gravação de E/S síncrona, o kernel enfileira uma operação de gravação para os dados e coloca o processo do aplicativo em uma E/S bloqueada. Infelizmente, o que isso significa é que o processo do aplicativo atual está bloqueado e não pode fazer nenhum outro processamento (ou E/S) até que essa operação de gravação seja concluída.
Quando um aplicativo inicia uma leitura de E/S assíncrona, a função read() geralmente retorna após a leitura de um subconjunto do grande bloco de dados. O aplicativo precisa chamar repetidamente read() com o tamanho dos dados restantes a serem lidos, até que todos os dados necessários sejam lidos. Cada chamada adicional para leitura introduz alguma sobrecarga, pois introduz uma alternância de contexto entre o espaço do usuário e o kernel. A implementação de um loop apertado para chamar repetidamente read() desperdiça ciclos de CPU que outros processos poderiam ter usado. Portanto, geralmente se implementa o bloqueio usando select() até que a próxima read() retorne bytes diferentes de zero read-in. ou seja, o ASYNC é feito para bloquear, assim como a leitura SYNC faz.
Quando um aplicativo inicia uma gravação de E/S assíncrona, o kernel atualiza as páginas correspondentes no cache de página e as marca sujas. Em seguida, o controle retorna rapidamente ao aplicativo que pode continuar a ser executado. Os dados são liberados para o disco rígido posteriormente em um momento mais ideal (baixa carga da CPU) de uma maneira mais ideal (gravações agrupadas sequencialmente).
Portanto, leituras SYNC e gravações ASYNC geralmente são um bom caminho a seguir, pois permitem que o kernel otimize a ordem e o tempo das solicitações de E/S subjacentes.