Warum ist C <stdio.h> FILE* fread() schneller als Win32 ReadFile()?
Vergleich Lesen von Dateien mithilfe der folgenden drei Methoden:
- C
<stdio.h>
FILE*
- Win32
CreateFile()
/ReadFile()
- Win32 memory mapping
Bemerkte ich, dass #1 ist schneller als die #2, und #3 ist die Schnellste.
z.B. in der Reihenfolge der schnellsten zum langsamsten, für die Bearbeitung einer 900MB Datei test, ich bekam diese Ergebnisse:
Win32-Speicher-mapping: 821.308 ms
C-Datei (FILE*): 1779.83 ms
Win32-Datei (CreateFile): 3649.67 ms
Warum ist die C <stdio.h>
Technik schneller als Win32 ReadFile()
Zugang? Ich würde erwarten, dass raw-Win32-APIs zu haben weniger Aufwand als CRT. Was vermisse ich hier?
Kompilierbare testen von C++ - source-code folgt.
BEARBEITEN
Ich wiederholte die tests mit 4KB lese-Puffer und mit drei verschiedenen Dateien (mit gleichem Inhalt) zu vermeiden caching Effekte verfälschen können performance-Messungen, und nun die Ergebnisse wie erwartet sind.
Zum Beispiel, für eine Datei von circa 400 MB hier sind die Ergebnisse:
-
Win32 memory mapping: 305.908 ms
-
Win32-Datei (CreateFile): 451.402 ms
-
C-Datei (FILE*): 460.579 ms
////////////////////////////////////////////////////////////////////////////////
//Test file reading using C FILE*, Win32 CreateFile and Win32 memory mapping.
////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
#include <exception>
#include <iostream>
#include <stdexcept>
#include <vector>
#include <Windows.h>
//------------------------------------------------------------------------
// Performance (speed) measurement
//------------------------------------------------------------------------
long long counter()
{
LARGE_INTEGER li;
QueryPerformanceCounter(&li);
return li.QuadPart;
}
long long frequency()
{
LARGE_INTEGER li;
QueryPerformanceFrequency(&li);
return li.QuadPart;
}
void print_time(const long long start, const long long finish,
const char * const s)
{
std::cout << s << ": " << (finish - start) * 1000.0 / frequency() << " ms\n";
}
//------------------------------------------------------------------------
// RAII handle wrappers
//------------------------------------------------------------------------
struct c_file_traits
{
typedef FILE* type;
static FILE* invalid_value()
{
return nullptr;
}
static void close(FILE* f)
{
fclose(f);
}
};
struct win32_file_traits
{
typedef HANDLE type;
static HANDLE invalid_value()
{
return INVALID_HANDLE_VALUE;
}
static void close(HANDLE h)
{
CloseHandle(h);
}
};
struct win32_handle_traits
{
typedef HANDLE type;
static HANDLE invalid_value()
{
return nullptr;
}
static void close(HANDLE h)
{
CloseHandle(h);
}
};
template <typename Traits>
class handle
{
public:
typedef typename Traits::type type;
handle()
: _h(Traits::invalid_value())
{
}
explicit handle(type h)
: _h(h)
{
}
~handle()
{
close();
}
bool valid() const
{
return (_h != Traits::invalid_value());
}
type get() const
{
return _h;
}
void close()
{
if (valid())
Traits::close(_h);
_h = Traits::invalid_value();
}
void reset(type h)
{
if (h != _h)
{
close();
_h = h;
}
}
private: //Ban copy
handle(const handle&);
handle& operator=(const handle&);
private:
type _h; //wrapped raw handle
};
typedef handle<c_file_traits> c_file_handle;
typedef handle<win32_file_traits> win32_file_handle;
typedef handle<win32_handle_traits> win32_handle;
//------------------------------------------------------------------------
// File reading tests using various techniques
//------------------------------------------------------------------------
unsigned long long count_char_using_c_file(const std::string& filename, const char ch)
{
unsigned long long char_count = 0;
#pragma warning(push)
#pragma warning(disable: 4996) //fopen use is OK
c_file_handle file(fopen(filename.c_str(), "rb"));
#pragma warning(pop)
if (!file.valid())
throw std::runtime_error("Can't open file.");
std::vector<char> read_buffer(4*1024); //4 KB
bool has_more_data = true;
while (has_more_data)
{
size_t read_count = fread(read_buffer.data(), 1, read_buffer.size(), file.get());
for (size_t i = 0; i < read_count; i++)
{
if (read_buffer[i] == ch)
char_count++;
}
if (read_count < read_buffer.size())
has_more_data = false;
}
return char_count;
}
unsigned long long count_char_using_win32_file(const std::string& filename, const char ch)
{
unsigned long long char_count = 0;
win32_file_handle file(::CreateFileA(
filename.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
nullptr
)
);
if (!file.valid())
throw std::runtime_error("Can't open file.");
std::vector<char> read_buffer(4*1024); //4 KB
bool has_more_data = true;
while (has_more_data)
{
DWORD read_count = 0;
if (!ReadFile(file.get(), read_buffer.data(), read_buffer.size(), &read_count, nullptr))
throw std::runtime_error("File read error using ReadFile().");
for (size_t i = 0; i < read_count; i++)
{
if (read_buffer[i] == ch)
char_count++;
}
if (read_count < sizeof(read_buffer))
has_more_data = false;
}
return char_count;
}
//Memory-map a file.
class file_map
{
public:
explicit file_map(const std::string& filename)
: _view(nullptr), _length(0)
{
_file.reset(::CreateFileA(
filename.c_str(),
GENERIC_READ,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr));
if (!_file.valid())
return;
LARGE_INTEGER file_size;
if (!GetFileSizeEx(_file.get(), &file_size))
return;
if (file_size.QuadPart == 0)
return;
_mapping.reset(::CreateFileMapping(
_file.get(), nullptr,
PAGE_READONLY,
0,
0,
nullptr)
);
if (!_mapping.valid())
return;
_view = reinterpret_cast<char*>
(::MapViewOfFile(_mapping.get(), FILE_MAP_READ, 0, 0, 0));
if (!_view)
return;
_length = file_size.QuadPart;
}
~file_map()
{
if (_view)
UnmapViewOfFile(_view);
}
bool valid() const
{
return (_view != nullptr);
}
const char * begin() const
{
return _view;
}
const char * end() const
{
return begin() + length();
}
unsigned long long length() const
{
return _length;
}
private: //ban copy
file_map(const file_map&);
file_map& operator=(const file_map&);
private:
win32_file_handle _file;
win32_handle _mapping;
char* _view;
unsigned long long _length; //in bytes
};
unsigned long long count_char_using_memory_mapping(const std::string& filename, const char ch)
{
unsigned long long char_count = 0;
file_map view(filename);
if (!view.valid())
throw std::runtime_error("Can't create memory-mapping of file.");
for (auto it = view.begin(); it != view.end(); ++it)
{
if (*it == ch)
{
char_count++;
}
}
return char_count;
}
template <typename TestFunc>
void run_test(const char * message, TestFunc test, const std::string& filename, const char ch)
{
const long long start = counter();
const unsigned long long char_count = test(filename, ch);
const long long finish = counter();
print_time(start, finish, message);
std::cout << "Count of \'" << ch << "\' : " << char_count << "\n\n";
}
int main(int argc, char* argv[])
{
static const int kExitOk = 0;
static const int kExitError = 1;
if (argc != 3)
{
std::cerr << argv[0] << " <char> <filename>.\n";
std::cerr << "Counts occurrences of ASCII character <char>\n";
std::cerr << "in the <filename> file.\n\n";
return kExitError;
}
const char ch = *(argv[1]);
const std::string filename = argv[2];
try
{
//Execute tests on THREE different files with the same content,
//to avoid caching effects.
//(file names have incremental number suffix).
run_test("C <stdio.h> file (FILE*)", count_char_using_c_file, filename + "1", ch);
run_test("Win32 file (CreateFile)", count_char_using_win32_file, filename + "2", ch);
run_test("Win32 memory mapping", count_char_using_memory_mapping, filename + "3", ch);
return kExitOk;
}
catch (const std::exception& e)
{
std::cerr << "\n*** ERROR: " << e.what() << '\n';
return kExitError;
}
}
////////////////////////////////////////////////////////////////////////////////
- Zu reduzieren syscall-overhead, der
FILE
Umsetzung eigene Pufferung. Auch nichts Magisches. - Als kurzer Hinweis: Wenn Sie back-to-back-tests auf die GLEICHE Datei, das zweite Testergebnis verzerrt werden durch Datei-Daten - /Metadaten-caching. Windows speichert Datei-Daten wie die Festplatten-controller-hardware. Ich konnte nicht sagen, von einem schnellen Lesen des Codes, ob die obige Aussage gilt, aber es am häufigsten ist die Ursache der Dinge wie diese.
- Was passiert, wenn Sie die Puffergröße erhöhen in
count_char_using_win32_file()
? - Auf meinem Rechner
fopen
ist die langsamste nach setzen von ' Lesen-Puffer entsprechend der claster-Größe (4096) - Ich erinnere mich gelesen zu haben noch eine Frage auf stackoverflow Fragen, warum fread ist schneller als ReadFile, es ist stackoverflow.com/questions/14290337/.... Der Kerl war es zu Lesen, byte-by-byte, so wird natürlich fread war schneller mit seiner eigenen Pufferung.
- Welche tools Sie verwenden und welche Befehle Sie verwenden, um zu bauen, der Maßstab? Ich bekomme sehr unterschiedliche Ergebnisse, je nachdem diese details.
- Ist das wirklich ein Duplikat von "fwrite Ist schneller als WriteFile in windows?" - Frage? Ich würde nicht denken, dass das Lesen einer Datei haben die gleiche Art von Interaktion mit Spül-Daten in eine Datei schreiben tut.
- Ich verwendet VS2010 SP1. Die .exe gebaut wurde, aus der IDE, im release-build, mit default Einstellungen.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Liefen gerade einige tests auf meinem Rechner, die zeigt, dass eine Erhöhung der Puffergröße tatsächlich erhöht Leistung:
Einige Schritte in die Visual Studio 2012-debugger zeigt, dass die Puffer-Größe der DATEI,* die Methode ist 4096 bytes, zumindest auf meinem Rechner. (Und wie andere schon gesagt haben, es ruft
ReadFile
zu, es sei denn, Sie Lesen von der Konsole aus.)Interessant ist auch, dass große Puffer geringfügig verlangsamen die Leistung. Verschieben der
new
Betreiber außerhalb der test löst nicht das Problem entweder.Zunächst die memory-mapped-test war ziemlich langsam, für mich ran weil ich es im Debug-Modus. Ich habe alle aktualisierten Ergebnisse mit dem Release-Modus kompilieren. Speicher-mapping wurde der erste.
CreateFile()
/ReadFile()
Technik und die CFILE*
<stdio.h>
Technik, und mit 16-KB-Puffer, Sie sind fast gleichwertig (memory mapping ist immer noch #1).C <stdio.h> file (FILE*)
Modus ging hinunter, um1080.64 ms
war mal wieder schneller als dann CreateFile-Methode. Können Sie reproduzieren diese?FILE*
- und Win32-Dateien, bekomme ich fast die gleichen Ergebnisse, mit einigen "Schwankungen" von wenigen Zehnteln von Millisekunden zu Gunsten der einen oder der anderen.Den schnellsten Zugriff auf die Festplatte, die ich jemals erreicht wurde mit
ReadFile
. Aber ich habe ausdrücklich öffnete die Datei mit den flags um meine disk access-und caching-Anforderungen. Wenn Sie es einfach verwenden, verbatim, der Vergleich ist ein wenig lahm.Lesen Sie mehr über die Funktion, sowie
CreateFile
. Sie finden, dass Sie Lesen können Daten in (vielfachen von) Sektor-großen Blöcken, die auf den Sektor ausgerichteten Speicher. Dann werden Sie aus-führenfread
.Wie schon andere gesagt haben,
fread
eigene Pufferung. Ihre Pufferung Umsetzung mitReadFile
muss noch arbeiten.Check-out MSDN. Alle Informationen gibt es. Konkret hier:
Datei-Pufferung
Caching-Verhalten
FILE_FLAG_SEQUENTIAL_SCAN
Flagge. Können Sie etwas konkreter über Ihren Vorschlag? Welche flags muss ich verwenden für optimale sequentielle lese-Zugriff?Sind Sie sicher, dass Sie testen, richtig?
Wie sind Sie mit der Buchhaltung für die Datenträger-position, suchen, Zeit, Datei Cachen etc?
stdio und win32 sind letztlich die gleichen Aufrufe der Windows-kernel, die Datei zu öffnen.
mmap macht die Dinge ein wenig anders, weil Sie reservieren können tatsächlich Lesen der Daten, bis es verwendet wird, wenn Sie eine Feste Größe der Datei und Leistung ankommt, mmap ist eine gute option
Bei der Verwendung von memory-mapped-Datei, gibt es keine Notwendigkeit, kopieren Sie den Inhalt der Datei auf Ihre Bewerbung - es wird abgebildet als ein Teil des virtuellen Speichers, die direkt aus dem Betriebssystem, so dass, wenn Sie den Zugriff auf die Datei-Inhalte, es muss nur gelesen werden, direkt in eine Seite, dass geht in dem zugeordneten Speicher.
Wenn Sie tun die Arbeit korrekt, wenn Sie mit der Win32-API, es sollte schneller sein, die die C-stdio, da es weniger overhead in dem Aufruf. Es ist jedoch durchaus möglich, dass Sie sich nicht immer die ideale balance zwischen system-call-overhead und "zu groß einen Puffer, so dass das Lesen dauert länger als nötig." Ich würde vorschlagen, Sie versuchen mit 4K oder 8K (vielleicht sogar 32K) als Puffer in der Win32-API-Funktionalität - mit einer Puffer-Größe, die ein Vielfaches von 4K ist ideal, da eine Speicher-Seite ist (in der Regel) 4 KB. Weniger Aufrufe der API sorgt für weniger Aufwand, aber Sie wollen nicht zu weit zu gehen.
[Ich habe einige Tests wie diese auf Linux den anderen Tag, und fand ähnliche Ergebnisse, und aus meiner Erfahrung ist es: verwenden Sie unterschiedliche Dateien für jeden test, da sonst das Dateisystem-caching helfen die tests, die später ausgeführt werden!].