Skip to content

Instantly share code, notes, and snippets.

@asmichi
Last active February 27, 2024 13:50
Show Gist options
  • Save asmichi/2953e74b1bbdfcf73b34ea8928b06a66 to your computer and use it in GitHub Desktop.
Save asmichi/2953e74b1bbdfcf73b34ea8928b06a66 to your computer and use it in GitHub Desktop.
Benchmark Windows file system metadata query
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <array>
#include <cstdio>
#include <chrono>
#include <thread>
#include <vector>
constexpr int FileCount = 1024;
constexpr int IterationCount = 100;
const wchar_t* g_Root;
std::array<std::wstring, FileCount> g_Files;
struct State
{
std::thread Thread;
std::chrono::microseconds ElapsedMicroseconds;
};
void benchmark(State* pState)
{
const auto start = std::chrono::high_resolution_clock::now();
WIN32_FILE_ATTRIBUTE_DATA data{};
WIN32_FIND_DATA findData{};
for (int i = 0; i < IterationCount; i++)
{
for (auto& f : g_Files)
{
if (!GetFileAttributesExW(f.c_str(), GetFileExInfoStandard, &data))
{
std::printf("error: GetFileAttributesExW failed: %d\n", GetLastError());
return;
}
//auto hFile = CreateFileW(f.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
//if (hFile == INVALID_HANDLE_VALUE)
//{
// std::printf("error: CreateFileW failed: %d\n", GetLastError());
// return;
//}
//CloseHandle(hFile);
//auto hFind = FindFirstFileExW(f.c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, 0);
//if (hFind == INVALID_HANDLE_VALUE)
//{
// std::printf("error: FindFirstFileExW failed: %d\n", GetLastError());
// return;
//}
//FindClose(hFind);
}
}
pState->ElapsedMicroseconds = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - start);
}
int wmain(int argc, wchar_t* argv[])
{
if (argc < 3)
{
std::printf("exe <Root> <ThreadCount>\n");
exit(1);
}
g_Root = argv[1];
int threadCount = _wtoi(argv[2]);
if (threadCount <= 0)
{
std::printf("Invalid thread count: %ls\n", argv[2]);
exit(1);
}
if (!CreateDirectoryW(g_Root, nullptr) && GetLastError() != ERROR_ALREADY_EXISTS)
{
std::printf("fatal error: CreateDirectoryW failed: %ls, %d\n", g_Root, GetLastError());
exit(1);
}
wchar_t buf[MAX_PATH + 10];
for (int i = 0; i < FileCount; i++)
{
wsprintf(buf, L"%ls\\File%04d", g_Root, i);
g_Files[i] = buf;
auto hFile = CreateFileW(g_Files[i].c_str(), 0, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
if (hFile == INVALID_HANDLE_VALUE)
{
std::printf("fatal error: CreateFileW failed: %ls, %d\n", g_Files[i].c_str(), GetLastError());
exit(1);
}
CloseHandle(hFile);
}
long totalInvocation = static_cast<long>(threadCount) * IterationCount * FileCount;
std::printf("File# : %d\n", FileCount);
std::printf("Thread# : %d\n", threadCount);
std::printf("Iteration# : %d\n", IterationCount);
std::printf("Invocation#: %ld\n", totalInvocation);
std::vector<State> states;
for (int i = 0; i < threadCount; i++)
{
states.push_back(State{});
}
for (auto& s : states)
{
s.Thread = std::thread(benchmark, &s);
}
std::chrono::microseconds totalMicroseconds{};
for (auto& s : states)
{
s.Thread.join();
totalMicroseconds += s.ElapsedMicroseconds;
}
auto totalMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(totalMicroseconds);
std::printf("%lld ms\n", totalMilliseconds.count());
auto millisecondsPerInvocation = static_cast<double>(totalMicroseconds.count()) / totalInvocation;
std::printf("%.3f us per invocation\n", millisecondsPerInvocation);
}
// When a directory has a large number of files (>1000 or so),
// iterating over files in it is sometimes up to 10 times slower.
//
// This benchmark creates 10000 files and then performs GetFileAttributesExW on each file.
//
// Actual workloads will be some massively parallel operation where:
//
// 1. A build system tests if input files are updated or not.
// 2. Clang preprocesses and tries to locate header files from include search directories.
// (Not MSVC which is optimized for Windows file system.)
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <array>
#include <cstdio>
#include <chrono>
#include <thread>
#include <vector>
/*
F:\TestDir>F:\source\ConsoleApplication1\x64\Release\ConsoleApplication1.exe F:\TestDir\ 1
File# : 10000
Iteration# : 10
Invocation#: 100000
1516 ms
15.168 us per invocation
F:\TestDir>F:\source\ConsoleApplication1\x64\Release\ConsoleApplication1.exe F:\TestDir\ 1
File# : 10000
Iteration# : 10
Invocation#: 100000
325 ms
3.257 us per invocation
F:\TestDir>F:\source\ConsoleApplication1\x64\Release\ConsoleApplication1.exe F:\TestDir\ 1
File# : 10000
Iteration# : 10
Invocation#: 100000
1063 ms
10.635 us per invocation
F:\TestDir>F:\source\ConsoleApplication1\x64\Release\ConsoleApplication1.exe F:\TestDir\ 1
File# : 10000
Iteration# : 10
Invocation#: 100000
313 ms
3.133 us per invocation
*/
constexpr int FileCount = 10000;
constexpr int IterationCount = 10;
int wmain(int argc, wchar_t* argv[])
{
if (argc < 2)
{
std::printf("exe <Root>\n");
return 1;
}
const wchar_t* root = argv[1];
if (!CreateDirectoryW(root, nullptr) && GetLastError() != ERROR_ALREADY_EXISTS)
{
std::printf("fatal error: CreateDirectoryW failed: %ls, %d\n", root, GetLastError());
exit(1);
}
std::vector<std::wstring> files;
for (int i = 0; i < FileCount; i++)
{
wchar_t buf[MAX_PATH + 10];
wsprintf(buf, L"%ls\\File%04d", root, i);
files.push_back(buf);
auto hFile = CreateFileW(buf, 0, 0, nullptr, CREATE_ALWAYS, 0, nullptr);
if (hFile == INVALID_HANDLE_VALUE)
{
std::printf("fatal error: CreateFileW failed: %ls, %d\n", buf, GetLastError());
exit(1);
}
CloseHandle(hFile);
}
constexpr long totalInvocation = IterationCount * FileCount;
std::printf("File# : %d\n", FileCount);
std::printf("Iteration# : %d\n", IterationCount);
std::printf("Invocation#: %ld\n", totalInvocation);
{
const auto start = std::chrono::high_resolution_clock::now();
WIN32_FILE_ATTRIBUTE_DATA data{};
WIN32_FIND_DATA findData{};
for (int i = 0; i < IterationCount; i++)
{
for (auto& f : files)
{
if (!GetFileAttributesExW(f.c_str(), GetFileExInfoStandard, &data))
{
std::printf("error: GetFileAttributesExW failed: %d\n", GetLastError());
return 1;
}
//auto hFile = CreateFileW(f.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr);
//if (hFile == INVALID_HANDLE_VALUE)
//{
// std::printf("error: CreateFileW failed: %d\n", GetLastError());
// return;
//}
//CloseHandle(hFile);
//auto hFind = FindFirstFileExW(f.c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, 0);
//if (hFind == INVALID_HANDLE_VALUE)
//{
// std::printf("error: FindFirstFileExW failed: %d\n", GetLastError());
// return;
//}
//FindClose(hFind);
}
}
auto elapsedMicroseconds = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - start);
auto elapsedMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(elapsedMicroseconds);
std::printf("%lld ms\n", elapsedMilliseconds.count());
auto microsecondsPerInvocation = static_cast<double>(elapsedMicroseconds.count()) / totalInvocation;
std::printf("%.3f us per invocation\n", microsecondsPerInvocation);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment