É fwrite mais rápido que WriteFile no windows?
Eu sempre pensei que WriteFile é mais eficiente do que fwrite, porque fwrite chama para WriteFile internamente, mas o seguinte código de teste mostra-me que fwrite é mais rápido do que WriteFile.
fwrite custa 2 milisegundos enquanto o WriteFile precisa de 27000 (FILE_ATTRIBUTE_NORMAL), ambos são liberados após cada chamada de gravação. Se eu chamar WriteFile com FILE_FLAG_WRITE_THROUGH e comentar a linha FlushFileBuffers (wfile), WriteFile será mais rápido, custa 800.
Então é realmente que fwrite chama para WriteFile? O que faz uma diferença tão grande? Como o fwrite funciona internamente? Como posso gravar dados no arquivo com API de forma mais eficiente do que fwrite? (Unbufferd, synchronous).
#include <Windows.h>
#include <stdio.h>
#include <iostream>
int main() {
FILE* cfile = fopen("file1.txt", "w");
HANDLE wfile = CreateFile("file2.txt", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS,
/*FILE_ATTRIBUTE_NORMAL*/FILE_FLAG_WRITE_THROUGH, NULL);
DWORD written = 0;
DWORD start_time, end_time;
char * text = "test message ha ha ha ha";
int size = strlen(text);
int times = 999;
start_time = timeGetTime();
for(int i = 0; i < times; ++i) {
fwrite(text, 1, size, cfile);
fflush(cfile);
}
end_time = timeGetTime();
std::cout << end_time - start_time << '\n';
start_time = timeGetTime();
for(int i = 0; i < times; ++i) {
WriteFile(wfile, text, size, &written, NULL);
//FlushFileBuffers(wfile);
}
end_time = timeGetTime();
std::cout << end_time - start_time << std::endl;
system("pause");
return 0;
}
Atualizar: Obrigado pelas respostas, eis a resposta: veja o diretório VS \ VS \ crt \ src \ fflush.c:
//fflush.c
int __cdecl _fflush_nolock (FILE *str) {
//irrelevant codes
if (str->_flag & _IOCOMMIT) {
return (_commit(_fileno(str)) ? EOF : 0);
}
return 0;
}
então aqui está um sinalizador _IOCOMMIT, então veja ... \ src \ fdopen.c
FILE * __cdecl _tfdopen (int filedes, const _TSCHAR *mode) {
//irrelevant codes
while(*++mode && whileflag)
switch(*mode) {
//...
case _T('c'):
if (cnflag)
whileflag = 0;
else {
cnflag = 1;
fileflag |= _IOCOMMIT;
}
break;
//...
}
_tfopen é chamado por fopen internamente, consulte os documentos de fopen, eu acho isso:
"modo: 'c'
Ative o sinalizador de confirmação para o nome do arquivo associado para que o conteúdo do buffer do arquivo seja gravado diretamente no disco, se fflush ou _flushall for chamado. "Portanto, _commit é chamado apenas se o sinalizador 'c' for definido ao chamar fopen.
a função _commit eventualmente chama FlushFileBuffers.
Além destes, eu acho que quando eu escrevo apenas alguns dados para arquivo (não excede o tamanho do buffer), se fwrite sem fflush, o texto não será gravado aparentemente, enquanto para API, após WriteFile mesmo se eu não chamar FlushFileBuffers , quando abro o arquivo (o programa está em Sleep), o conteúdo é gravado em arquivo automaticamente, essa foi uma das razões pelas quais fiquei confuso sobre flush, essa operação pode ser feita pelo SO, WriteFile copia dados para o cache do sistema, e seu buffer de arquivos é gerenciado pelo SO, então é razoável que fflush () só chame WriteFile internamente sem descarga real, o sistema sabe quando liberá-los, talvez quando o identificador de arquivo é fechado ou quando ocorre outro acesso de E / S a esse arquivo. Então eu modifiquei o benchmark como este:
start_time = timeGetTime();
for(int i = 0; i < times; ++i) {
fwrite(text, 1, size, cfile);
fflush(cfile);
}
end_time = timeGetTime();
std::cout << end_time - start_time << '\n';
start_time = timeGetTime();
for(int i = 0; i < times; ++i) {
WriteFile(wfile, text, size, &written, NULL);
}
end_time = timeGetTime();
std::cout << end_time - start_time << std::endl;
o resultado é de vezes: 99999 fwrite: 217 WriteFile: 171
Assim, concluindo, para acelerar a operação de gravação de arquivos da API:
Não chame explicitamente os FlushFileBuffers, os dados no cache do sistema serão liberados para o disco quando necessário.
Obter um buffer para WriteFile, assim como fwrite faz, porque a chamada de API custa mais tempo do que simplesmente memcpy, chamar WriteFile quando o buffer é preenchido.