Implementation of IO Ring in Windows
Fixing the two compilation warnings.
This commit is contained in:
BIN
.cache/clangd/index/arena.c.9D15F3F90FD5376F.idx
Normal file
BIN
.cache/clangd/index/arena.c.9D15F3F90FD5376F.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/arena.h.D70DBB2C6778A245.idx
Normal file
BIN
.cache/clangd/index/arena.h.D70DBB2C6778A245.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/base.h.2DF61E974E1BB064.idx
Normal file
BIN
.cache/clangd/index/base.h.2DF61E974E1BB064.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/file_hasher.c.AC1BEF31045A7497.idx
Normal file
BIN
.cache/clangd/index/file_hasher.c.AC1BEF31045A7497.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/lf_mpmc.h.FB4A8CD7AC664EBA.idx
Normal file
BIN
.cache/clangd/index/lf_mpmc.h.FB4A8CD7AC664EBA.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/platform.c.2B562A3FE6816950.idx
Normal file
BIN
.cache/clangd/index/platform.c.2B562A3FE6816950.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/xxh_x86dispatch.h.3E8D4826C191778C.idx
Normal file
BIN
.cache/clangd/index/xxh_x86dispatch.h.3E8D4826C191778C.idx
Normal file
Binary file not shown.
BIN
.cache/clangd/index/xxhash.h.11DA710B069D4A59.idx
Normal file
BIN
.cache/clangd/index/xxhash.h.11DA710B069D4A59.idx
Normal file
Binary file not shown.
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,5 +3,6 @@ file_hasher.ilk
|
||||
file_hasher.rdi
|
||||
file_hasher.exe
|
||||
file_hashes.txt
|
||||
Binaries/file_hashes.txt
|
||||
file_list.txt
|
||||
temp_code.c
|
||||
|
||||
14
README.md
14
README.md
@@ -5,14 +5,16 @@ Collects some metadata and hashes files.
|
||||
## Building:
|
||||
### Windows:
|
||||
#### Release:
|
||||
clang-cl /O3 file_hasher.c xxh_x86dispatch.c advapi32.lib
|
||||
clang -O3 file_hasher.c xxh_x86dispatch.c -ladvapi32 -o file_hasher
|
||||
gcc -O3 file_hasher.c xxh_x86dispatch.c -ladvapi32 -o file_hasher
|
||||
clang-cl /O3 file_hasher.c xxh_x86dispatch.c
|
||||
|
||||
Note: MinGW does not provide IO Ring headers yet, to fix that include ioringapi.c, this will dynamically load all the functions and define all the symbols necessary to replace the official header.
|
||||
clang -O3 file_hasher.c xxh_x86dispatch.c -o file_hasher
|
||||
gcc -O3 file_hasher.c xxh_x86dispatch.c -o file_hasher
|
||||
|
||||
#### Debug:
|
||||
clang-cl /Zi /Od file_hasher.c xxh_x86dispatch.c advapi32.lib
|
||||
clang -g -O0 file_hasher.c xxh_x86dispatch.c -ladvapi32 -o file_hasher
|
||||
gcc -g -O0 file_hasher.c xxh_x86dispatch.c -ladvapi32 -o file_hasher
|
||||
clang-cl /Zi /Od file_hasher.c xxh_x86dispatch.c
|
||||
clang -g -O0 file_hasher.c xxh_x86dispatch.c -o file_hasher
|
||||
gcc -g -O0 file_hasher.c xxh_x86dispatch.c -o file_hasher
|
||||
|
||||
### Linux:
|
||||
#### Release:
|
||||
|
||||
16
base.h
16
base.h
@@ -1,9 +1,11 @@
|
||||
#pragma once
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
|
||||
#include <assert.h>
|
||||
#include <immintrin.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -11,13 +13,25 @@
|
||||
#include <time.h>
|
||||
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
#define PLATFORM_WINDOWS 1
|
||||
// #define PLATFORM_WINDOWS 1
|
||||
// #define WIN32_LEAN_AND_MEAN
|
||||
// #define NTDDI_VERSION NTDDI_WIN11
|
||||
//
|
||||
// #pragma comment(lib, "kernel32.Lib")
|
||||
|
||||
#include <aclapi.h>
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <ioringapi.h>
|
||||
#include <ntioring_x.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <windows.h>
|
||||
#include <winerror.h>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma comment(lib, "advapi32.lib")
|
||||
#endif
|
||||
|
||||
#define strdup _strdup
|
||||
#else
|
||||
|
||||
7
compile_commands.json
Normal file
7
compile_commands.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"directory": "D:/Code/c/filehasher",
|
||||
"command": "clang-cl /O2 file_hasher.c xxh_x86dispatch.c",
|
||||
"file": "file_hasher.c"
|
||||
}
|
||||
]
|
||||
@@ -89,12 +89,33 @@ int main(int argc, char **argv) {
|
||||
// -------------------------------
|
||||
// Scanning and hashing
|
||||
// -------------------------------
|
||||
|
||||
// test_io_ring();
|
||||
MPMCQueue dir_queue;
|
||||
mpmc_init(&dir_queue, MiB(1));
|
||||
|
||||
MPMCQueue file_queue;
|
||||
mpmc_init(&file_queue, MiB(1));
|
||||
|
||||
// Starting hash threads
|
||||
// size_t num_hash_threads = num_threads;
|
||||
//
|
||||
// WorkerContext workers[num_hash_threads];
|
||||
// Thread *hash_threads =
|
||||
// arena_push(&gp_arena, sizeof(Thread) * num_hash_threads, true);
|
||||
//
|
||||
// for (size_t i = 0; i < num_hash_threads; ++i) {
|
||||
// workers[i].arena = arena_create(¶ms);
|
||||
// workers[i].file_queue = &file_queue;
|
||||
//
|
||||
// if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker, &workers[i])
|
||||
// !=
|
||||
// 0) {
|
||||
// fprintf(stderr, "Failed to create hash thread %zu\n", i);
|
||||
// exit(1);
|
||||
// }
|
||||
// }
|
||||
|
||||
// Starting hash threads
|
||||
size_t num_hash_threads = num_threads;
|
||||
|
||||
@@ -106,13 +127,46 @@ int main(int argc, char **argv) {
|
||||
workers[i].arena = arena_create(¶ms);
|
||||
workers[i].file_queue = &file_queue;
|
||||
|
||||
if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker, &workers[i]) !=
|
||||
if (thread_create(&hash_threads[i], (ThreadFunc)hash_worker_io_ring, &workers[i])
|
||||
!=
|
||||
0) {
|
||||
fprintf(stderr, "Failed to create hash thread %zu\n", i);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Starting hash threads
|
||||
// size_t num_hash_threads = num_threads;
|
||||
//
|
||||
// WorkerContext workers[num_hash_threads];
|
||||
// Thread *hash_threads =
|
||||
// arena_push(&gp_arena, sizeof(Thread) * num_hash_threads, true);
|
||||
//
|
||||
// // Check if I/O Ring is available
|
||||
// bool io_ring_available = false;
|
||||
// HIORING test_ring = io_ring_init();
|
||||
// if (test_ring) {
|
||||
// io_ring_available = true;
|
||||
// io_ring_cleanup(test_ring);
|
||||
// // printf("I/O Ring is available, using high-performance async I/O\n");
|
||||
// } else {
|
||||
// printf("I/O Ring not available, using buffered I/O\n");
|
||||
// }
|
||||
//
|
||||
// for (size_t i = 0; i < num_hash_threads; ++i) {
|
||||
// workers[i].arena = arena_create(¶ms);
|
||||
// workers[i].file_queue = &file_queue;
|
||||
//
|
||||
// // Select the appropriate worker function
|
||||
// ThreadFunc fn = io_ring_available ? (ThreadFunc)hash_worker_io_ring
|
||||
// : (ThreadFunc)hash_worker;
|
||||
//
|
||||
// if (thread_create(&hash_threads[i], fn, &workers[i]) != 0) {
|
||||
// fprintf(stderr, "Failed to create hash thread %zu\n", i);
|
||||
// exit(1);
|
||||
// }
|
||||
// }
|
||||
|
||||
// Starting progress printing thread
|
||||
Thread progress_thread_handle;
|
||||
if (thread_create(&progress_thread_handle, (ThreadFunc)progress_thread,
|
||||
@@ -209,6 +263,14 @@ int main(int argc, char **argv) {
|
||||
// -------------------------------
|
||||
// Print summary
|
||||
// -------------------------------
|
||||
// DEBUG
|
||||
uint64_t incomplete = atomic_load(&g_io_ring_fallbacks);
|
||||
if (incomplete > 0) {
|
||||
printf(
|
||||
"\nI/O Ring incomplete files: %llu (fallback to buffered I/O used)\n",
|
||||
(unsigned long long)incomplete);
|
||||
}
|
||||
//
|
||||
double total_seconds = timer_elapsed(&total_timer);
|
||||
|
||||
printf("Completed hashing %zu files\n", total_found);
|
||||
|
||||
147
io_ring_test.c
Normal file
147
io_ring_test.c
Normal file
@@ -0,0 +1,147 @@
|
||||
#pragma once
|
||||
|
||||
#include <ioringapi.h>
|
||||
#include <ntioring_x.h>
|
||||
// #include "ioringapi.c"
|
||||
#include <winerror.h>
|
||||
|
||||
// Initialize I/O Ring
|
||||
HIORING io_ring_init(void) {
|
||||
|
||||
// if (!io_ring_load_functions()) {
|
||||
// printf("[I/O Ring] Failed to load functions\n");
|
||||
// return NULL;
|
||||
// }
|
||||
|
||||
IORING_CAPABILITIES caps;
|
||||
ZeroMemory(&caps, sizeof(caps));
|
||||
|
||||
HRESULT hr = QueryIoRingCapabilities(&caps);
|
||||
if (FAILED(hr)) {
|
||||
printf("[I/O Ring] QueryIoRingCapabilities failed: 0x%08lx\n", hr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// printf("[I/O Ring] MaxVersion=%d, MaxSubmission=%u, MaxCompletion=%u\n",
|
||||
// (int)caps.MaxVersion, caps.MaxSubmissionQueueSize,
|
||||
// caps.MaxCompletionQueueSize);
|
||||
|
||||
if (caps.MaxVersion < IORING_VERSION_1) {
|
||||
printf("[I/O Ring] Version too old\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
IORING_CREATE_FLAGS flags = {0};
|
||||
HIORING ring = NULL;
|
||||
|
||||
// hr = CreateIoRing(IORING_VERSION_1, flags, 256, 512, &ring);
|
||||
hr = CreateIoRing(caps.MaxVersion, flags, 256, 512, &ring);
|
||||
if (FAILED(hr)) {
|
||||
printf("[I/O Ring] CreateIoRing failed: 0x%08lx\n", hr);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// printf("[I/O Ring] Created successfully\n");
|
||||
|
||||
// Check if read operation is supported
|
||||
|
||||
// HRESULT io_ring_support = IsIoRingOpSupported(ring, IORING_OP_READ);
|
||||
// if (io_ring_support == S_FALSE) {
|
||||
// printf("[I/O Ring] Not supported, %ld /n", io_ring_support);
|
||||
// }
|
||||
|
||||
// Get ring info
|
||||
IORING_INFO info;
|
||||
ZeroMemory(&info, sizeof(info));
|
||||
GetIoRingInfo(ring, &info);
|
||||
// printf("[I/O Ring] Submission: %u, Completion: %u\n",
|
||||
// info.SubmissionQueueSize, info.CompletionQueueSize);
|
||||
|
||||
return ring;
|
||||
}
|
||||
|
||||
void io_ring_cleanup(HIORING ring) {
|
||||
if (ring) {
|
||||
CloseIoRing(ring);
|
||||
// printf("[I/O Ring] Closed\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Read file using I/O Ring
|
||||
int io_ring_read_file(HIORING ring, HANDLE hFile, void *buffer, DWORD size,
|
||||
UINT64 offset) {
|
||||
|
||||
IORING_HANDLE_REF file_ref = IoRingHandleRefFromHandle(hFile);
|
||||
IORING_BUFFER_REF buf_ref = IoRingBufferRefFromPointer(buffer);
|
||||
|
||||
HRESULT hr = BuildIoRingReadFile(ring, file_ref, buf_ref, size, offset,
|
||||
(UINT_PTR)buffer, IOSQE_FLAGS_NONE);
|
||||
|
||||
if (FAILED(hr))
|
||||
return -1;
|
||||
|
||||
UINT32 submitted = 0;
|
||||
hr = SubmitIoRing(ring, 1, INFINITE, &submitted);
|
||||
if (FAILED(hr) || submitted == 0)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
IORING_CQE cqe;
|
||||
hr = PopIoRingCompletion(ring, &cqe);
|
||||
|
||||
if (FAILED(hr))
|
||||
continue;
|
||||
|
||||
if (cqe.UserData != (UINT_PTR)buffer)
|
||||
continue;
|
||||
|
||||
if (FAILED(cqe.ResultCode))
|
||||
return -1;
|
||||
|
||||
return (int)cqe.Information;
|
||||
}
|
||||
}
|
||||
|
||||
// Test function
|
||||
void test_io_ring(void) {
|
||||
printf("\n=== Testing I/O Ring ===\n");
|
||||
|
||||
HIORING ring = io_ring_init();
|
||||
if (!ring) {
|
||||
printf("I/O Ring not available\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create test file
|
||||
HANDLE hFile = CreateFileA("test.txt", GENERIC_READ | GENERIC_WRITE, 0, NULL,
|
||||
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile != INVALID_HANDLE_VALUE) {
|
||||
char test_data[] =
|
||||
"Hello, I/O Ring! This is a test of the Windows I/O Ring API.";
|
||||
DWORD written;
|
||||
WriteFile(hFile, test_data, sizeof(test_data), &written, NULL);
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
|
||||
// Read using I/O Ring
|
||||
hFile = CreateFileA("test.txt", GENERIC_READ, FILE_SHARE_READ, NULL,
|
||||
OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
|
||||
if (hFile != INVALID_HANDLE_VALUE) {
|
||||
char buffer[512] = {0};
|
||||
int bytes = io_ring_read_file(ring, hFile, buffer, sizeof(buffer), 0);
|
||||
if (bytes > 0) {
|
||||
printf("Read %d bytes: %s\n", bytes, buffer);
|
||||
} else {
|
||||
printf("Failed to read file\n");
|
||||
}
|
||||
CloseHandle(hFile);
|
||||
} else {
|
||||
printf("Failed to open test file\n");
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
DeleteFileA("test.txt");
|
||||
io_ring_cleanup(ring);
|
||||
|
||||
printf("=== Test complete ===\n\n");
|
||||
}
|
||||
285
ioringapi.c
Normal file
285
ioringapi.c
Normal file
@@ -0,0 +1,285 @@
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
#include <winnt.h>
|
||||
|
||||
// Forward declarations
|
||||
typedef struct IORING_HANDLE_REF IORING_HANDLE_REF;
|
||||
typedef struct IORING_BUFFER_REF IORING_BUFFER_REF;
|
||||
typedef void *HIORING;
|
||||
|
||||
/* --------------------- Types declaration --------------------- */
|
||||
typedef enum IORING_CREATE_ADVISORY_FLAGS {
|
||||
IORING_CREATE_ADVISORY_FLAGS_NONE,
|
||||
IORING_CREATE_SKIP_BUILDER_PARAM_CHECKS
|
||||
} IORING_CREATE_ADVISORY_FLAGS;
|
||||
// Specifies advisory flags for creating an I/O ring with a call to
|
||||
// CreateIoRing.
|
||||
|
||||
typedef enum IORING_CREATE_REQUIRED_FLAGS {
|
||||
IORING_CREATE_REQUIRED_FLAGS_NONE
|
||||
} IORING_CREATE_REQUIRED_FLAGS;
|
||||
// Specifies required flags for creating an I/O ring with a call to
|
||||
// CreateIoRing.
|
||||
|
||||
typedef enum IORING_REF_KIND {
|
||||
IORING_REF_RAW = 0,
|
||||
IORING_REF_REGISTERED = 1,
|
||||
} IORING_REF_KIND;
|
||||
// Specifies the type of an IORING_HANDLE_REF structure.
|
||||
|
||||
typedef enum IORING_SQE_FLAGS {
|
||||
IOSQE_FLAGS_NONE,
|
||||
IOSQE_FLAGS_DRAIN_PRECEDING_OPS
|
||||
} IORING_SQE_FLAGS;
|
||||
// Specifies kernel behavior options for I/O ring submission queue entries
|
||||
|
||||
// IORING_REGISTERED_BUFFER structure
|
||||
typedef struct IORING_REGISTERED_BUFFER {
|
||||
UINT32 Index;
|
||||
UINT32 Offset;
|
||||
} IORING_REGISTERED_BUFFER;
|
||||
|
||||
// IORING_HANDLE_REF
|
||||
struct IORING_HANDLE_REF {
|
||||
IORING_REF_KIND Kind;
|
||||
union {
|
||||
HANDLE Handle;
|
||||
UINT32 Index;
|
||||
} HandleUnion;
|
||||
};
|
||||
// Represents a reference to a file handle used in an I/O ring operation
|
||||
|
||||
// IORING_BUFFER_REF
|
||||
struct IORING_BUFFER_REF {
|
||||
IORING_REF_KIND Kind;
|
||||
union {
|
||||
void *Address;
|
||||
IORING_REGISTERED_BUFFER IndexAndOffset;
|
||||
} BufferUnion;
|
||||
};
|
||||
|
||||
typedef struct IORING_BUFFER_INFO {
|
||||
void *Address;
|
||||
UINT32 Length;
|
||||
} IORING_BUFFER_INFO;
|
||||
|
||||
// IORING_BUFFER_REF represents a reference to a buffer used in an I/O ring
|
||||
// operation
|
||||
|
||||
// IORING_VERSION enumeration
|
||||
typedef enum IORING_VERSION {
|
||||
IORING_VERSION_INVALID = 0,
|
||||
IORING_VERSION_1 = 1,
|
||||
IORING_VERSION_2 = 2,
|
||||
IORING_VERSION_3 = 3,
|
||||
IORING_VERSION_4 = 4,
|
||||
} IORING_VERSION;
|
||||
|
||||
typedef enum IORING_FEATURE_FLAGS {
|
||||
IORING_FEATURE_FLAGS_NONE = 0,
|
||||
IORING_FEATURE_UM_EMULATION = 1
|
||||
} IORING_FEATURE_FLAGS;
|
||||
|
||||
// IORING_CAPABILITIES structure
|
||||
typedef struct IORING_CAPABILITIES {
|
||||
IORING_VERSION MaxVersion;
|
||||
UINT32 MaxSubmissionQueueSize;
|
||||
UINT32 MaxCompletionQueueSize;
|
||||
IORING_FEATURE_FLAGS FeatureFlags;
|
||||
} IORING_CAPABILITIES;
|
||||
// Represents the IORING API capabilities.
|
||||
|
||||
// IORING_CQE structure
|
||||
typedef struct IORING_CQE {
|
||||
UINT_PTR UserData;
|
||||
HRESULT ResultCode;
|
||||
ULONG_PTR Information;
|
||||
} IORING_CQE;
|
||||
// Represents a completed I/O ring queue entry.
|
||||
|
||||
// IORING_CREATE_FLAGS structure
|
||||
typedef struct IORING_CREATE_FLAGS {
|
||||
IORING_CREATE_REQUIRED_FLAGS Required;
|
||||
IORING_CREATE_ADVISORY_FLAGS Advisory;
|
||||
} IORING_CREATE_FLAGS;
|
||||
// Specifies flags for creating an I/O ring with a call to CreateIoRing.
|
||||
|
||||
// IORING_INFO structure
|
||||
typedef struct IORING_INFO {
|
||||
IORING_VERSION IoRingVersion;
|
||||
IORING_CREATE_FLAGS Flags;
|
||||
UINT32 SubmissionQueueSize;
|
||||
UINT32 CompletionQueueSize;
|
||||
} IORING_INFO;
|
||||
// Represents the shape and version information for the specified I/O ring
|
||||
|
||||
// IORING_OP_CODE for IsIoRingOpSupported
|
||||
typedef enum IORING_OP_CODE {
|
||||
IORING_OP_NOP = 0,
|
||||
IORING_OP_READ = 1,
|
||||
IORING_OP_WRITE = 2,
|
||||
IORING_OP_FLUSH = 3,
|
||||
IORING_OP_REGISTER_BUFFERS = 4,
|
||||
IORING_OP_REGISTER_FILES = 5,
|
||||
IORING_OP_CANCEL = 6,
|
||||
} IORING_OP_CODE;
|
||||
|
||||
/* --------------------- Dynamic loader --------------------- */
|
||||
// Function pointer types
|
||||
typedef BOOL(WINAPI *IsIoRingOpSupported_t)(HIORING, IORING_OP_CODE);
|
||||
typedef HRESULT(WINAPI *QueryIoRingCapabilities_t)(IORING_CAPABILITIES *);
|
||||
typedef HRESULT(WINAPI *GetIoRingInfo_t)(HIORING, IORING_INFO *);
|
||||
typedef HRESULT(WINAPI *CreateIoRing_t)(IORING_VERSION, IORING_CREATE_FLAGS,
|
||||
UINT32, UINT32, HIORING *);
|
||||
typedef HRESULT(WINAPI *CloseIoRing_t)(HIORING);
|
||||
typedef HRESULT(WINAPI *SubmitIoRing_t)(HIORING, UINT32, UINT32, UINT32 *);
|
||||
typedef HRESULT(WINAPI *PopIoRingCompletion_t)(HIORING, IORING_CQE *);
|
||||
typedef HRESULT(WINAPI *SetIoRingCompletionEvent_t)(HIORING, HANDLE);
|
||||
typedef HRESULT(WINAPI *BuildIoRingCancelRequest_t)(HIORING, IORING_HANDLE_REF,
|
||||
UINT_PTR, UINT_PTR);
|
||||
typedef HRESULT(WINAPI *BuildIoRingReadFile_t)(HIORING, IORING_HANDLE_REF,
|
||||
IORING_BUFFER_REF, UINT32,
|
||||
UINT64, UINT_PTR,
|
||||
IORING_SQE_FLAGS);
|
||||
typedef HRESULT(WINAPI *BuildIoRingRegisterBuffers_t)(
|
||||
HIORING, UINT32, IORING_BUFFER_INFO const[], UINT_PTR);
|
||||
|
||||
typedef HRESULT(WINAPI *BuildIoRingRegisterFileHandles_t)(HIORING, UINT32,
|
||||
HANDLE const[],
|
||||
UINT_PTR);
|
||||
|
||||
// Core:
|
||||
// Queries the support of the specified operation for the specified I/O ring
|
||||
static IsIoRingOpSupported_t IsIoRingOpSupported = NULL;
|
||||
|
||||
// Queries the OS for the supported capabilities for IORINGs
|
||||
static QueryIoRingCapabilities_t QueryIoRingCapabilities = NULL;
|
||||
|
||||
// Gets information about the API version and queue sizes of an I/O ring
|
||||
static GetIoRingInfo_t GetIoRingInfo = NULL;
|
||||
|
||||
// Creates a new instance of an I/O ring submission/completion queue pair and
|
||||
// returns a handle for referencing the I/O ring
|
||||
static CreateIoRing_t CreateIoRing = NULL;
|
||||
|
||||
// Closes an HIORING handle that was previously opened with a call to
|
||||
// CreateIoRing
|
||||
static CloseIoRing_t CloseIoRing = NULL;
|
||||
|
||||
// Submission / completion:
|
||||
// Submits all constructed but not yet submitted entries to the kernel’s queue
|
||||
// and optionally waits for a set of operations to complete
|
||||
static SubmitIoRing_t SubmitIoRing = NULL;
|
||||
|
||||
// Pops a single entry from the completion queue, if one is available
|
||||
static PopIoRingCompletion_t PopIoRingCompletion = NULL;
|
||||
|
||||
// Registers a completion queue event with an IORING
|
||||
static SetIoRingCompletionEvent_t SetIoRingCompletionEvent = NULL;
|
||||
|
||||
// Operations:
|
||||
// Performs an asynchronous read from a file using an I/O ring
|
||||
static BuildIoRingReadFile_t BuildIoRingReadFile = NULL;
|
||||
|
||||
// Attempts to cancel a previously submitted I/O ring operation
|
||||
static BuildIoRingCancelRequest_t BuildIoRingCancelRequest = NULL;
|
||||
|
||||
// Registers an array of buffers with the system for future I/O ring operations
|
||||
static BuildIoRingRegisterBuffers_t BuildIoRingRegisterBuffers = NULL;
|
||||
|
||||
// Registers an array of file handles with the system for future I/O ring
|
||||
// operations
|
||||
static BuildIoRingRegisterFileHandles_t BuildIoRingRegisterFileHandles = NULL;
|
||||
|
||||
static int io_ring_loaded = 0;
|
||||
|
||||
static int io_ring_load_functions(void) {
|
||||
if (io_ring_loaded)
|
||||
return 1;
|
||||
|
||||
HMODULE hKernel = GetModuleHandleW(L"kernel32.dll");
|
||||
if (!hKernel)
|
||||
return 0;
|
||||
|
||||
IsIoRingOpSupported =
|
||||
(IsIoRingOpSupported_t)GetProcAddress(hKernel, "IsIoRingOpSupported");
|
||||
QueryIoRingCapabilities = (QueryIoRingCapabilities_t)GetProcAddress(
|
||||
hKernel, "QueryIoRingCapabilities");
|
||||
GetIoRingInfo = (GetIoRingInfo_t)GetProcAddress(hKernel, "GetIoRingInfo");
|
||||
CreateIoRing = (CreateIoRing_t)GetProcAddress(hKernel, "CreateIoRing");
|
||||
CloseIoRing = (CloseIoRing_t)GetProcAddress(hKernel, "CloseIoRing");
|
||||
SubmitIoRing = (SubmitIoRing_t)GetProcAddress(hKernel, "SubmitIoRing");
|
||||
PopIoRingCompletion =
|
||||
(PopIoRingCompletion_t)GetProcAddress(hKernel, "PopIoRingCompletion");
|
||||
SetIoRingCompletionEvent = (SetIoRingCompletionEvent_t)GetProcAddress(
|
||||
hKernel, "SetIoRingCompletionEvent");
|
||||
BuildIoRingReadFile =
|
||||
(BuildIoRingReadFile_t)GetProcAddress(hKernel, "BuildIoRingReadFile");
|
||||
BuildIoRingCancelRequest = (BuildIoRingCancelRequest_t)GetProcAddress(
|
||||
hKernel, "BuildIoRingCancelRequest");
|
||||
BuildIoRingRegisterBuffers = (BuildIoRingRegisterBuffers_t)GetProcAddress(
|
||||
hKernel, "BuildIoRingRegisterBuffers");
|
||||
BuildIoRingRegisterFileHandles =
|
||||
(BuildIoRingRegisterFileHandles_t)GetProcAddress(
|
||||
hKernel, "BuildIoRingRegisterFileHandles");
|
||||
|
||||
io_ring_loaded =
|
||||
(IsIoRingOpSupported && QueryIoRingCapabilities && CreateIoRing &&
|
||||
CloseIoRing && SubmitIoRing && PopIoRingCompletion &&
|
||||
SetIoRingCompletionEvent && BuildIoRingReadFile &&
|
||||
BuildIoRingCancelRequest && BuildIoRingRegisterBuffers &&
|
||||
BuildIoRingRegisterFileHandles);
|
||||
|
||||
if (io_ring_loaded)
|
||||
printf("[I/O Ring] Functions loaded\n");
|
||||
else
|
||||
printf("[I/O Ring] Some functions not available\n");
|
||||
|
||||
return io_ring_loaded;
|
||||
}
|
||||
|
||||
/* ------------- Standard helper functions definition ------------- */
|
||||
// Creates an instance of the IORING_BUFFER_REF structure with the provided
|
||||
// buffer index and offset
|
||||
static inline IORING_BUFFER_REF
|
||||
IoRingBufferRefFromIndexAndOffset(UINT32 index, UINT32 offset) {
|
||||
IORING_BUFFER_REF ref;
|
||||
ref.Kind = IORING_REF_REGISTERED;
|
||||
ref.BufferUnion.IndexAndOffset.Index = index;
|
||||
ref.BufferUnion.IndexAndOffset.Offset = offset;
|
||||
return ref;
|
||||
}
|
||||
|
||||
// Creates an instance of the IORING_BUFFER_REF structure from the provided
|
||||
// pointer
|
||||
static IORING_BUFFER_REF IoRingBufferRefFromPointer(void *addr) {
|
||||
IORING_BUFFER_REF ref;
|
||||
ref.Kind = IORING_REF_RAW;
|
||||
ref.BufferUnion.Address = addr;
|
||||
return ref;
|
||||
}
|
||||
|
||||
// Creates an instance of the IORING_HANDLE_REF structure from the provided file
|
||||
// handle
|
||||
static IORING_HANDLE_REF IoRingHandleRefFromHandle(HANDLE h) {
|
||||
IORING_HANDLE_REF ref;
|
||||
ref.Kind = IORING_REF_RAW;
|
||||
ref.HandleUnion.Handle = h;
|
||||
return ref;
|
||||
}
|
||||
|
||||
// Creates an instance of the IORING_HANDLE_REF structure from the provided
|
||||
// index
|
||||
static inline IORING_HANDLE_REF IoRingHandleRefFromIndex(UINT32 index) {
|
||||
IORING_HANDLE_REF ref;
|
||||
ref.Kind = IORING_REF_REGISTERED; // MUST be registered
|
||||
ref.HandleUnion.Index = index;
|
||||
return ref;
|
||||
}
|
||||
|
||||
// NOTE: If you are using index-based buffers or handles, make sure you have
|
||||
// successfully called BuildIoRingRegisterBuffers or
|
||||
// BuildIoRingRegisterFileHandles first so the kernel has a valid table to look
|
||||
// into, otherwise the kernel will treat the index as an invalid memory
|
||||
// address/handle.
|
||||
694
platform.c
694
platform.c
@@ -1,7 +1,5 @@
|
||||
#pragma once // ensure that a given header file is included only once in a
|
||||
// single compilation unit
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
|
||||
#include "arena.h"
|
||||
#include "base.h"
|
||||
#include "lf_mpmc.h"
|
||||
@@ -9,7 +7,7 @@
|
||||
#include "arena.c"
|
||||
|
||||
// xxhash include
|
||||
#define XXH_INLINE_ALL
|
||||
#define XXH_STATIC_LINKING_ONLY
|
||||
#include "xxh_x86dispatch.h"
|
||||
|
||||
// ----------------------------- Config -------------------------------------
|
||||
@@ -584,8 +582,6 @@ void scan_folder(const char *base, ScannerContext *ctx) {
|
||||
}
|
||||
|
||||
#elif defined(__linux__)
|
||||
To test
|
||||
Choice 1
|
||||
static int platform_get_file_times_fd(int dir_fd, const char *name,
|
||||
time_t *created, time_t *modified) {
|
||||
struct stat st;
|
||||
@@ -604,9 +600,9 @@ static int platform_get_file_owner_fd(int dir_fd, const char *name, char *owner,
|
||||
struct passwd pw;
|
||||
struct passwd *result;
|
||||
char buffer[4096]; // Sufficiently large buffer for passwd data
|
||||
|
||||
|
||||
// Reentrant version (thread-safe)
|
||||
if (getpwuid_r(st.st_uid, &pw, buffer, sizeof(buffer), &result) == 0 &&
|
||||
if (getpwuid_r(st.st_uid, &pw, buffer, sizeof(buffer), &result) == 0 &&
|
||||
result != NULL && result->pw_name != NULL) {
|
||||
strncpy(owner, result->pw_name, owner_size - 1);
|
||||
owner[owner_size - 1] = '\0';
|
||||
@@ -618,170 +614,111 @@ static int platform_get_file_owner_fd(int dir_fd, const char *name, char *owner,
|
||||
}
|
||||
return -1;
|
||||
|
||||
void scan_folder(const char *base, ScannerContext *ctx) {
|
||||
PathBuilder pb;
|
||||
path_builder_init(&pb, base);
|
||||
|
||||
void scan_folder(const char *base, ScannerContext *ctx) {
|
||||
PathBuilder pb;
|
||||
path_builder_init(&pb, base);
|
||||
int dir_fd = open(base, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
|
||||
if (dir_fd == -1)
|
||||
return;
|
||||
|
||||
int dir_fd = open(base, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
|
||||
if (dir_fd == -1)
|
||||
return;
|
||||
DIR *dir = fdopendir(dir_fd);
|
||||
if (!dir) {
|
||||
close(dir_fd);
|
||||
return;
|
||||
}
|
||||
|
||||
DIR *dir = fdopendir(dir_fd);
|
||||
if (!dir) {
|
||||
close(dir_fd);
|
||||
return;
|
||||
}
|
||||
struct dirent *entry;
|
||||
|
||||
struct dirent *entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (entry->d_name[0] == '.' &&
|
||||
(entry->d_name[1] == 0 ||
|
||||
(entry->d_name[1] == '.' && entry->d_name[2] == 0)))
|
||||
continue;
|
||||
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (entry->d_name[0] == '.' &&
|
||||
(entry->d_name[1] == 0 ||
|
||||
(entry->d_name[1] == '.' && entry->d_name[2] == 0)))
|
||||
continue;
|
||||
size_t name_len = strlen(entry->d_name);
|
||||
path_builder_set_filename(&pb, entry->d_name, name_len);
|
||||
|
||||
size_t name_len = strlen(entry->d_name);
|
||||
path_builder_set_filename(&pb, entry->d_name, name_len);
|
||||
|
||||
int file_type = DT_UNKNOWN;
|
||||
int file_type = DT_UNKNOWN;
|
||||
#ifdef _DIRENT_HAVE_D_TYPE
|
||||
file_type = entry->d_type;
|
||||
file_type = entry->d_type;
|
||||
#endif
|
||||
|
||||
// Fast path using d_type
|
||||
if (file_type != DT_UNKNOWN) {
|
||||
if (file_type == DT_LNK)
|
||||
continue; // Skip symlinks
|
||||
// Fast path using d_type
|
||||
if (file_type != DT_UNKNOWN) {
|
||||
if (file_type == DT_LNK)
|
||||
continue; // Skip symlinks
|
||||
|
||||
if (file_type == DT_DIR) {
|
||||
char *dir_path = path_builder_dup_arena(&pb, ctx->path_arena, false);
|
||||
mpmc_push_work(ctx->dir_queue, dir_path);
|
||||
continue;
|
||||
if (file_type == DT_DIR) {
|
||||
char *dir_path = path_builder_dup_arena(&pb, ctx->path_arena, false);
|
||||
mpmc_push_work(ctx->dir_queue, dir_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (file_type == DT_REG) {
|
||||
atomic_fetch_add(&g_files_found, 1);
|
||||
FileEntry *fe = arena_push(&ctx->meta_arena, sizeof(FileEntry), true);
|
||||
|
||||
// Use fstatat for file info
|
||||
struct stat st;
|
||||
if (fstatat(dir_fd, entry->d_name, &st, 0) == 0) {
|
||||
// Convert times using fd variant
|
||||
platform_get_file_times_fd(dir_fd, entry->d_name, &fe->created_time,
|
||||
&fe->modified_time);
|
||||
platform_get_file_owner_fd(dir_fd, entry->d_name, fe->owner,
|
||||
sizeof(fe->owner));
|
||||
fe->size_bytes = (uint64_t)st.st_size;
|
||||
|
||||
// Normalize path
|
||||
char temp_path[MAX_PATHLEN];
|
||||
memcpy(temp_path, pb.buffer,
|
||||
(pb.filename_pos - pb.buffer) + name_len + 1);
|
||||
normalize_path(temp_path);
|
||||
|
||||
fe->path =
|
||||
arena_push(&ctx->path_arena, strlen(temp_path) + 1, false);
|
||||
strcpy(fe->path, temp_path);
|
||||
|
||||
mpmc_push(ctx->file_queue, fe);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (file_type == DT_REG) {
|
||||
atomic_fetch_add(&g_files_found, 1);
|
||||
FileEntry *fe = arena_push(&ctx->meta_arena, sizeof(FileEntry),
|
||||
true);
|
||||
// Fallback for unknown types
|
||||
struct stat st;
|
||||
if (fstatat(dir_fd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW) == 0) {
|
||||
if (S_ISLNK(st.st_mode))
|
||||
continue;
|
||||
|
||||
// Use fstatat for file info
|
||||
struct stat st;
|
||||
if (fstatat(dir_fd, entry->d_name, &st, 0) == 0) {
|
||||
// Convert times using fd variant
|
||||
platform_get_file_times_fd(dir_fd, entry->d_name,
|
||||
&fe->created_time,
|
||||
&fe->modified_time);
|
||||
platform_get_file_owner_fd(dir_fd, entry->d_name, fe->owner,
|
||||
sizeof(fe->owner));
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
char *dir_path = path_builder_dup_arena(&pb, ctx->path_arena, false);
|
||||
mpmc_push_work(ctx->dir_queue, dir_path);
|
||||
} else if (S_ISREG(st.st_mode)) {
|
||||
atomic_fetch_add(&g_files_found, 1);
|
||||
FileEntry *fe = arena_push(&ctx->meta_arena, sizeof(FileEntry), true);
|
||||
|
||||
platform_get_file_times(pb.buffer, &fe->created_time,
|
||||
&fe->modified_time);
|
||||
platform_get_file_owner(pb.buffer, fe->owner, sizeof(fe->owner));
|
||||
fe->size_bytes = (uint64_t)st.st_size;
|
||||
|
||||
// Normalize path
|
||||
char temp_path[MAX_PATHLEN];
|
||||
memcpy(temp_path, pb.buffer,
|
||||
(pb.filename_pos - pb.buffer) + name_len + 1);
|
||||
normalize_path(temp_path);
|
||||
|
||||
fe->path = arena_push(&ctx->path_arena, strlen(temp_path) + 1,
|
||||
false); strcpy(fe->path, temp_path);
|
||||
fe->path = arena_push(&ctx->path_arena, strlen(temp_path) + 1, false);
|
||||
strcpy(fe->path, temp_path);
|
||||
|
||||
mpmc_push(ctx->file_queue, fe);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for unknown types
|
||||
struct stat st;
|
||||
if (fstatat(dir_fd, entry->d_name, &st, AT_SYMLINK_NOFOLLOW) == 0) {
|
||||
if (S_ISLNK(st.st_mode))
|
||||
continue;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
char *dir_path = path_builder_dup_arena(&pb, ctx->path_arena, false);
|
||||
mpmc_push_work(ctx->dir_queue, dir_path);
|
||||
} else if (S_ISREG(st.st_mode)) {
|
||||
atomic_fetch_add(&g_files_found, 1);
|
||||
FileEntry *fe = arena_push(&ctx->meta_arena, sizeof(FileEntry),
|
||||
true);
|
||||
|
||||
platform_get_file_times(pb.buffer, &fe->created_time,
|
||||
&fe->modified_time);
|
||||
platform_get_file_owner(pb.buffer, fe->owner, sizeof(fe->owner));
|
||||
fe->size_bytes = (uint64_t)st.st_size;
|
||||
|
||||
char temp_path[MAX_PATHLEN];
|
||||
memcpy(temp_path, pb.buffer,
|
||||
(pb.filename_pos - pb.buffer) + name_len + 1);
|
||||
normalize_path(temp_path);
|
||||
|
||||
fe->path = arena_push(&ctx->path_arena, strlen(temp_path) + 1,
|
||||
false); strcpy(fe->path, temp_path);
|
||||
|
||||
mpmc_push(ctx->file_queue, fe);
|
||||
}
|
||||
}
|
||||
closedir(dir); // Closes dir_fd automatically
|
||||
}
|
||||
|
||||
closedir(dir); // Closes dir_fd automatically
|
||||
}
|
||||
|
||||
// Choice 2
|
||||
|
||||
// void scan_folder(const char *base, ScannerContext *ctx) {
|
||||
// PathBuilder pb;
|
||||
// path_builder_init(&pb, base);
|
||||
//
|
||||
// DIR *dir = opendir(base);
|
||||
// if (!dir)
|
||||
// return;
|
||||
//
|
||||
// struct dirent *entry;
|
||||
// struct stat st;
|
||||
//
|
||||
// while ((entry = readdir(dir)) != NULL) {
|
||||
// if (entry->d_name[0] == '.' &&
|
||||
// (entry->d_name[1] == 0 ||
|
||||
// (entry->d_name[1] == '.' && entry->d_name[2] == 0)))
|
||||
// continue;
|
||||
//
|
||||
// size_t name_len = strlen(entry->d_name);
|
||||
// path_builder_set_filename(&pb, entry->d_name, name_len);
|
||||
//
|
||||
// if (lstat(pb.buffer, &st) == 0 && S_ISLNK(st.st_mode))
|
||||
// continue;
|
||||
//
|
||||
// if (stat(pb.buffer, &st) == 0) {
|
||||
// if (S_ISDIR(st.st_mode)) {
|
||||
// char *dir_path = path_builder_dup_arena(&pb, ctx->path_arena, false);
|
||||
// mpmc_push_work(ctx->dir_queue, dir_path);
|
||||
// } else {
|
||||
// atomic_fetch_add(&g_files_found, 1);
|
||||
//
|
||||
// FileEntry *fe = arena_push(&ctx->meta_arena, sizeof(FileEntry), true);
|
||||
//
|
||||
// // Create a temporary copy for normalization
|
||||
// char temp_path[MAX_PATHLEN];
|
||||
// memcpy(temp_path, pb.buffer,
|
||||
// (pb.filename_pos - pb.buffer) + name_len + 1);
|
||||
// normalize_path(temp_path);
|
||||
//
|
||||
// fe->path = arena_push(&ctx->path_arena, strlen(temp_path) + 1, false);
|
||||
// strcpy(fe->path, temp_path);
|
||||
//
|
||||
// platform_get_file_times(pb.buffer, &fe->created_time,
|
||||
// &fe->modified_time);
|
||||
// platform_get_file_owner(pb.buffer, fe->owner, sizeof(fe->owner));
|
||||
// fe->size_bytes = (uint64_t)st.st_size;
|
||||
//
|
||||
// mpmc_push(ctx->file_queue, fe);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// closedir(dir);
|
||||
// }
|
||||
|
||||
#endif
|
||||
|
||||
// ------------------------- Scan worker --------------------------------
|
||||
@@ -864,7 +801,7 @@ static THREAD_RETURN hash_worker(void *arg) {
|
||||
return THREAD_RETURN_VALUE;
|
||||
}
|
||||
|
||||
// ----------------------------- Progress display ---------------------------
|
||||
// ------------------------- Progress display ---------------------------
|
||||
static THREAD_RETURN progress_thread(void *arg) {
|
||||
(void)arg; // Unused parameter
|
||||
|
||||
@@ -940,3 +877,472 @@ static THREAD_RETURN progress_thread(void *arg) {
|
||||
|
||||
return THREAD_RETURN_VALUE;
|
||||
}
|
||||
|
||||
// ======================== Hash worker IO Ring ========================
|
||||
// -------------------------- Configuration ---------------------------
|
||||
#define IORING_READ_BLOCK (4096 * 64)
|
||||
#define NUM_BUFFERS_PER_THREAD 16
|
||||
#define SUBMIT_TIMEOUT_MS 30000
|
||||
#define USERDATA_REGISTER 1
|
||||
|
||||
// Global stats
|
||||
static atomic_uint_fast64_t g_io_ring_fallbacks = 0;
|
||||
|
||||
// -------------------------- Buffer structure ---------------------------
|
||||
typedef struct IoBuffer {
|
||||
void *data;
|
||||
uint64_t offset;
|
||||
size_t size;
|
||||
size_t bytes_read;
|
||||
HRESULT result;
|
||||
int buffer_id;
|
||||
int completed;
|
||||
} IoBuffer;
|
||||
|
||||
// ============================================================================
|
||||
// Thread-local I/O Ring context
|
||||
// ============================================================================
|
||||
typedef struct ThreadIoContext {
|
||||
HIORING ring;
|
||||
HANDLE completion_event;
|
||||
IoBuffer buffers[NUM_BUFFERS_PER_THREAD];
|
||||
int buffer_pool[NUM_BUFFERS_PER_THREAD];
|
||||
int free_count;
|
||||
int initialized;
|
||||
} ThreadIoContext;
|
||||
|
||||
// -------------------------- File context ---------------------------
|
||||
typedef struct FileReadContext {
|
||||
HANDLE hFile;
|
||||
uint64_t file_size;
|
||||
XXH3_state_t hash_state;
|
||||
char path[MAX_PATH];
|
||||
|
||||
// Completion tracking
|
||||
int reads_submitted;
|
||||
int reads_completed;
|
||||
int reads_hashed;
|
||||
uint64_t bytes_hashed;
|
||||
int failed_reads;
|
||||
int active_reads;
|
||||
int buffers_ready; // Count of buffers ready to hash
|
||||
|
||||
// For in-order hashing
|
||||
uint64_t next_hash_offset; // Next offset that needs to be hashed
|
||||
uint64_t next_read_offset; // Next offset to submit for reading
|
||||
|
||||
} FileReadContext;
|
||||
|
||||
static _Thread_local ThreadIoContext *g_thread_ctx = NULL;
|
||||
|
||||
// ---------------------- Initialize thread context ---------------------------
|
||||
static ThreadIoContext *io_ring_init_thread(void) {
|
||||
if (g_thread_ctx && g_thread_ctx->initialized) {
|
||||
return g_thread_ctx;
|
||||
}
|
||||
|
||||
if (!g_thread_ctx) {
|
||||
g_thread_ctx = (ThreadIoContext *)calloc(1, sizeof(ThreadIoContext));
|
||||
if (!g_thread_ctx)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create I/O Ring
|
||||
IORING_CAPABILITIES caps;
|
||||
QueryIoRingCapabilities(&caps);
|
||||
|
||||
UINT32 queue_size = min(4096, caps.MaxSubmissionQueueSize);
|
||||
IORING_CREATE_FLAGS flags = {0};
|
||||
HRESULT hr = CreateIoRing(caps.MaxVersion, flags, queue_size, queue_size * 2,
|
||||
&g_thread_ctx->ring);
|
||||
|
||||
// Create completion event
|
||||
g_thread_ctx->completion_event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
if (g_thread_ctx->completion_event) {
|
||||
SetIoRingCompletionEvent(g_thread_ctx->ring,
|
||||
g_thread_ctx->completion_event);
|
||||
}
|
||||
|
||||
// Initialize buffer pool
|
||||
for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) {
|
||||
|
||||
// 4096 alignment
|
||||
void *ptr = _aligned_malloc(IORING_READ_BLOCK, 4096);
|
||||
if (!ptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g_thread_ctx->buffers[i].data = ptr;
|
||||
|
||||
g_thread_ctx->buffer_pool[i] = i;
|
||||
g_thread_ctx->buffers[i].buffer_id = i;
|
||||
}
|
||||
g_thread_ctx->free_count = NUM_BUFFERS_PER_THREAD;
|
||||
|
||||
IORING_BUFFER_INFO buf_info[NUM_BUFFERS_PER_THREAD];
|
||||
|
||||
for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) {
|
||||
buf_info[i].Address = g_thread_ctx->buffers[i].data;
|
||||
buf_info[i].Length = IORING_READ_BLOCK;
|
||||
}
|
||||
|
||||
HRESULT hb = BuildIoRingRegisterBuffers(
|
||||
g_thread_ctx->ring, NUM_BUFFERS_PER_THREAD, buf_info, USERDATA_REGISTER);
|
||||
|
||||
if (FAILED(hb)) {
|
||||
printf("Buffer registration failed: 0x%lx\n", hb);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Submit registration
|
||||
SubmitIoRing(g_thread_ctx->ring, 0, 0, NULL);
|
||||
|
||||
g_thread_ctx->initialized = 1;
|
||||
|
||||
return g_thread_ctx;
|
||||
}
|
||||
|
||||
static void io_ring_cleanup_thread(void) {
|
||||
if (!g_thread_ctx)
|
||||
return;
|
||||
|
||||
if (g_thread_ctx->completion_event)
|
||||
CloseHandle(g_thread_ctx->completion_event);
|
||||
if (g_thread_ctx->ring)
|
||||
CloseIoRing(g_thread_ctx->ring);
|
||||
for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) {
|
||||
_aligned_free(g_thread_ctx->buffers[i].data);
|
||||
}
|
||||
free(g_thread_ctx);
|
||||
g_thread_ctx = NULL;
|
||||
}
|
||||
|
||||
// ---------------------- Get a free buffer from pool ------------------------
|
||||
static IoBuffer *get_free_buffer(ThreadIoContext *ctx) {
|
||||
|
||||
if (ctx->free_count == 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int idx = ctx->buffer_pool[--ctx->free_count];
|
||||
IoBuffer *buf = &ctx->buffers[idx];
|
||||
buf->completed = 0;
|
||||
buf->bytes_read = 0;
|
||||
buf->result = E_PENDING;
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void return_buffer(ThreadIoContext *ctx, IoBuffer *buf) {
|
||||
if (!buf)
|
||||
return;
|
||||
|
||||
ctx->buffer_pool[ctx->free_count++] = buf->buffer_id;
|
||||
}
|
||||
|
||||
// -------------------------- Submit async read ---------------------------
|
||||
static HRESULT submit_read(ThreadIoContext *ctx, FileReadContext *file_ctx,
|
||||
IoBuffer *buf, uint64_t offset, size_t size) {
|
||||
buf->offset = offset;
|
||||
buf->size = size;
|
||||
|
||||
IORING_HANDLE_REF file_ref = IoRingHandleRefFromHandle(file_ctx->hFile);
|
||||
IORING_BUFFER_REF buffer_ref =
|
||||
IoRingBufferRefFromIndexAndOffset(buf->buffer_id, 0);
|
||||
|
||||
HRESULT hr =
|
||||
BuildIoRingReadFile(ctx->ring, file_ref, buffer_ref, (UINT32)size, offset,
|
||||
(UINT_PTR)buf, IOSQE_FLAGS_NONE);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
file_ctx->active_reads++;
|
||||
file_ctx->reads_submitted++;
|
||||
} else {
|
||||
buf->completed = 1;
|
||||
return_buffer(ctx, buf);
|
||||
}
|
||||
return hr;
|
||||
}
|
||||
|
||||
// -------------------------- Process completions ---------------------------
|
||||
static int process_completions(ThreadIoContext *ctx,
|
||||
FileReadContext *file_ctx) {
|
||||
IORING_CQE cqe;
|
||||
int processed = 0;
|
||||
|
||||
while (PopIoRingCompletion(ctx->ring, &cqe) == S_OK) {
|
||||
|
||||
if (cqe.UserData == USERDATA_REGISTER || cqe.UserData == 0)
|
||||
continue;
|
||||
|
||||
IoBuffer *buf = (IoBuffer *)cqe.UserData;
|
||||
|
||||
if (buf && !buf->completed) {
|
||||
buf->result = cqe.ResultCode;
|
||||
buf->bytes_read = (DWORD)cqe.Information;
|
||||
buf->completed = 1;
|
||||
|
||||
if (SUCCEEDED(cqe.ResultCode) && cqe.Information > 0) {
|
||||
file_ctx->active_reads--;
|
||||
file_ctx->reads_completed++;
|
||||
file_ctx->buffers_ready++;
|
||||
} else {
|
||||
file_ctx->failed_reads++;
|
||||
file_ctx->active_reads--;
|
||||
file_ctx->reads_completed++;
|
||||
}
|
||||
|
||||
processed++;
|
||||
}
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
// -------------------- Hash buffer in sequential order -----------------------
|
||||
static int hash_sequential_buffers(ThreadIoContext *ctx,
|
||||
FileReadContext *file_ctx) {
|
||||
int hashed = 0;
|
||||
|
||||
// Keep hashing while the next buffer in sequence is ready
|
||||
while (file_ctx->next_hash_offset < file_ctx->file_size) {
|
||||
// Find the buffer that contains next_hash_offset
|
||||
IoBuffer *found_buf = NULL;
|
||||
|
||||
for (int i = 0; i < NUM_BUFFERS_PER_THREAD; i++) {
|
||||
IoBuffer *buf = &ctx->buffers[i];
|
||||
if (buf->completed && buf->offset == file_ctx->next_hash_offset) {
|
||||
found_buf = buf;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_buf)
|
||||
break; // Buffer not ready yet
|
||||
|
||||
// Found the correct buffer in order - hash it
|
||||
if (SUCCEEDED(found_buf->result) && found_buf->bytes_read > 0) {
|
||||
XXH3_128bits_update(&file_ctx->hash_state, found_buf->data,
|
||||
found_buf->bytes_read);
|
||||
atomic_fetch_add(&g_bytes_processed, found_buf->bytes_read);
|
||||
|
||||
// Update bytes_hashed for this file!
|
||||
file_ctx->bytes_hashed += found_buf->bytes_read;
|
||||
file_ctx->reads_hashed++;
|
||||
file_ctx->buffers_ready--;
|
||||
|
||||
// Mark as processed and return buffer to pool
|
||||
found_buf->completed = 0;
|
||||
return_buffer(ctx, found_buf);
|
||||
|
||||
// Move to next offset
|
||||
file_ctx->next_hash_offset += found_buf->size;
|
||||
hashed++;
|
||||
} else if (found_buf->bytes_read == 0 && SUCCEEDED(found_buf->result)) {
|
||||
// Read operation failed with an error code
|
||||
file_ctx->reads_hashed++;
|
||||
file_ctx->buffers_ready--;
|
||||
found_buf->completed = 0;
|
||||
return_buffer(ctx, found_buf);
|
||||
file_ctx->next_hash_offset += found_buf->size;
|
||||
hashed++;
|
||||
} else {
|
||||
// Read failed
|
||||
file_ctx->failed_reads++;
|
||||
file_ctx->reads_hashed++;
|
||||
file_ctx->buffers_ready--;
|
||||
found_buf->completed = 0;
|
||||
return_buffer(ctx, found_buf);
|
||||
file_ctx->next_hash_offset += found_buf->size;
|
||||
hashed++;
|
||||
}
|
||||
}
|
||||
|
||||
return hashed;
|
||||
}
|
||||
|
||||
// ------------- Submit pending reads - fill all free buffers -----------------
|
||||
static int submit_pending_reads(ThreadIoContext *ctx,
|
||||
FileReadContext *file_ctx) {
|
||||
int submitted = 0;
|
||||
|
||||
while (1) {
|
||||
// Check if we have more data to read
|
||||
uint64_t current_offset = file_ctx->next_read_offset;
|
||||
int has_data = (current_offset < file_ctx->file_size);
|
||||
|
||||
if (!has_data)
|
||||
break;
|
||||
|
||||
// Get a free buffer
|
||||
IoBuffer *buf = get_free_buffer(ctx);
|
||||
if (!buf)
|
||||
break;
|
||||
|
||||
size_t remaining = file_ctx->file_size - current_offset;
|
||||
|
||||
size_t bytes_to_read;
|
||||
|
||||
if (remaining >= IORING_READ_BLOCK) {
|
||||
bytes_to_read = IORING_READ_BLOCK;
|
||||
} else {
|
||||
// Round UP to sector size (4096)
|
||||
bytes_to_read = (remaining + 4095) & ~4095;
|
||||
}
|
||||
|
||||
HRESULT hr = submit_read(ctx, file_ctx, buf, current_offset, bytes_to_read);
|
||||
|
||||
if (SUCCEEDED(hr)) {
|
||||
submitted++;
|
||||
|
||||
file_ctx->next_read_offset += bytes_to_read;
|
||||
} else {
|
||||
return_buffer(ctx, buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return submitted;
|
||||
}
|
||||
|
||||
// -------------------------- Wait for completions ---------------------------
|
||||
static void wait_for_completions(ThreadIoContext *ctx,
|
||||
FileReadContext *file_ctx) {
|
||||
int has_active = (file_ctx->active_reads > 0);
|
||||
|
||||
if (has_active && ctx->completion_event) {
|
||||
WaitForSingleObject(ctx->completion_event, SUBMIT_TIMEOUT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------- Main parallel hashing function ----------------------
|
||||
static void xxh3_hash_file_parallel(ThreadIoContext *ctx, const char *path,
|
||||
char *out_hex, unsigned char *temp_buffer) {
|
||||
|
||||
// Validate I/O Ring
|
||||
if (!ctx || !ctx->ring) {
|
||||
xxh3_hash_file_stream(path, out_hex, temp_buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
HANDLE hFile = CreateFileA(
|
||||
path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
|
||||
OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL);
|
||||
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
xxh3_hash_file_stream(path, out_hex, temp_buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
LARGE_INTEGER file_size;
|
||||
if (!GetFileSizeEx(hFile, &file_size)) {
|
||||
xxh3_hash_file_stream(path, out_hex, temp_buffer);
|
||||
CloseHandle(hFile);
|
||||
return;
|
||||
}
|
||||
|
||||
FileReadContext file_ctx;
|
||||
memset(&file_ctx, 0, sizeof(file_ctx));
|
||||
file_ctx.hFile = hFile;
|
||||
file_ctx.file_size = file_size.QuadPart;
|
||||
file_ctx.next_hash_offset = 0;
|
||||
file_ctx.next_read_offset = 0;
|
||||
strncpy(file_ctx.path, path, MAX_PATH - 1);
|
||||
file_ctx.path[MAX_PATH - 1] = 0;
|
||||
XXH3_128bits_reset(&file_ctx.hash_state);
|
||||
|
||||
// Submit initial reads
|
||||
submit_pending_reads(ctx, &file_ctx);
|
||||
|
||||
if (file_ctx.reads_submitted > 0) {
|
||||
UINT32 submitted = 0;
|
||||
SubmitIoRing(ctx->ring, 0, 0, &submitted);
|
||||
}
|
||||
|
||||
// Main loop
|
||||
while (file_ctx.reads_hashed < file_ctx.reads_submitted) {
|
||||
// Process completions
|
||||
process_completions(ctx, &file_ctx);
|
||||
|
||||
// Hash buffers in sequential order (critical!)
|
||||
hash_sequential_buffers(ctx, &file_ctx);
|
||||
|
||||
// Submit more reads if needed
|
||||
if (file_ctx.active_reads < NUM_BUFFERS_PER_THREAD &&
|
||||
file_ctx.next_read_offset < file_ctx.file_size) {
|
||||
int new_reads = submit_pending_reads(ctx, &file_ctx);
|
||||
if (new_reads > 0) {
|
||||
UINT32 submitted = 0;
|
||||
SubmitIoRing(ctx->ring, 0, 0, &submitted);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait if nothing to hash and active reads exist
|
||||
if (file_ctx.active_reads > 0 && file_ctx.buffers_ready == 0) {
|
||||
wait_for_completions(ctx, &file_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
// Final verification
|
||||
if (file_ctx.bytes_hashed == file_ctx.file_size &&
|
||||
file_ctx.failed_reads == 0) {
|
||||
XXH128_hash_t h = XXH3_128bits_digest(&file_ctx.hash_state);
|
||||
snprintf(out_hex, HASH_STRLEN, "%016llx%016llx",
|
||||
(unsigned long long)h.high64, (unsigned long long)h.low64);
|
||||
} else {
|
||||
if (file_ctx.bytes_hashed != file_ctx.file_size) {
|
||||
atomic_fetch_add(&g_io_ring_fallbacks, 1);
|
||||
}
|
||||
xxh3_hash_file_stream(path, out_hex, temp_buffer);
|
||||
}
|
||||
|
||||
CloseHandle(hFile);
|
||||
}
|
||||
|
||||
// -------------------------- Hash worker I/O Ring ---------------------------
|
||||
static THREAD_RETURN hash_worker_io_ring(void *arg) {
|
||||
WorkerContext *ctx = (WorkerContext *)arg;
|
||||
unsigned char *temp_buffer = (unsigned char *)malloc(IORING_READ_BLOCK);
|
||||
char hash[HASH_STRLEN];
|
||||
|
||||
if (!temp_buffer)
|
||||
return THREAD_RETURN_VALUE;
|
||||
|
||||
// Initialize I/O Ring for this thread
|
||||
ThreadIoContext *ring_ctx = io_ring_init_thread();
|
||||
if (!ring_ctx || !ring_ctx->ring) {
|
||||
printf("Thread %lu: I/O Ring unavailable, using buffered I/O\n",
|
||||
GetCurrentThreadId());
|
||||
free(temp_buffer);
|
||||
return hash_worker(arg);
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
FileEntry *fe = mpmc_pop(ctx->file_queue);
|
||||
if (!fe)
|
||||
break;
|
||||
|
||||
// Pass the I/O Ring context to the hashing function
|
||||
xxh3_hash_file_parallel(ring_ctx, fe->path, hash, temp_buffer);
|
||||
|
||||
char created[32], modified[32];
|
||||
format_time(fe->created_time, created, sizeof(created));
|
||||
format_time(fe->modified_time, modified, sizeof(modified));
|
||||
|
||||
double size_kib = (double)fe->size_bytes / 1024.0;
|
||||
|
||||
char stack_buf[1024];
|
||||
int len =
|
||||
snprintf(stack_buf, sizeof(stack_buf), "%s\t%s\t%.2f\t%s\t%s\t%s\n",
|
||||
hash, fe->path, size_kib, created, modified, fe->owner);
|
||||
|
||||
char *dst = arena_push(&ctx->arena, len, false);
|
||||
memcpy(dst, stack_buf, len);
|
||||
atomic_fetch_add(&g_files_hashed, 1);
|
||||
}
|
||||
|
||||
io_ring_cleanup_thread();
|
||||
free(temp_buffer);
|
||||
|
||||
return THREAD_RETURN_VALUE;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#define XXH_INLINE_ALL
|
||||
|
||||
/*
|
||||
* xxHash - Extremely Fast Hash algorithm
|
||||
* Copyright (C) 2020-2021 Yann Collet
|
||||
|
||||
Reference in New Issue
Block a user