CPU Thread Affinity
This page explains how CPU thread affinity currently works in hlquery.
What Affinity Means
CPU thread affinity pins a thread to one or more CPU cores so the scheduler keeps that thread on a specific core set. In hlquery this is used to reduce cache misses and keep high-throughput worker paths stable.
Where It Is Used
Affinity is used in two places:
- Search thread pools (
SearchThreadPool) insrc/common/SearchThreadPool.cpp - Socket I/O worker threads (
SocketEngine::IOWorkerThread) insrc/socketengines/epoll.cpp
Pool-by-Pool Core Selection
CPUAffinityManager::GetOptimalCoresForPool() chooses cores by pool type:
HTTP_REQUEST: first half of available CPU coresSEARCH: second half of available CPU coresWRITE: core0and optionally core1MANAGEMENT: core0(management pool affinity is disabled by default)
This logic is currently simple and based on std::thread::hardware_concurrency().
Thread Pool Behavior
When pools are initialized, affinity is enabled for:
- HTTP pool
- Search pool
- Write pool
Affinity is disabled for:
- Management pool
Each pool stores a vector of candidate cores via SetCPUAffinity(...). Worker threads are then assigned cores in round-robin order:
cpu_core = CPUCores[i % CPUCores.size()]
The same round-robin rule is used when scaling up pool threads.
Linux-Only Details
Affinity calls are compiled only on Linux (#if defined(__linux__)), using:
pthread_setaffinity_np(...)for thread affinitysched_setaffinity(...)for process affinity (available inCPUAffinityManager)
On non-Linux platforms, affinity calls are no-ops.
I/O Worker Behavior
SocketEngine::IOWorkerThread() sets affinity inside the worker thread itself:
CPU_SET(WorkerID, &cpuset); pthread_setaffinity_np(pthread_self(), ...)
Because this is called from the worker thread context, the pinned thread is unambiguous.
Current Caveat In Search Thread Pools
In SearchThreadPool::SetThreadAffinity(...) and CPUAffinityManager::SetThreadAffinity(...), the thread_id parameter is currently ignored and pthread_self() is used.
That means affinity is applied to the calling thread, not explicitly to the target std::thread::id. Since pool affinity is applied from management/startup code after thread creation, behavior may not always match the intended per-worker pinning.
In short:
- Intent: pin each pool worker to its assigned core
- Current implementation detail: uses caller thread handle (
pthread_self())
NUMA Notes
NUMA support is currently basic:
- Topology detection assumes a single socket and one NUMA node
EnableNUMAOptimization()is a placeholder switchOptimizeForNUMA()distributes threads using the detected topology model
For multi-socket or complex NUMA hosts, treat current behavior as best-effort rather than strict placement.