为什么 C <stdio.h> FILE* fread() 比 Win32 ReadFile() 快?
Why is C <stdio.h> FILE* fread() faster than Win32 ReadFile()?
使用以下三种技术比较读取文件:
- C
<stdio.h>
FILE*
- Win32
CreateFile()
/ReadFile()
- Win32 内存映射
我注意到 #1 比 #2 快,#3 是最快的。
例如,从最快到最慢排序,用于处理 900MB 的测试文件,我得到了以下结果:
Win32 内存映射:821.308 ms
C 文件 (文件*): 1779.83 ms
Win32 文件 (创建文件): 3649.67 ms
为什么 C <stdio.h>
技术比 Win32 ReadFile()
访问更快?我希望原始 Win32 API 的开销比 CRT 少。我在这里错过了什么?
可编译的测试C++源代码如下。
编辑
我使用 4KB 读取缓冲区并使用三个不同的文件(具有相同的内容)重复测试,以避免可能扭曲性能测量的缓存效果,现在结果符合预期。
例如,对于大约 400 MB 的文件,结果为:
-
Win32 内存映射:305.908 ms
-
Win32 文件 (创建文件): 451.402 ms
-
C 文件 (文件*): 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() << " msn";
}
//------------------------------------------------------------------------
// 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 << "nn";
}
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.nn";
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;
}
}
////////////////////////////////////////////////////////////////////////////////
刚刚在我的机器上运行了一些测试,表明增加缓冲区大小实际上提高了性能:
C <stdio.h> file (FILE*): 1431.93 ms
Bufsize: 0
Count of 'x' : 3161882
Win32 file (CreateFile): 2289.45 ms
Bufsize: 1024
Count of 'x' : 3161882
Win32 file (CreateFile): 1714.5 ms
Bufsize: 2048
Count of 'x' : 3161882
Win32 file (CreateFile): 1479.16 ms
Bufsize: 4096
Count of 'x' : 3161882
Win32 file (CreateFile): 1328.25 ms
Bufsize: 8192
Count of 'x' : 3161882
Win32 file (CreateFile): 1256.1 ms
Bufsize: 16384
Count of 'x' : 3161882
Win32 file (CreateFile): 1223.54 ms
Bufsize: 32768
Count of 'x' : 3161882
Win32 file (CreateFile): 1224.84 ms
Bufsize: 65536
Count of 'x' : 3161882
Win32 file (CreateFile): 1212.4 ms
Bufsize: 131072
Count of 'x' : 3161882
Win32 file (CreateFile): 1238.09 ms
Bufsize: 262144
Count of 'x' : 3161882
Win32 file (CreateFile): 1209.2 ms
Bufsize: 524288
Count of 'x' : 3161882
Win32 file (CreateFile): 1223.67 ms
Bufsize: 1048576
Count of 'x' : 3161882
Win32 file (CreateFile): 1349.98 ms
Bufsize: 2097152
Count of 'x' : 3161882
Win32 memory mapping: 796.281 ms
Bufsize: 0
Count of 'x' : 3161882
Visual Studio 2012 调试器中的一些步骤显示 FILE* 方法的缓冲区大小为 4096 字节,至少在我的计算机上是这样。(正如其他人已经说过的那样,除非您从控制台阅读,否则它也调用ReadFile
。
同样有趣的是,大缓冲区会略微降低性能。将new
运算符移出测试也不能解决问题。
首先,内存映射测试对我来说很慢,因为我在调试模式下运行它。我已经使用发布模式编译更新了所有结果。内存映射成为第一个。
我获得的最快的磁盘访问是使用 ReadFile
。 但我专门打开了带有标志的文件,以满足我的磁盘访问和缓存要求。 如果你只是逐字逐句地使用它,比较有点蹩脚。
您应该阅读有关该功能的更多信息,以及 CreateFile
. 您会发现可以将扇区大小块中的数据读取到扇区对齐的内存中。 然后你会超越fread
.
正如其他人所说,fread
正在做自己的缓冲。 使用 ReadFile
进行缓冲实现仍然需要工作。
查看 MSDN。 所有信息都在那里。 具体来说,在这里:
-
文件缓冲
-
缓存行为
你确定你测试正确吗?
您如何考虑磁盘位置、寻道时间、文件缓存等?
stdio 和 win32 最终对 Windows 内核进行相同的调用以打开文件。
mmap 的做法略有不同,因为它可以保留实际读取的数据,直到使用为止 - 如果您有固定的文件大小并且性能很重要,mmap 是一个不错的选择
使用内存映射文件时,无需将文件的内容复制到您的应用程序 - 它直接从操作系统作为虚拟内存的一部分映射进来,因此当您访问文件内容时,只需将其直接读取到进入映射内存的页面中。
如果在使用 Win32 API 时正确执行工作,则它应该比 C stdio 更快,因为调用中的开销更少。但是,您很可能没有在系统调用开销和"缓冲区太大,因此读取时间超过必要时间"之间取得理想的平衡。我建议您尝试在 Win32 API 功能中使用 4K 或 8K(甚至可能是 32K)作为缓冲区 - 缓冲区大小是 4K 的倍数是理想的,因为内存页(通常)为 4KB。对 API 的调用越少,开销就越少,但您不想走得太远。
[前几天我在 Linux 上做了一些这样的测试,发现了类似的结果 - 根据我在那里的经验:为每个测试使用不同的文件,否则你的文件系统缓存将有助于稍后运行的测试!
- Win32编译器选项和内存分配
- C++win32 API创建多个类似视口的窗口
- WM_CTLCOLORSTATIC从未在WIN32应用程序中触发
- 检测win32服务创建和删除的最佳方法
- WIN32:C++,为什么在WM_CLOSE上调用Messagebox函数程序正在冻结
- 将 win32 hbitmap 转换为 winrt softwarebitmap
- 使用 WIN32 API (C/C++) 对特定树视图项进行着色
- Alt+Enter 在 Win32 应用中,管理大小调整和分辨率
- VSCode C++ 编译的exe感染了Win32:TrojanX-gen[Trj]
- 从预处理器获取 Windows 版本(C++ Win32)
- Issues with Win32 ReadProcessMemory API
- Win32 发送输入鼠标移动滞后并冻结
- 如何从 Win32 C++ 应用程序输出到父控制台窗口?
- C++线程:如何在一个线程仍在运行时阻止另一个线程执行 (Win32)
- 将 Win32/WinAPI 应用程序移植到 wxWidgets
- Win32 API 控制台光标在 WriteConsole 后不移动
- C++ Win32 Threads
- C++ win32 如何使密码字段可选并启用复制和粘贴?
- 对fread的返回值感到困惑
- 为什么 C <stdio.h> FILE* fread() 比 Win32 ReadFile() 快?