Last active
February 27, 2024 13:50
-
-
Save asmichi/2953e74b1bbdfcf73b34ea8928b06a66 to your computer and use it in GitHub Desktop.
Benchmark Windows file system metadata query
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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