Czy fwrite jest szybszy niż WriteFile w oknach?

Zawsze myślałem, że WriteFile jest bardziej wydajny niż fwrite, ponieważ fwrite wywołuje wewnętrznie WriteFile, ale poniższy kod testowy pokazuje, że fwrite jest znacznie szybszy niż WriteFile.

fwrite kosztuje 2 milisekundy, a WriteFile 27000 (FILE_ATTRIBUTE_NORMAL), obie opróżniają po każdym wywołaniu zapisu. Jeśli wywołam WriteFile z FILE_FLAG_WRITE_THROUGH i skomentuję linię FlushFileBuffers (wfile), WriteFile będzie szybszy, kosztuje 800.

Czy to naprawdę jest to, że fwrite wywołuje WriteFile? Co sprawia taką ogromną różnicę? Jak działa fwrite wewnętrznie? Jak mogę wydajniej zapisywać dane do pliku za pomocą API niż fwrite? (Niebuforowane, synchroniczne).

   #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;
   }

Aktualizacja: Dziękujemy za odpowiedzi, oto odpowiedź: zobacz katalog VS CS crc flc flush.c:

    //fflush.c
    int __cdecl _fflush_nolock (FILE *str) {
        //irrelevant codes
        if (str->_flag & _IOCOMMIT) {
                return (_commit(_fileno(str)) ? EOF : 0);
        }
        return 0;
    }

więc oto flaga _IOCOMMIT, a następnie zobacz ... 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 jest wywoływany wewnętrznie przez fopen, patrz dokumenty fopen, znajduję to:

„tryb:„ c ”

Włącz flagę zatwierdzania dla skojarzonej nazwy pliku, aby zawartość bufora plików była zapisywana bezpośrednio na dysku, jeśli zostanie wywołany fflush lub _flushall. „Tak więc _commit jest wywoływane tylko wtedy, gdy flaga 'c' jest ustawiona podczas wywoływania fopen.

funkcja _commit ostatecznie wywołuje FlushFileBuffers.

Poza tym stwierdzam, że gdy piszę tylko kilka danych do pliku (nie przekraczając rozmiaru bufora), jeśli fwrite bez fflush, tekst nie będzie zapisany, podczas gdy dla API, po WriteFile, nawet jeśli nie wywołam FlushFileBuffers , kiedy otwieram plik (program jest w trybie uśpienia), zawartość jest zapisywana automatycznie do pliku, co było jednym z powodów, dla których byłem zdezorientowany co do koloru, ta operacja może być wykonana przez system operacyjny, WriteFile kopiuje dane do pamięci podręcznej systemu, i jego bufor plików jest zarządzany przez system operacyjny, więc jest rozsądne, że fflush () wywołuje tylko WriteFile wewnętrznie bez rzeczywistego opróżniania, system wie, kiedy je opróżnić, może gdy uchwyt pliku jest zamknięty lub gdy pojawi się inny dostęp do tego pliku. Zmodyfikowałem więc benchmark:

      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;

wynikiem są czasy: 99999 fwrite: 217 WriteFile: 171

Podsumowując, aby przyspieszyć operację zapisu pliku API:

Nie wywołuj jawnie FlushFileBuffers, dane w pamięci podręcznej systemu zostaną w razie potrzeby opróżnione na dysk.

Uzyskaj bufor dla WriteFile, tak jak robi to fwrite, ponieważ wywołanie API kosztuje więcej czasu niż po prostu memcpy, wywołaj WriteFile, gdy bufor jest zapełniony.

questionAnswers(2)

yourAnswerToTheQuestion