| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Name | Name | Last commit date | ||
|---|---|---|---|---|
An empirical case study and exploration into high-performance UTF-8 (8-bit) and UTF-16 (16-bit wchar_t) substring searching within the Windows kernel environment. The study examines how the cost of entering vector execution in Ring 0 changes the optimal substring-search strategy across buffer sizes, pattern characteristics, and execution environments. Combining Boyer-Moore-Horspool, SWAR, AVX2, and ARM64 NEON, this project examines the low-level trade-offs of kernel-mode vector optimization, cache locality, and general-purpose register (GPR) fallback algorithms. While not strictly benchmark-driven during its design, the repository includes comprehensive hardware telemetry to validate its architectural findings.
In user-mode applications, maximizing substring search performance is often a matter of streaming vectors directly through AVX-512 or AVX2 pipelines. In Windows kernel mode (Ring 0), vector optimization introduces strict architectural trade-offs:
+-------------------------------------------------------------------------+
| Call: Find() |
+------------------------------------+------------------------------------+
|
Buffer <= KERNEL_THRESHOLD?
|
+------------------+------------------+
| YES | NO
v v
+---------------------------+ +-----------------------+
| GPR SWAR / | | KeSaveExtendedState |
| BMH Fast Path | +-----------+-----------+
| (Zero FPU State Overhead) | |
+---------------------------+ +-----------v-----------+
| AVX2 / NEON 4x Slide |
+-----------+-----------+
|
+-----------v-----------+
| KeRestoreExtendedState|
+-----------------------+
The Windows kernel does not preserve floating-point and extended vector (XMM/YMM/ZMM) registers across thread context switches by default. To safely issue SIMD instructions in Ring 0 without corrupting user-mode thread state, the caller must allocate an XSTATE_SAVE structure and invoke KeSaveExtendedProcessorState(XSTATE_MASK_AVX, ...) (or KeSaveFloatingPointState on ARM64).
This state preservation routine writes processor context to memory via XSAVE/XRSTOR, introducing fixed microsecond overheads that severely penalize short operations.
Smart Auto is the default runtime routing mode that selects between the GPR/SWAR/BMH path and the vectorized path according to the implementation's buffer-size threshold and processor capabilities. For small search buffers (e.g., < 512 characters), the XSTATE setup and teardown latency exceeds the execution time of the search itself. The engine uses the KERNEL_THRESHOLD_CHARS heuristic to dynamically balance throughput and context-switch costs:
Table precomputation in Initialize() relies on ExAllocatePool2 with POOL_FLAG_PAGED and POOL_FLAG_CACHE_ALIGNED. The initialization routines are annotated with PAGED_CODE() and must execute at PASSIVE_LEVEL or APC_LEVEL.
The primary production engines (CKmStrSearch8 and CKmStrSearch16) combine Boyer-Moore-Horspool bad-character shift rules with unrolled vector sliding.
UTF-16 characters (wchar_t) span a 65,536 value space. A naive BMH table would require 256KB (65536 * 4 bytes), guaranteed to blow out the CPU's L1 Data Cache (typically 32KB–48KB per core), leading to continuous L2/L3 cache misses.
16-Bit Character (ch)
|
v
[ ch * 0x9E3779B9U ] ---> Multiplicative Hash (Golden Ratio 2^32 / phi)
|
(hash >> 21) ---> 11-Bit Extraction (0 .. 2047)
|
v
[ 8KB Table ] ---> 2,048-entry skip table designed to remain L1-cache resident
To prevent worst-case O(N * M) degradation on highly repetitive streams (e.g., searching for aaaaab within aaaaaaaaaaaaaa...), the SIMD and SWAR loaders broadcast and check the last character of the needle rather than the first. This creates instant mismatch rejections in homogeneous text streams.
SIMD Vectorization Pipeline (4x Unrolled AVX2 / NEON)
+-----------------------------------------------------------------------------+
| Lane 0: [ 32 Bytes YMM0 ] === cmpeq(vLast) ===> Mask0 |
| Lane 1: [ 32 Bytes YMM1 ] === cmpeq(vLast) ===> Mask1 |
| Lane 2: [ 32 Bytes YMM2 ] === cmpeq(vLast) ===> Mask2 ===> OR ===> testz |
| Lane 3: [ 32 Bytes YMM3 ] === cmpeq(vLast) ===> Mask3 |
+-----------------------------------------------------------------------------+
When operating beneath the vector threshold or on platforms lacking AVX2, the engine uses 64-bit general-purpose registers to evaluate memory without invoking FPU state.
Using Mycroft’s bit-twiddling zero-byte detection algorithm:
// 8-Bit SWAR Byte Matching:
ULONGLONG chunk = *reinterpret_cast<const ULONGLONG*>(ptr);
ULONGLONG v = chunk ^ c8; // Matching bytes become 0x00
if (((v - 0x0101010101010101ULL) & ~v & 0x8080808080808080ULL) != 0)
{
// Zero-byte match detected in 8-byte word
}Wide characters adapt the same technique across four 16-bit integers simultaneously using 0x0001000100010001ULL and 0x8000800080008000ULL masks.
Located in the Experimental/ directory, KmBndmSearch8 and KmBndmSearch16 explore an alternative algorithmic theory based on Backward Nondeterministic DAWG Matching (BNDM).
These implementations are not pure BNDM. They are heavily hybridized to survive the constraints of the kernel environment:
Despite its favorable theoretical/algorithmic characteristics, the BNDM variant consistently lags behind the Hybrid BMH implementation in kernel evaluations. Hardware telemetry reveals four persistent architectural bottlenecks:
To evaluate the exploration, testing was performed using a custom multi-threaded kernel test harness (KmStrSearchShared.h). The benchmark numbers are comparative measurements: each implementation is evaluated under the same workload, hardware, and test harness. They are not intended as universal throughput claims or as a replacement benchmark for arbitrary strstr() implementations. CRT strstr/wcsstr serves as the baseline because it provides a familiar reference implementation against which the experimental engines can be compared on identical workloads.
The suite repeatedly executes three distinct corpus patterns to assess best-case, worst-case, and real-world scenarios:
| Buffer Size | Scenario | Engine | Throughput | vs CRT Baseline |
|---|---|---|---|---|
| Tiny (80 B) | Standard | CRT (wcsstr) | 6.72 GB/s | Baseline |
| Scalar Hybrid (BMH) | 3.77 GB/s | 0.56x | ||
| AVX2 (Explicit) | 1.40 GB/s | 0.20x (XSTATE overhead) | ||
| Mismatched Dense | CRT (wcsstr) | 0.81 GB/s | Baseline | |
| Scalar Hybrid (BMH) | 3.80 GB/s | 4.64x | ||
| Medium (20 KB) | Standard | CRT (wcsstr) | 24.63 GB/s | Baseline |
| Smart Auto | 56.59 GB/s | 2.29x | ||
| Mismatched Dense | CRT (wcsstr) | 0.30 GB/s | Baseline | |
| Smart Auto | 56.84 GB/s | 183.42x | ||
| Large (2 MB) | Realistic Log | CRT (wcsstr) | 16.44 GB/s | Baseline |
| Smart Auto | 41.55 GB/s | 2.52x |
In the tested VMware environment, CRT strstr/wcsstr exhibited substantially lower throughput, consistent with an un-vectorized fallback path, magnifying the benefits of explicit Ring 0 vector sliding.
| Buffer Size | Scenario | Engine | Throughput | vs CRT Baseline |
|---|---|---|---|---|
| Medium (20 KB) | Standard (16-bit) | CRT (wcsstr) | 1.49 GB/s | Baseline |
| Smart Auto | 31.46 GB/s | 21.00x | ||
| AVX2 (Explicit) | 48.66 GB/s | 32.47x | ||
| Large (2 MB) | Mismatched Dense (8-bit) | CRT (strstr) | 0.22 GB/s | Baseline |
| Smart Auto | 49.72 GB/s | 222.87x | ||
| Realistic Log (8-bit) | CRT (strstr) | 1.35 GB/s | Baseline | |
| Smart Auto | 52.73 GB/s | 38.99x |
The measurements suggest that kernel substring-search performance is dominated not by a single universally superior algorithm, but by the interaction between buffer size, pattern characteristics, memory locality, and the cost of entering vector execution. The results motivate the hybrid routing strategy and explain why the BNDM variant, despite its attractive bit-parallel structure, did not outperform the simpler BMH-based design in the tested workloads.
├── Experimental/
│ ├── KmBndmSearch8.h # 8-bit BNDM bit-parallel class declaration
│ ├── KmBndmSearch8.cpp # 8-bit BNDM implementation
│ ├── KmBndmSearch16.h # 16-bit BNDM bitmask bloom merger declaration
│ └── KmBndmSearch16.cpp # 16-bit BNDM implementation
├── Shared/
│ └── KmStrSearchShared.h # Unified Kernel/User multi-threaded test harness
├── StrSearch/
│ ├── KmStrSearch8.h # 8-bit substring search class declaration
│ ├── KmStrSearch8.cpp # AVX2, NEON, SWAR, and BMH implementation (8-bit)
│ ├── KmStrSearch16.h # UTF-16 Hybrid BMH class declaration
│ └── KmStrSearch16.cpp # Golden Ratio Hash, AVX2, NEON, SWAR (16-bit)
├── TestKm/
│ └── KmStrSearchDrv.cpp # Kernel driver for test execution
└── TestUm/
└── KmStrSearchUm.cpp # User-mode test suite wrapper
The classes CKmStrSearch8 (for char / UTF-8) and CKmStrSearch16 (for wchar_t / UTF-16) provide matching APIs.
#include <ntddk.h>
#include "KmStrSearch16.h"
VOID SearchExample(const wchar_t* pKernelLogBuffer, size_t cchLogLength)
{
PAGED_CODE(); // Required: Class allocates from Paged Pool
CKmStrSearch16 searchEngine;
const wchar_t needle[] = L"BUGCHECK_CODE_CRITICAL";
// 1. Initialize table (PASSIVE_LEVEL or APC_LEVEL)
if (!searchEngine.Initialize(needle, wcslen(needle)))
{
return; // Allocation failure or invalid length
}
// 2. Execute Search using 'Auto' routing
int matchIndex = searchEngine.Find(pKernelLogBuffer, cchLogLength);
if (matchIndex != -1)
{
// Pattern matched at pKernelLogBuffer[matchIndex]
}
}Callers can override the auto-bypass heuristic by explicitly specifying an execution engine:
// Force scalar GPR execution (guarantees zero XSTATE context saving)
int idxScalar = searchEngine.Find(pBuffer, cchLen, CKmStrSearch16::SearchEngine::Scalar);
// Explicitly execute AVX2 (automatically wraps KeSaveExtendedProcessorState)
int idxAVX2 = searchEngine.Find(pBuffer, cchLen, CKmStrSearch16::SearchEngine::AVX2);Native MSVC 2026 solution and project files are provided in the repository to build both the test kernel driver and the user mode test suite code.
⚠️ Performance Caveat: While the user-mode (TestUm) test suite code is provided for convenience and logic verification, its performance metrics cannot be trusted to reflect true system capabilities. The search engines, specifically the dynamic SIMD/SWAR routing logic and state-saving bypasses, are designed exclusively for the Windows kernel architecture. User-mode environments handle thread context switching and vector register preservation entirely differently. Always refer to the KmStrSearchDrv.sys kernel driver telemetry for accurate Ring 0 performance evaluations.
This project is licensed under the MIT License. See the LICENSE file for details.
| Back | FazBrowse Home | New Git URL |