16#if UNRAVEL_PLATFORM_WINDOWS && (UNRAVEL_COMPILER_MSVC || UNRAVEL_COMPILER_CLANG)
17#define WIN32_LEAN_AND_MEAN
19#elif UNRAVEL_PLATFORM_LINUX || UNRAVEL_PLATFORM_OSX
28constexpr size_t read_chunk_size = 64 * 1024;
30enum class read_backend : std::uint8_t
37enum class read_mode : std::uint8_t
45 read_backend backend = read_backend::file_stdio;
46 double cold_seconds = 0.0;
47 double hot_seconds = 0.0;
48 std::uint64_t cold_checksum = 0;
49 std::uint64_t hot_checksum = 0;
50 size_t bytes_read = 0;
53auto backend_name(read_backend backend) ->
const char*
57 case read_backend::file_stdio:
59 case read_backend::file_istream:
60 return "file_istream";
61 case read_backend::fstream:
67auto read_mode_name(read_mode mode) ->
const char*
71 case read_mode::chunked:
72 return "chunked (64 KiB)";
73 case read_mode::whole_file:
79auto add_chunk_checksum(std::uint64_t checksum,
const std::uint8_t* data,
size_t size) -> std::uint64_t
81 for(
size_t i = 0;
i <
size; ++
i)
83 checksum +=
static_cast<std::uint64_t
>(data[
i]);
88auto add_chunk_checksum(std::uint64_t checksum,
const char* data,
size_t size) -> std::uint64_t
90 return add_chunk_checksum(checksum,
reinterpret_cast<const std::uint8_t*
>(data),
size);
93auto collect_regular_files(
const path& directory) -> std::vector<path>
95 std::vector<path> files;
97 if(!fs::exists(directory, ec) || !fs::is_directory(directory, ec))
101 for(
const auto&
entry : fs::recursive_directory_iterator(directory, ec))
107 if(
entry.is_regular_file(ec))
109 files.push_back(
entry.path());
112 std::sort(files.begin(), files.end());
116auto file_byte_size(
const path& file_path) ->
size_t
119 const auto size = fs::file_size(file_path, ec);
120 if(ec ||
size ==
static_cast<std::uintmax_t
>(-1))
124 return static_cast<size_t>(
size);
127auto total_bytes_for_files(
const std::vector<path>& files) ->
size_t
130 for(
const auto& file_path : files)
132 total += file_byte_size(file_path);
137auto format_bytes_summary(
size_t bytes) -> std::string
139 constexpr double kb = 1024.0;
140 constexpr double mb = 1024.0 * 1024.0;
141 constexpr double gb = 1024.0 * 1024.0 * 1024.0;
142 const double val =
static_cast<double>(
bytes);
145 return fmt::format(
"{:.2f} GiB", val / gb);
149 return fmt::format(
"{:.1f} MiB", val / mb);
153 return fmt::format(
"{:.1f} KiB", val / kb);
155 return fmt::format(
"{} B",
bytes);
158auto try_drop_os_page_cache() ->
bool
160#if UNRAVEL_PLATFORM_LINUX
162 FILE* drop_caches = std::fopen(
"/proc/sys/vm/drop_caches",
"we");
163 if(drop_caches ==
nullptr)
167 const int wrote = std::fputs(
"3", drop_caches);
168 const int closed = std::fclose(drop_caches);
169 return wrote != EOF && closed == 0;
170#elif UNRAVEL_PLATFORM_WINDOWS && (UNRAVEL_COMPILER_MSVC || UNRAVEL_COMPILER_CLANG)
171 HANDLE
token =
nullptr;
172 if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &
token))
176 TOKEN_PRIVILEGES privileges{};
177 privileges.PrivilegeCount = 1;
178 if(!LookupPrivilegeValueA(
nullptr, SE_INCREASE_QUOTA_NAME, &privileges.Privileges[0].Luid))
183 privileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
184 AdjustTokenPrivileges(
token, FALSE, &privileges, 0,
nullptr,
nullptr);
186 return SetSystemFileCacheSize(
static_cast<SIZE_T
>(-1),
static_cast<SIZE_T
>(-1), 0) != FALSE;
187#elif UNRAVEL_PLATFORM_OSX
189 return std::system(
"/usr/sbin/purge >/dev/null 2>&1") == 0;
195auto checksum_file_stdio_chunked(
const path& file_path) -> std::uint64_t
197 std::uint64_t checksum = 0;
198 FILE* file = std::fopen(file_path.string().c_str(),
"rb");
203 std::array<char, read_chunk_size> buffer{};
204 size_t bytes_read = 0;
205 while((bytes_read = std::fread(buffer.data(), 1, buffer.size(), file)) > 0)
207 checksum = add_chunk_checksum(checksum, buffer.data(), bytes_read);
213auto checksum_file_stdio_whole(
const path& file_path) -> std::uint64_t
215 const size_t file_size = file_byte_size(file_path);
220 FILE* file = std::fopen(file_path.string().c_str(),
"rb");
225 std::vector<char> buffer(file_size);
226 const size_t bytes_read = std::fread(buffer.data(), 1, file_size, file);
232 return add_chunk_checksum(0, buffer.data(), bytes_read);
235auto checksum_file_istream_chunked(
const path& file_path) -> std::uint64_t
237 std::uint64_t checksum = 0;
239 if(!stream.is_open())
243 std::array<char, read_chunk_size> buffer{};
246 stream.read(buffer.data(),
static_cast<std::streamsize
>(buffer.size()));
247 const auto bytes_read =
static_cast<size_t>(stream.gcount());
252 checksum = add_chunk_checksum(checksum, buffer.data(), bytes_read);
257auto checksum_file_istream_whole(
const path& file_path) -> std::uint64_t
259 const size_t file_size = file_byte_size(file_path);
265 if(!stream.is_open())
269 std::vector<char> buffer(file_size);
270 stream.read(buffer.data(),
static_cast<std::streamsize
>(file_size));
271 const auto bytes_read =
static_cast<size_t>(stream.gcount());
276 return add_chunk_checksum(0, buffer.data(), bytes_read);
279auto checksum_file_fstream_chunked(
const path& file_path) -> std::uint64_t
281 std::uint64_t checksum = 0;
282 std::ifstream stream(file_path, std::ios::binary);
287 std::array<char, read_chunk_size> buffer{};
290 stream.read(buffer.data(),
static_cast<std::streamsize
>(buffer.size()));
291 const auto bytes_read =
static_cast<size_t>(stream.gcount());
296 checksum = add_chunk_checksum(checksum, buffer.data(), bytes_read);
301auto checksum_file_fstream_whole(
const path& file_path) -> std::uint64_t
303 const size_t file_size = file_byte_size(file_path);
308 std::ifstream stream(file_path, std::ios::binary);
313 std::vector<char> buffer(file_size);
314 stream.read(buffer.data(),
static_cast<std::streamsize
>(file_size));
315 const auto bytes_read =
static_cast<size_t>(stream.gcount());
320 return add_chunk_checksum(0, buffer.data(), bytes_read);
323auto checksum_file(read_backend backend, read_mode mode,
const path& file_path) -> std::uint64_t
327 case read_backend::file_stdio:
328 return mode == read_mode::whole_file ? checksum_file_stdio_whole(file_path)
329 : checksum_file_stdio_chunked(file_path);
330 case read_backend::file_istream:
331 return mode == read_mode::whole_file ? checksum_file_istream_whole(file_path)
332 : checksum_file_istream_chunked(file_path);
333 case read_backend::fstream:
334 return mode == read_mode::whole_file ? checksum_file_fstream_whole(file_path)
335 : checksum_file_fstream_chunked(file_path);
340auto checksum_files(read_backend backend, read_mode mode,
const std::vector<path>& files) -> std::uint64_t
342 std::uint64_t checksum = 0;
343 for(
const auto& file_path : files)
345 checksum += checksum_file(backend, mode, file_path);
350auto run_backend_pass(read_backend backend,
352 const std::vector<path>& files,
353 bool drop_cache_first) -> std::pair<double, std::uint64_t>
357 const bool dropped = try_drop_os_page_cache();
360 APPLOG_WARNING(
"benchmark_directory_reads: page cache drop failed for {} / {} (cold timing may be warm)",
361 backend_name(backend),
362 read_mode_name(mode));
365 using clock_t = std::chrono::steady_clock;
366 const auto start = clock_t::now();
367 const std::uint64_t checksum = checksum_files(backend, mode, files);
368 const double seconds = std::chrono::duration<double>(clock_t::now() -
start).count();
369 return {seconds, checksum};
372auto run_all_backends(read_mode mode,
const std::vector<path>& files) -> std::array<backend_timing, 3>
374 std::array<backend_timing, 3> results{};
375 const std::array<read_backend, 3> backends = {
376 read_backend::file_stdio,
377 read_backend::file_istream,
378 read_backend::fstream,
380 const size_t total_bytes = total_bytes_for_files(files);
381 for(
size_t i = 0;
i < backends.size(); ++
i)
383 results[
i].backend = backends[
i];
384 results[
i].bytes_read = total_bytes;
385 const auto [cold_seconds, cold_checksum] = run_backend_pass(backends[i], mode, files,
true);
386 results[
i].cold_seconds = cold_seconds;
387 results[
i].cold_checksum = cold_checksum;
389 for(
size_t i = 0;
i < backends.size(); ++
i)
391 const auto [hot_seconds, hot_checksum] = run_backend_pass(backends[i], mode, files,
false);
392 results[
i].hot_seconds = hot_seconds;
393 results[
i].hot_checksum = hot_checksum;
398void log_benchmark_results(read_mode mode,
const std::array<backend_timing, 3>& results)
400 APPLOG_INFO(
"benchmark_directory_reads: --- {} ---", read_mode_name(mode));
401 APPLOG_INFO(
"benchmark_directory_reads: backend cold(s) hot(s) hot speedup checksum ok");
402 for(
const backend_timing& result : results)
404 const double speedup =
405 result.hot_seconds > 0.0 ? result.cold_seconds / result.hot_seconds : 0.0;
406 const bool checksum_ok = result.cold_checksum == result.hot_checksum;
407 APPLOG_INFO(
"benchmark_directory_reads: {:16} {:8.3f} {:8.3f} {:8.2f}x {}",
408 backend_name(result.backend),
412 checksum_ok ?
"yes" :
"no");
420 const auto files = collect_regular_files(directory);
423 APPLOG_WARNING(
"benchmark_directory_reads: no regular files under '{}'", directory.string());
426 const size_t total_bytes = total_bytes_for_files(files);
427 APPLOG_INFO(
"benchmark_directory_reads: directory='{}', files={}, total={}",
430 format_bytes_summary(total_bytes));
431 log_benchmark_results(read_mode::chunked, run_all_backends(read_mode::chunked, files));
432 log_benchmark_results(read_mode::whole_file, run_all_backends(read_mode::whole_file, files));
#define APPLOG_WARNING(...)
constexpr std::ios_base::openmode default_file_read_mode
void benchmark_directory_reads(const path &directory)
std::chrono::steady_clock clock_t
Alias for the steady clock used for timing actions.
std::vector< math::vec3 > start