É 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.

questionAnswers(2)

yourAnswerToTheQuestion