From 2941ce89c1cdfd81e26c3de21bfaa368f18a9ea6 Mon Sep 17 00:00:00 2001 From: Chris Blume Date: Thu, 25 Sep 2025 15:48:56 -0400 Subject: [PATCH] Use unbuffered IO --- Code/EntryPoint.cpp | 10 ++++++++-- Code/OverlappedIOFileRead.cpp | 19 ++++++++++++++++--- Code/OverlappedIOFileRead.hpp | 14 ++++++++++++-- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/Code/EntryPoint.cpp b/Code/EntryPoint.cpp index bb3957e..4ffbb46 100644 --- a/Code/EntryPoint.cpp +++ b/Code/EntryPoint.cpp @@ -2,10 +2,16 @@ #include "OverlappedIOFileRead.hpp" -int main() { +int main(int argc, const char * argv[]) { + if (argc != 2) { + std::cerr << "Error: No file provided.\n"; + std::cerr << "Use the form: FileReadSpeedTest.exe C:\\my\\file.txt" << std::endl; + return -1; + } + const DWORD worker_thread_count = 6; // TODO: Take the file as part of the command line parameters. - auto overlapped_io_file_read = PrepareToReadFile(R"(../../Test Data/crystaldiskmark nvme ssd.png)", worker_thread_count); + auto overlapped_io_file_read = PrepareToReadFile(argv[1], worker_thread_count); if (!overlapped_io_file_read.has_value()) { std::cerr << "Error"; return -1; diff --git a/Code/OverlappedIOFileRead.cpp b/Code/OverlappedIOFileRead.cpp index ee98158..092f598 100644 --- a/Code/OverlappedIOFileRead.cpp +++ b/Code/OverlappedIOFileRead.cpp @@ -14,10 +14,14 @@ namespace { + std::chrono::time_point pre_open_time_; + + std::optional CreateOverlappedIOFile(LPCSTR file_name) noexcept { - // TODO: Add FILE_FLAG_NO_BUFFERING flag - // and FILE_FLAG_RANDOM_ACCESS or FILE_FLAG_SEQUENTIAL_SCAN - HANDLE file_handle = CreateFileA(file_name, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + pre_open_time_ = std::chrono::high_resolution_clock::now(); + // Use FILE_FLAG_RANDOM_ACCESS instead of FILE_FLAG_SEQUENTIAL_SCAN for file formats where that is a better fit + // FILE_FLAG_NO_BUFFERING + HANDLE file_handle = CreateFileA(file_name, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_SEQUENTIAL_SCAN | FILE_FLAG_NO_BUFFERING, NULL); if (file_handle == INVALID_HANDLE_VALUE) { // GetLastError return std::nullopt; @@ -183,6 +187,11 @@ void OverlappedIOFileRead::WaitForThreadsToFinish() noexcept { // Wait for worker threads to finish WaitForMultipleObjects(static_cast(thread_pool_.threads_.size()), (const HANDLE*)thread_pool_.threads_.data(), TRUE, INFINITE); + auto open_delay = std::chrono::duration_cast(overlapped_io_file_.file_open_time_ - pre_open_time_); + auto open_to_read_delay = std::chrono::duration_cast(read_issue_time_ - overlapped_io_file_.file_open_time_); + std::cout << "Open delay: " << open_delay << std::endl; + std::cout << "Open to read delay: " << open_to_read_delay << std::endl; + size_t i = 0; for (auto& context : contexts_) { auto read_issue_delay = std::chrono::duration_cast(context.request_start_time_ - read_issue_time_); @@ -231,6 +240,10 @@ std::expected PrepareToReadFile(LP return std::unexpected{PrepareToReadFileError::CouldNotCreateCompletionPort}; } + // TODO: Creating the threads takes a significant amount of time. + // This happens after the file is opened and the completion port is created. + // The OS *could* begin prefetching the data. I don't know if it does or not. + // But if it does, this gives it ample opportunity to impact the measurements. auto thread_pool = CreateThreadPool(WorkerThread, completion_port->handle_, worker_thread_count); if (!thread_pool.has_value()) { return std::unexpected{PrepareToReadFileError::CouldNotCreateThread}; diff --git a/Code/OverlappedIOFileRead.hpp b/Code/OverlappedIOFileRead.hpp index 46f5952..65bfb85 100644 --- a/Code/OverlappedIOFileRead.hpp +++ b/Code/OverlappedIOFileRead.hpp @@ -14,10 +14,21 @@ #define NOMINMAX #include -using OverlappedIOFile = SpecificHandleObject; +//using OverlappedIOFile = SpecificHandleObject; using CompletionPort = SpecificHandleObject; using Thread = SpecificHandleObject; +struct OverlappedIOFile : public SpecificHandleObject { + + explicit OverlappedIOFile(HANDLE handle) noexcept + : SpecificHandleObject(std::move(handle)) + , file_open_time_(std::chrono::high_resolution_clock::now()) + {} + + std::chrono::time_point file_open_time_; + +}; + class ThreadPool { public: @@ -40,7 +51,6 @@ struct IOContext { std::chrono::time_point request_start_time_; std::chrono::time_point request_complete_time_; - }; class OverlappedIOFileRead {