Reworking process_completion() function
This commit is contained in:
53
README.md
53
README.md
@@ -26,13 +26,38 @@ It is a high performance cross platform Windows and Linux compatible program, it
|
||||
UCRT64 uses the modern Universal C Runtime (ucrtbase.dll), which supports the newest APIs,
|
||||
the standard MSYS2 uses the legacy msvcrt.dll and does not support IO Ring.
|
||||
To install:
|
||||
pacman -S mingw-w64-ucrt-x86_64-gcc
|
||||
pacman -S mingw-w64-ucrt-x86_64-clang
|
||||
pacman -S mingw-w64-ucrt-x86_64-cmake
|
||||
or:
|
||||
pacman -S mingw-w64-ucrt-x86_64-gcc
|
||||
|
||||
pacman -Syu
|
||||
And add to path:
|
||||
C:\msys64\ucrt64\bin
|
||||
|
||||
Additionally, to use clang-cl install the latest version of Windows SDK and MSVC, or at least select these in Visual Studio Installer:
|
||||
* MSVC Build tools fo x64/86.
|
||||
* C++ Build tools core features.
|
||||
* MSBuild support for LLVM (clang-cl) toolset.
|
||||
* Windows Universal C runtime.
|
||||
* Windows Universal CRT SDK.
|
||||
* Windows 11 SDK.
|
||||
|
||||
And use the MSVC command prompt or run a script to add MSVC environment variables to current session.
|
||||
Ex: for PowerShell Terminal save as .ps1 (not persistent):
|
||||
```ps1
|
||||
# Add MS visual studio environment variables
|
||||
cmd /c '"C:\Program Files (x86)\Microsoft Visual Studio\18\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" x64 && set' |
|
||||
ForEach-Object {
|
||||
if ($_ -match "^(.*?)=(.*)$") {
|
||||
Set-Item -Path "Env:$($matches[1])" -Value $matches[2]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Optional: to use the build system
|
||||
pacman -S mingw-w64-ucrt-x86_64-cmake
|
||||
The build system uses Ninja and fallsback to make, in Windows it prefers clang-cl > gcc > clang, and in Linux gcc > clang.
|
||||
|
||||
### Using a build system
|
||||
| Command | Description|
|
||||
| :--- | :--- |
|
||||
@@ -52,7 +77,7 @@ clang -g -O0 file_hasher.c xxhash.c xxh_x86dispatch.c -o filehasher
|
||||
clang-cl /Zi /Od file_hasher.c xxhash.c xxh_x86dispatch.c
|
||||
|
||||
## Linux
|
||||
**Requirements**: GCC, CMake and Ninja
|
||||
**Requirements**: GCC or clang, optional CMake, Ninja or make.
|
||||
|
||||
### Using a build system
|
||||
| Command | Description|
|
||||
@@ -109,13 +134,16 @@ While both systems share the same core concept, their APIs and management styles
|
||||
| **Partial Updates** | Supports `IORING_REGISTER_FILES_UPDATE` to swap specific indices. | No partial updates; a new registration replaces the entire table. |
|
||||
| **Scope of Operations** | Extremely broad (files, sockets, timers, signals, etc.). | Primarily focused on file storage (read, write, flush). |
|
||||
|
||||
### Completion Wait count
|
||||
### Completion Wait count and peek
|
||||
To avoid busy waiting when receiving CQEs, we can use io_uring_submit_and_wait() in Linux by entering a wait count,
|
||||
the threads sleeps until the count of CQEs are received, in windows the wait_count is present in SubmitIoRing()
|
||||
the threads sleep until the count of CQEs are received, in windows the wait_count is present in SubmitIoRing()
|
||||
but is not implemented yet, so we wait with a completion event for a single completion. Another limitation on the completion
|
||||
event is that the kernel will waik up the thread only when receiving the first CQE, after that we need to drain the completion
|
||||
queue completely before sleeping again, or we enter an eternal slumber. And my config, each time the thread wakes up
|
||||
it receives rarely more than 3 to 5 CQEs and most of the time only one CQE.
|
||||
queue completely before sleeping again, or we enter an eternal slumber.
|
||||
In the other hand, in Linux we can batch pop completions with io_uring_peek_batch_cqe() + io_uring_cq_advance(),
|
||||
in Windows we can only pop one completion at a time with PopIoRingCompletion() (equivalent to io_uring_peek_cqe() + io_uring_cqe_seen()).
|
||||
To simulate the same behavior as the Linux functions we use a double loop, an outer loop to control how much we wait
|
||||
and in inner loop to drain all the available completions.
|
||||
|
||||
### Filtering CQEs
|
||||
|
||||
@@ -149,12 +177,15 @@ IO Ring implementation.
|
||||
"Increase the limit to solve this warning.\n");
|
||||
```
|
||||
|
||||
The Memlock limit in Linux restricts the amount of memory a process can
|
||||
"lock" into physical RAM using the mlock() family of system calls. This
|
||||
The Memlock limit in Linux restricts the amount of memory that can be
|
||||
"locked" into physical RAM using the mlock() family of system calls. This
|
||||
prevents the operating system from swapping that memory out to disk.
|
||||
And registering buffers will lock the buffers memory so the hardware
|
||||
can access it directly without kernel intervention and prevents the kernel from
|
||||
swapping it to the SSD or HDD. Increase the limit to be able to register the buffers.
|
||||
swapping it to the SSD or HDD.
|
||||
This limit does not apply to a single process, but it applies to what all the runnig processes can lock, so in order
|
||||
to be able to register the buffers, we need to set it to unlimited or increase it to at least:
|
||||
num_hash_threads * NUM_BUFFERS_PER_THREAD * IORING_BUFFER_SIZE + extra memory reserved for other processes.
|
||||
|
||||
#### *Modifying the Limit*
|
||||
The method for changing the memlock limit depends on whether you are
|
||||
@@ -166,7 +197,7 @@ the /etc/security/limits.conf file. Add the following lines:
|
||||
```conf
|
||||
# Example for a specific user (replace 'username'), unlimited or a custom value in KB
|
||||
username soft memlock unlimited
|
||||
username hard memlock unlimited
|
||||
username hard memlock unlimitedhttps://wiki.postgresql.org/wiki/AIO
|
||||
```
|
||||
```conf
|
||||
# Example for all users
|
||||
|
||||
82
build.bat
82
build.bat
@@ -2,14 +2,23 @@
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
:: ============================================================================
|
||||
:: build.bat - Build script with compiler preference: clang-cl > gcc > clang
|
||||
:: Usage: build [Release|Debug] [clean]
|
||||
:: build.bat
|
||||
:: ============================================================================
|
||||
|
||||
:: Get script directory (project root)
|
||||
set SCRIPT_DIR=%~dp0
|
||||
set SCRIPT_DIR=%SCRIPT_DIR:~0,-1%
|
||||
|
||||
:: ---------------------------------------------------------------------------
|
||||
:: Default values
|
||||
:: ---------------------------------------------------------------------------
|
||||
|
||||
set BUILD_TYPE=Release
|
||||
set CLEAN_BUILD=0
|
||||
|
||||
:: --------------------------------------------------------------------------
|
||||
:: Parse arguments
|
||||
:: --------------------------------------------------------------------------
|
||||
:parse_args
|
||||
if "%~1"=="" goto :main
|
||||
|
||||
@@ -29,27 +38,34 @@ if /i "%~1"=="clean" (
|
||||
goto :parse_args
|
||||
)
|
||||
|
||||
:: Unknown argument fallback (the *)
|
||||
echo Unknown argument: %~1
|
||||
echo Usage: .\%~nx0 [Release^|Debug] [clean]
|
||||
echo Usage: build [Release^|Debug] [clean]
|
||||
exit /b 1
|
||||
|
||||
:main
|
||||
set BUILD_DIR=build\windows\%BUILD_TYPE%
|
||||
set BUILD_DIR=%SCRIPT_DIR%\build\windows\%BUILD_TYPE%
|
||||
|
||||
echo === Building filehasher (%BUILD_TYPE%) ===
|
||||
|
||||
:: --------------------------------------------------------------------------
|
||||
:: Clean if requested
|
||||
:: --------------------------------------------------------------------------
|
||||
if %CLEAN_BUILD%==1 (
|
||||
echo Cleaning...
|
||||
if exist "%BUILD_DIR%" rmdir /s /q "%BUILD_DIR%" 2>nul
|
||||
)
|
||||
|
||||
:: Create build dir
|
||||
:: --------------------------------------------------------------------------
|
||||
:: Create build directory
|
||||
:: --------------------------------------------------------------------------
|
||||
if not exist "%BUILD_DIR%" mkdir "%BUILD_DIR%"
|
||||
pushd "%BUILD_DIR%"
|
||||
|
||||
:: Find compiler in preferred order
|
||||
:: --------------------------------------------------------------------------
|
||||
:: Compiler selection
|
||||
:: --------------------------------------------------------------------------
|
||||
set CC=
|
||||
|
||||
where clang-cl >nul 2>&1
|
||||
if !ERRORLEVEL! equ 0 (
|
||||
echo Compiler: clang-cl ^(preferred^)
|
||||
@@ -71,12 +87,14 @@ if !ERRORLEVEL! equ 0 (
|
||||
goto :find_generator
|
||||
)
|
||||
|
||||
echo ERROR: No suitable compiler found! (clang-cl, gcc, or clang required)
|
||||
echo ERROR: No suitable compiler found!
|
||||
popd
|
||||
exit /b 1
|
||||
|
||||
:: --------------------------------------------------------------------------
|
||||
:: Generator selection (prefer ninja)
|
||||
:: --------------------------------------------------------------------------
|
||||
:find_generator
|
||||
:: Find Ninja for build system
|
||||
set GEN=
|
||||
where ninja >nul 2>&1
|
||||
if !ERRORLEVEL! equ 0 (
|
||||
@@ -86,35 +104,67 @@ if !ERRORLEVEL! equ 0 (
|
||||
echo Generator: Default
|
||||
)
|
||||
|
||||
:: --------------------------------------------------------------------------
|
||||
:: Configure
|
||||
:: --------------------------------------------------------------------------
|
||||
echo.
|
||||
echo Configuring CMake...
|
||||
set CMD=cmake ../../.. %GEN% %CC% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
||||
|
||||
:: --------------------------------------------------------------------------
|
||||
:: compile_commands.json logic
|
||||
:: --------------------------------------------------------------------------
|
||||
set EXPORT_COMPILE_COMMANDS=OFF
|
||||
|
||||
if /i "%BUILD_TYPE%"=="Release" (
|
||||
if exist "%SCRIPT_DIR%\compile_commands.json" (
|
||||
echo compile_commands.json already exists - skipping generation
|
||||
) else (
|
||||
echo compile_commands.json will be generated
|
||||
set EXPORT_COMPILE_COMMANDS=ON
|
||||
)
|
||||
)
|
||||
|
||||
set CMD=cmake "%SCRIPT_DIR%" %GEN% %CC% -DCMAKE_BUILD_TYPE=%BUILD_TYPE% -DCMAKE_EXPORT_COMPILE_COMMANDS=%EXPORT_COMPILE_COMMANDS%
|
||||
|
||||
echo !CMD!
|
||||
!CMD!
|
||||
if !ERRORLEVEL! neq 0 (echo ERROR: Configuration failed & popd & exit /b 1)
|
||||
if !ERRORLEVEL! neq 0 (
|
||||
echo ERROR: Configuration failed
|
||||
popd
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: --------------------------------------------------------------------------
|
||||
:: Build
|
||||
:: --------------------------------------------------------------------------
|
||||
echo.
|
||||
echo Building...
|
||||
cmake --build . --config %BUILD_TYPE%
|
||||
if !ERRORLEVEL! neq 0 (echo ERROR: Build failed & popd & exit /b 1)
|
||||
if !ERRORLEVEL! neq 0 (
|
||||
echo ERROR: Build failed
|
||||
popd
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: Check if compile_commands.json exists in the current build directory
|
||||
:: --------------------------------------------------------------------------
|
||||
:: Copy compile_commands.json (only if generated)
|
||||
:: --------------------------------------------------------------------------
|
||||
if /i "%EXPORT_COMPILE_COMMANDS%"=="ON" (
|
||||
if exist "compile_commands.json" (
|
||||
echo.
|
||||
echo clangd: compile_commands.json generated
|
||||
|
||||
:: Copy from current build dir up two levels to the project root
|
||||
copy /Y "compile_commands.json" "..\..\..\compile_commands.json" >nul 2>&1
|
||||
copy /Y "compile_commands.json" "%SCRIPT_DIR%\compile_commands.json" >nul 2>&1
|
||||
if !ERRORLEVEL! equ 0 (
|
||||
echo clangd: Copied to project root
|
||||
) else (
|
||||
echo clangd: Could not copy to project root
|
||||
echo clangd: Copy failed
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
popd
|
||||
|
||||
echo.
|
||||
echo === Build Complete ===
|
||||
echo Executable: %BUILD_DIR%\filehasher.exe
|
||||
25
build.sh
25
build.sh
@@ -74,7 +74,7 @@ mkdir -p "${BUILD_DIR}"
|
||||
cd "${BUILD_DIR}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Find compiler (prefer gcc, fallback to clang)
|
||||
# Compiler selection (prefer gcc, fallback to clang)
|
||||
# ---------------------------------------------------------------------------
|
||||
echo -e "${YELLOW}Detecting compiler...${NC}"
|
||||
|
||||
@@ -135,7 +135,7 @@ fi
|
||||
echo
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Find build system (prefer ninja)
|
||||
# Generator selection (prefer ninja)
|
||||
# ---------------------------------------------------------------------------
|
||||
echo -e "${YELLOW}Selecting build system...${NC}"
|
||||
|
||||
@@ -165,6 +165,22 @@ echo
|
||||
# Configure
|
||||
# ---------------------------------------------------------------------------
|
||||
echo -e "${YELLOW}Configuring CMake...${NC}"
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# compile_commands.json logic
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
EXPORT_COMPILE_COMMANDS=OFF
|
||||
|
||||
if [[ "$BUILD_TYPE" == "Release" ]]; then
|
||||
if [[ -f "${SCRIPT_DIR}/compile_commands.json" ]]; then
|
||||
echo -e " compile_commands.json already exists - skipping generation"
|
||||
else
|
||||
echo -e " compile_commands.json will be generated"
|
||||
EXPORT_COMPILE_COMMANDS=ON
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e " Build type: ${BUILD_TYPE}"
|
||||
echo -e " Compiler: ${CC_NAME}"
|
||||
echo -e " Generator: ${GENERATOR_NAME}"
|
||||
@@ -173,7 +189,7 @@ cmake "${SCRIPT_DIR}" \
|
||||
-G "${GENERATOR}" \
|
||||
-DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \
|
||||
-DCMAKE_C_COMPILER="${CC_BINARY}" \
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=ON
|
||||
-DCMAKE_EXPORT_COMPILE_COMMANDS=${EXPORT_COMPILE_COMMANDS}
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "${RED}CMake configuration failed!${NC}"
|
||||
@@ -243,13 +259,14 @@ echo -e " Output: ${BUILD_DIR}/"
|
||||
# ---------------------------------------------------------------------------
|
||||
# Copy compile_commands.json for clangd
|
||||
# ---------------------------------------------------------------------------
|
||||
if [[ "${EXPORT_COMPILE_COMMANDS}" == "ON" ]]; then
|
||||
if [[ -f "${BUILD_DIR}/compile_commands.json" ]]; then
|
||||
echo -e " clangd: compile_commands.json generated"
|
||||
|
||||
# Always copy the latest version
|
||||
cp "${BUILD_DIR}/compile_commands.json" "${SCRIPT_DIR}/compile_commands.json"
|
||||
echo -e " clangd: Copied to project root"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
echo -e "${GREEN}Ready to run: ./${BUILD_DIR}/filehasher${NC}"
|
||||
|
||||
1
config.h
1
config.h
@@ -15,6 +15,7 @@
|
||||
#define IORING_BUFFER_SIZE KiB(256)
|
||||
#define NUM_BUFFERS_PER_THREAD 32
|
||||
#define MAX_ACTIVE_FILES 16
|
||||
#define MAX_WAIT_COUNT (NUM_BUFFERS_PER_THREAD / 2)
|
||||
|
||||
#define SUBMIT_TIMEOUT_MS 10000
|
||||
#define IORING_DEBUG_PRINTS 0
|
||||
|
||||
244
platform.c
244
platform.c
@@ -5,6 +5,7 @@
|
||||
#include "lf_mpmc.h"
|
||||
|
||||
#include "arena.c"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// xxhash include
|
||||
@@ -1149,12 +1150,6 @@ typedef struct {
|
||||
uint32_t MaxVersion;
|
||||
} IoRingCapabilities;
|
||||
|
||||
typedef struct {
|
||||
BUILD_READ_RETURN_VALUE ResultCode;
|
||||
uint32_t Information;
|
||||
uintptr_t UserData;
|
||||
} IoRingCQE;
|
||||
|
||||
// ------------------------ IO Ring Abstraction -------------------------
|
||||
#if defined(_WIN32) || defined(_WIN64)
|
||||
|
||||
@@ -1192,13 +1187,20 @@ static int close_ioring(ThreadIoContext *thread_ctx) {
|
||||
#define MAKE_BUF_INFO(a, l) \
|
||||
(IORING_BUFFER_INFO) { .Address = (a), .Length = (uint32_t)(l) }
|
||||
|
||||
static int ioring_submit(ThreadIoContext *thread_ctx, uint32_t wait_count,
|
||||
uint32_t *submitted) {
|
||||
HRESULT hr = SubmitIoRing(thread_ctx->ring, 0, SUBMIT_TIMEOUT_MS, submitted);
|
||||
// HRESULT hr = SubmitIoRing(ring, wait_count, SUBMIT_TIMEOUT_MS, submitted);
|
||||
static int ioring_submit(ThreadIoContext *thread_ctx, uint32_t *submitted) {
|
||||
|
||||
// The wait_count in windows is not implemented yet, so we wait with a
|
||||
// completion event for a single completion
|
||||
// uint32_t wait_count = MIN(thread_ctx->num_submissions, MAX_WAIT_COUNT);
|
||||
|
||||
// The wait_count in windows is not implemented yet, so we wait in
|
||||
// ioring_pop_completion()
|
||||
|
||||
HRESULT hr;
|
||||
|
||||
// if (wait_count > 0) {
|
||||
// hr = SubmitIoRing(ring, wait_count, SUBMIT_TIMEOUT_MS, submitted);
|
||||
// } else {
|
||||
hr = SubmitIoRing(thread_ctx->ring, 0, SUBMIT_TIMEOUT_MS, submitted);
|
||||
// }
|
||||
if (thread_ctx->num_submissions > 0) {
|
||||
WaitForSingleObject(thread_ctx->completion_event, SUBMIT_TIMEOUT_MS);
|
||||
}
|
||||
@@ -1222,7 +1224,7 @@ static void ioring_register_buffers(ThreadIoContext *thread_ctx,
|
||||
error_msg, (unsigned int)hr);
|
||||
}
|
||||
// Submit registration
|
||||
ioring_submit(thread_ctx, 0, NULL);
|
||||
ioring_submit(thread_ctx, NULL);
|
||||
}
|
||||
|
||||
#if USE_REGISTERED_FILES
|
||||
@@ -1293,52 +1295,69 @@ static BUILD_READ_RETURN_VALUE ioring_build_read(ThreadIoContext *thread_ctx,
|
||||
return hr;
|
||||
}
|
||||
|
||||
static int ioring_pop_completion(IoRingHandle ring, IoRingCQE *cqe) {
|
||||
static void ioring_process_completions(ThreadIoContext *thread_ctx) {
|
||||
uint32_t waited = 0;
|
||||
uint32_t target = MIN(thread_ctx->num_submissions, MAX_WAIT_COUNT);
|
||||
|
||||
while (waited < target) {
|
||||
|
||||
// ---- Drain all available CQEs (non-blocking) ----
|
||||
while (1) {
|
||||
IORING_CQE win_cqe;
|
||||
|
||||
while (1) {
|
||||
HRESULT hr = PopIoRingCompletion(ring, &win_cqe);
|
||||
HRESULT hr = PopIoRingCompletion(thread_ctx->ring, &win_cqe);
|
||||
|
||||
if (hr == S_FALSE)
|
||||
// No CQE available
|
||||
return 0;
|
||||
|
||||
if (FAILED(hr))
|
||||
return -1;
|
||||
|
||||
// Unlike linux, The Windows implementation treats buffer and file
|
||||
// registration as an asynchronous operation that we submit to the ring,
|
||||
// similar to a read or write. Those operations produce CQEs (completion
|
||||
// queue entries) that we filter here using
|
||||
// cqe.UserData == USERDATA_REGISTER
|
||||
if (win_cqe.UserData == USERDATA_REGISTER)
|
||||
continue;
|
||||
|
||||
cqe->ResultCode = win_cqe.ResultCode;
|
||||
cqe->Information = win_cqe.Information;
|
||||
cqe->UserData = win_cqe.UserData;
|
||||
|
||||
// Check for error and print warning
|
||||
if (FAILED(win_cqe.ResultCode)) {
|
||||
char error_msg[256];
|
||||
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, win_cqe.ResultCode, 0, error_msg, sizeof(error_msg),
|
||||
NULL);
|
||||
|
||||
// Try to get the file path from the buffer
|
||||
IoBuffer *buf = (IoBuffer *)win_cqe.UserData;
|
||||
const char *file_path = "unknown";
|
||||
if (buf && buf->file && buf->file->fe) {
|
||||
file_path = buf->file->fe->path;
|
||||
if (hr == S_FALSE) {
|
||||
// No more CQEs available right now
|
||||
break;
|
||||
}
|
||||
|
||||
if (FAILED(hr)) {
|
||||
fprintf(stderr, "WARNING: PopIoRingCompletion failed (0x%lx)\n", hr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip internal registration completions
|
||||
if (win_cqe.UserData == USERDATA_REGISTER) {
|
||||
continue;
|
||||
}
|
||||
|
||||
IoBuffer *buf = (IoBuffer *)win_cqe.UserData;
|
||||
FileReadContext *file = buf->file;
|
||||
|
||||
if (SUCCEEDED(win_cqe.ResultCode)) {
|
||||
buf->result = 0;
|
||||
buf->bytes_read = win_cqe.Information;
|
||||
} else {
|
||||
buf->result = win_cqe.ResultCode;
|
||||
buf->bytes_read = 0;
|
||||
|
||||
char error_msg[256];
|
||||
FormatMessageA(
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
|
||||
win_cqe.ResultCode, 0, error_msg, sizeof(error_msg), NULL);
|
||||
|
||||
fprintf(stderr,
|
||||
"WARNING: I/O completion error for file '%s' - Error: %s (Code: "
|
||||
"0x%lx)\n",
|
||||
file_path, error_msg, win_cqe.ResultCode);
|
||||
"WARNING: I/O completion error for file '%s' - Error: %s "
|
||||
"(Code: 0x%lx)\n",
|
||||
buf->file->fe->path, error_msg, win_cqe.ResultCode);
|
||||
}
|
||||
|
||||
return 1;
|
||||
file->active_reads--;
|
||||
file->reads_completed++;
|
||||
thread_ctx->num_submissions--;
|
||||
|
||||
// Count only "real" completions toward wait budget
|
||||
waited++;
|
||||
}
|
||||
|
||||
// ---- If we already waited enough, exit ----
|
||||
if (waited >= target) {
|
||||
break;
|
||||
}
|
||||
|
||||
// ---- Otherwise wait for more completions ----
|
||||
WaitForSingleObject(thread_ctx->completion_event, SUBMIT_TIMEOUT_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1431,9 +1450,10 @@ static void ioring_register_buffers(ThreadIoContext *thread_ctx,
|
||||
struct rlimit limit;
|
||||
getrlimit(RLIMIT_MEMLOCK, &limit);
|
||||
|
||||
fprintf(stderr,
|
||||
"WARNING: Buffer registration failed due to memlock limits "
|
||||
"(ENOMEM).\n"
|
||||
fprintf(
|
||||
stderr,
|
||||
"WARNING: Buffer registration failed due to Memlock limit, Error: "
|
||||
"Cannot allocate memory (code: -12, ENOMEM).\n"
|
||||
"See README for more informations.\n");
|
||||
|
||||
} else {
|
||||
@@ -1530,10 +1550,11 @@ static int ioring_build_read(ThreadIoContext *thread_ctx,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ioring_submit(ThreadIoContext *thread_ctx, uint32_t wait_count,
|
||||
uint32_t *submitted) {
|
||||
static int ioring_submit(ThreadIoContext *thread_ctx, uint32_t *submitted) {
|
||||
int ret;
|
||||
|
||||
uint32_t wait_count = MIN(thread_ctx->num_submissions, MAX_WAIT_COUNT);
|
||||
|
||||
if (wait_count > 0) {
|
||||
ret = io_uring_submit_and_wait(&((IoUring *)thread_ctx->ring)->ring,
|
||||
wait_count);
|
||||
@@ -1553,58 +1574,46 @@ static int ioring_submit(ThreadIoContext *thread_ctx, uint32_t wait_count,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ioring_pop_completion(IoRingHandle ring, IoRingCQE *cqe) {
|
||||
static void ioring_process_completions(ThreadIoContext *thread_ctx) {
|
||||
IoUring *impl = (IoUring *)thread_ctx->ring;
|
||||
|
||||
struct io_uring_cqe *cqe_ptr = NULL;
|
||||
struct io_uring_cqe *cqes[NUM_BUFFERS_PER_THREAD];
|
||||
|
||||
int ret = io_uring_peek_cqe(&((IoUring *)ring)->ring, &cqe_ptr);
|
||||
unsigned count =
|
||||
io_uring_peek_batch_cqe(&impl->ring, cqes, NUM_BUFFERS_PER_THREAD);
|
||||
|
||||
if (ret == -EAGAIN) {
|
||||
// No CQE available
|
||||
return 0;
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
// Error
|
||||
fprintf(stderr, "WARNING: io_uring_peek_cqe error - Error: %s (Code: %d)\n",
|
||||
strerror(-ret), ret);
|
||||
return -1;
|
||||
}
|
||||
for (unsigned i = 0; i < count; i++) {
|
||||
struct io_uring_cqe *cqe = cqes[i];
|
||||
|
||||
if (!cqe_ptr) {
|
||||
return 0;
|
||||
}
|
||||
int res = cqe->res;
|
||||
|
||||
int res = cqe_ptr->res;
|
||||
IoBuffer *buf = (IoBuffer *)cqe->user_data;
|
||||
FileReadContext *file = buf->file;
|
||||
|
||||
if (res >= 0) {
|
||||
cqe->ResultCode = 0;
|
||||
cqe->Information = (uint32_t)res;
|
||||
buf->result = 0;
|
||||
buf->bytes_read = (uint32_t)res;
|
||||
} else {
|
||||
cqe->ResultCode = res;
|
||||
cqe->Information = 0;
|
||||
buf->result = res;
|
||||
buf->bytes_read = 0;
|
||||
|
||||
fprintf(stderr,
|
||||
"WARNING: I/O completion error for file '%s' - Error: %s (Code: "
|
||||
"%d)\n",
|
||||
buf->file->fe->path, strerror(-res), res);
|
||||
}
|
||||
|
||||
cqe->UserData = (uintptr_t)cqe_ptr->user_data;
|
||||
|
||||
io_uring_cqe_seen(&((IoUring *)ring)->ring, cqe_ptr);
|
||||
|
||||
// Check for error and print warning
|
||||
if (res < 0) {
|
||||
// Try to get the file path from the buffer
|
||||
IoBuffer *buf = (IoBuffer *)cqe->UserData;
|
||||
const char *file_path = "unknown";
|
||||
if (buf && buf->file && buf->file->fe) {
|
||||
file_path = buf->file->fe->path;
|
||||
file->active_reads--;
|
||||
file->reads_completed++;
|
||||
thread_ctx->num_submissions--;
|
||||
}
|
||||
|
||||
fprintf(
|
||||
stderr,
|
||||
"WARNING: I/O completion error for file '%s' - Error: %s (Code: %d)\n",
|
||||
file_path, strerror(-res), res);
|
||||
}
|
||||
|
||||
return 1;
|
||||
// Mark CQE as seen, equivalent to io_uring_cqe_seen() but marks multiple CQEs
|
||||
io_uring_cq_advance(&impl->ring, count);
|
||||
}
|
||||
|
||||
FileHandle ioring_open_file(FileEntry *fe) {
|
||||
@@ -1790,22 +1799,22 @@ static void return_buffer(ThreadIoContext *ctx, IoBuffer *buf) {
|
||||
}
|
||||
|
||||
// -------------------------- Process completions ---------------------------
|
||||
static void process_completions(ThreadIoContext *thread_ctx) {
|
||||
IoRingCQE cqe;
|
||||
|
||||
while (ioring_pop_completion(thread_ctx->ring, &cqe) == 1) {
|
||||
|
||||
IoBuffer *buf = (IoBuffer *)cqe.UserData;
|
||||
FileReadContext *file = buf->file;
|
||||
|
||||
buf->result = cqe.ResultCode;
|
||||
buf->bytes_read = cqe.Information;
|
||||
|
||||
file->active_reads--;
|
||||
file->reads_completed++;
|
||||
thread_ctx->num_submissions--;
|
||||
}
|
||||
}
|
||||
// static void process_completions(ThreadIoContext *thread_ctx) {
|
||||
// IoRingCQE cqe;
|
||||
//
|
||||
// while (ioring_pop_completion(thread_ctx->ring, &cqe) == 1) {
|
||||
//
|
||||
// IoBuffer *buf = (IoBuffer *)cqe.UserData;
|
||||
// FileReadContext *file = buf->file;
|
||||
//
|
||||
// buf->result = cqe.ResultCode;
|
||||
// buf->bytes_read = cqe.Information;
|
||||
//
|
||||
// file->active_reads--;
|
||||
// file->reads_completed++;
|
||||
// thread_ctx->num_submissions--;
|
||||
// }
|
||||
// }
|
||||
|
||||
// -------------------- File operations -----------------------
|
||||
static int init_file(ThreadIoContext *thread_ctx, FileReadContext *file,
|
||||
@@ -2074,8 +2083,7 @@ static THREAD_RETURN hash_worker_ioring(void *arg) {
|
||||
FileQueue fq;
|
||||
memset(&fq, 0, sizeof(fq));
|
||||
|
||||
uint32_t submitted = 0;
|
||||
uint32_t wait_count;
|
||||
uint32_t submitted;
|
||||
|
||||
// Main pipeline loop
|
||||
for (;;) {
|
||||
@@ -2083,13 +2091,14 @@ static THREAD_RETURN hash_worker_ioring(void *arg) {
|
||||
// Submit new reads
|
||||
build_pending_reads(thread_ctx, &fq, worker_ctx);
|
||||
|
||||
wait_count = MIN(thread_ctx->num_submissions, NUM_BUFFERS_PER_THREAD - 6);
|
||||
|
||||
submitted = 0;
|
||||
ioring_submit(thread_ctx, wait_count, &submitted);
|
||||
ioring_submit(thread_ctx, &submitted);
|
||||
|
||||
// Process completions
|
||||
process_completions(thread_ctx);
|
||||
ioring_process_completions(thread_ctx);
|
||||
|
||||
// Hash files
|
||||
hash_ready_files(thread_ctx, &fq, worker_ctx);
|
||||
|
||||
#if IORING_DEBUG_STATS
|
||||
printf(
|
||||
@@ -2098,9 +2107,6 @@ static THREAD_RETURN hash_worker_ioring(void *arg) {
|
||||
thread_ctx->active_files, fq.count);
|
||||
#endif
|
||||
|
||||
// Hash files
|
||||
hash_ready_files(thread_ctx, &fq, worker_ctx);
|
||||
|
||||
// Exit condition
|
||||
if (!thread_ctx->submitting && thread_ctx->active_files == 0 &&
|
||||
thread_ctx->num_submissions == 0) {
|
||||
|
||||
Reference in New Issue
Block a user