← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
ck_threadpool.c File Reference

Persistent pthread thread pool for CK-Engine inference. More...

#include "ck_threadpool.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>

Go to the source code of this file.

Macros

#define _GNU_SOURCE
 
#define CK_SPIN_PAUSE()   ((void)0)
 

Functions

static void barrier_init (ck_barrier_t *b, int n_threads)
 
static void barrier_wait (ck_barrier_t *b)
 
static int ck_available_logical_cpus (void)
 
int ck_gemm_dynamic_schedule_enabled (void)
 
int ck_get_gemm_schedule (void)
 
int ck_get_num_threads (void)
 
int ck_get_physical_cores (void)
 
static void ck_parallel_for_worker (int ith, int nth, void *opaque)
 
int ck_set_gemm_schedule (int policy)
 
void ck_threadpool_barrier (ck_threadpool_t *pool)
 
int ck_threadpool_bounded_capacity (int default_threads, int logical_threads)
 
int ck_threadpool_capacity (const ck_threadpool_t *pool)
 
ck_threadpool_t * ck_threadpool_create (int n_threads)
 
ck_threadpool_t * ck_threadpool_create_capacity (int default_threads, int capacity_threads)
 
void ck_threadpool_destroy (ck_threadpool_t *pool)
 
void ck_threadpool_dispatch (ck_threadpool_t *pool, ck_work_fn_t fn, void *args)
 
void ck_threadpool_dispatch_n (ck_threadpool_t *pool, int active_threads, ck_work_fn_t fn, void *args)
 
ck_threadpool_t * ck_threadpool_global (void)
 
void ck_threadpool_global_destroy (void)
 
int ck_threadpool_n_threads (const ck_threadpool_t *pool)
 
void ck_threadpool_parallel_for_n (ck_threadpool_t *pool, int active_threads, int begin, int end, int grain_size, ck_range_fn_t fn, void *args)
 
void ck_threadpool_pause (ck_threadpool_t *pool)
 
void ck_threadpool_profile_reset (ck_threadpool_t *pool)
 
void ck_threadpool_profile_snapshot (const ck_threadpool_t *pool, ck_threadpool_profile_t *profile)
 
void ck_threadpool_resume (ck_threadpool_t *pool)
 
int ck_threadpool_thread_id (const ck_threadpool_t *pool)
 
static void global_pool_init (void)
 
static uint64_t monotonic_ns (void)
 
static void run_work (ck_threadpool_t *pool, ck_work_fn_t fn, int ith, int nth, void *args)
 
static void * worker_main (void *arg)
 

Variables

static _Thread_local ck_dispatch_context_t g_dispatch_context
 
static atomic_int g_gemm_schedule = CK_GEMM_SCHEDULE_AUTO
 
static ck_threadpool_t * g_threadpool = NULL
 
static pthread_once_t g_threadpool_once = PTHREAD_ONCE_INIT
 

Detailed Description

Persistent pthread thread pool for CK-Engine inference.

Architecture:

  • N-1 worker pthreads created at startup, calling thread executes slot 0
  • Workers spin on atomic dispatch counter waiting for work
  • Barriers use atomic counter + spin-wait with _mm_pause()
  • Hybrid polling: spin CK_THREADPOOL_SPIN_COUNT rounds, then condvar
  • All atomics on separate cache lines to avoid false sharing

Based on the ggml_threadpool design from llama.cpp, adapted for CK-Engine's kernel dispatch model.

Definition in file ck_threadpool.c.

Macro Definition Documentation

◆ _GNU_SOURCE

#define _GNU_SOURCE

Definition at line 2 of file ck_threadpool.c.

◆ CK_SPIN_PAUSE

#define CK_SPIN_PAUSE ( )    ((void)0)

Definition at line 36 of file ck_threadpool.c.

Function Documentation

◆ barrier_init()

static void barrier_init ( ck_barrier_t *  b,
int  n_threads 
)
static

Definition at line 145 of file ck_threadpool.c.

146{
147 atomic_store(&b->n_arrived, 0);
148 atomic_store(&b->n_phase, 0);
149 b->n_threads = n_threads;
150}

Referenced by ck_threadpool_create_capacity(), and ck_threadpool_dispatch_n().

◆ barrier_wait()

static void barrier_wait ( ck_barrier_t *  b)
static

Spin-wait barrier. All threads must call this. Uses phase counter to allow re-use without reset.

Definition at line 156 of file ck_threadpool.c.

157{
158 const int n = b->n_threads;
159 const int phase = atomic_load_explicit(&b->n_phase, memory_order_relaxed);
160
161 if (atomic_fetch_add_explicit(&b->n_arrived, 1, memory_order_acq_rel) == n - 1) {
162 /* Last thread to arrive — reset and advance phase */
163 atomic_store_explicit(&b->n_arrived, 0, memory_order_relaxed);
164 atomic_store_explicit(&b->n_phase, phase + 1, memory_order_release);
165 } else {
166 /* Spin until phase advances */
167 int spins = 0;
168 while (atomic_load_explicit(&b->n_phase, memory_order_acquire) == phase) {
170 spins++;
171 /* After many spins, yield to avoid wasting CPU on oversubscribed systems */
172 if (spins > CK_THREADPOOL_SPIN_COUNT * 16) {
173 sched_yield();
174 spins = 0;
175 }
176 }
177 }
178}
#define CK_SPIN_PAUSE()
#define CK_THREADPOOL_SPIN_COUNT

References CK_SPIN_PAUSE, and CK_THREADPOOL_SPIN_COUNT.

Referenced by ck_threadpool_barrier().

◆ ck_available_logical_cpus()

static int ck_available_logical_cpus ( void  )
static

Definition at line 642 of file ck_threadpool.c.

643{
644#ifdef __linux__
645 cpu_set_t allowed;
646 if (sched_getaffinity(0, sizeof(allowed), &allowed) == 0) {
647 int count = 0;
648 for (int cpu = 0; cpu < CPU_SETSIZE; ++cpu) {
649 if (CPU_ISSET(cpu, &allowed)) ++count;
650 }
651 if (count > 0) return count;
652 }
653#endif
654 const long online = sysconf(_SC_NPROCESSORS_ONLN);
655 return online > 0 ? (int)online : 1;
656}
const uint8_t size_t count
Definition true_bpe.h:209

References count.

Referenced by global_pool_init().

◆ ck_gemm_dynamic_schedule_enabled()

int ck_gemm_dynamic_schedule_enabled ( void  )

Return non-zero when independent GEMM tiles should use dynamic claiming.

Definition at line 55 of file ck_threadpool.c.

56{
57 const int policy = ck_get_gemm_schedule();
58 return policy == CK_GEMM_SCHEDULE_AUTO || policy == CK_GEMM_SCHEDULE_DYNAMIC;
59}
int ck_get_gemm_schedule(void)
@ CK_GEMM_SCHEDULE_AUTO
@ CK_GEMM_SCHEDULE_DYNAMIC

References CK_GEMM_SCHEDULE_AUTO, CK_GEMM_SCHEDULE_DYNAMIC, and ck_get_gemm_schedule().

Referenced by gemm_nt_q4_k_packed_vnni_x8_q8_k_split_min_threaded_4m().

◆ ck_get_gemm_schedule()

int ck_get_gemm_schedule ( void  )

Return the configured process-wide GEMM scheduling policy.

Definition at line 50 of file ck_threadpool.c.

51{
52 return atomic_load_explicit(&g_gemm_schedule, memory_order_acquire);
53}
static atomic_int g_gemm_schedule

References g_gemm_schedule.

Referenced by ck_gemm_dynamic_schedule_enabled().

◆ ck_get_num_threads()

int ck_get_num_threads ( void  )
extern

Definition at line 310 of file ckernel_strict.c.

311{
312 // Auto-initialize if not set
314 ck_set_num_threads(0); // Auto-detect
315 }
316 return g_num_threads;
317}
void ck_set_num_threads(int num_threads)
static int g_num_threads
static int g_threads_initialized

Referenced by global_pool_init().

◆ ck_get_physical_cores()

int ck_get_physical_cores ( void  )
extern

Definition at line 170 of file ckernel_strict.c.

171{
172 int physical_cores = 0;
173 int logical_cores = (int)sysconf(_SC_NPROCESSORS_ONLN);
174 if (logical_cores <= 0) {
175 logical_cores = 1;
176 }
177
178 int cpu_cores_hint = 0;
179 int siblings_hint = 0;
180
181 // Read from /proc/cpuinfo (Linux) and count unique (physical id, core id) pairs.
182 FILE *f = fopen("/proc/cpuinfo", "r");
183 if (f) {
184 char line[256];
185 int physical_id = -1;
186 int core_id = -1;
187
188 struct {
189 int physical_id;
190 int core_id;
191 } seen[8192];
192 int seen_count = 0;
193
194 const int seen_cap = (int)(sizeof(seen) / sizeof(seen[0]));
195
196 // Helper: add (pid,cid) to set if not present.
197 #define CK_ADD_PAIR(pid, cid) \
198 do { \
199 if ((pid) >= 0 && (cid) >= 0) { \
200 int exists = 0; \
201 for (int ii = 0; ii < seen_count; ++ii) { \
202 if (seen[ii].physical_id == (pid) && \
203 seen[ii].core_id == (cid)) { \
204 exists = 1; \
205 break; \
206 } \
207 } \
208 if (!exists && seen_count < seen_cap) { \
209 seen[seen_count].physical_id = (pid); \
210 seen[seen_count].core_id = (cid); \
211 ++seen_count; \
212 } \
213 } \
214 } while (0)
215
216 while (fgets(line, sizeof(line), f)) {
217 int val;
218
219 // Blank line separates processor blocks.
220 if (line[0] == '\n' || line[0] == '\0') {
221 CK_ADD_PAIR(physical_id, core_id);
222 physical_id = -1;
223 core_id = -1;
224 continue;
225 }
226
227 if (sscanf(line, "physical id : %d", &val) == 1) {
228 physical_id = val;
229 continue;
230 }
231 if (sscanf(line, "core id : %d", &val) == 1) {
232 core_id = val;
233 continue;
234 }
235 if (sscanf(line, "cpu cores : %d", &val) == 1) {
236 if (val > cpu_cores_hint) cpu_cores_hint = val;
237 continue;
238 }
239 if (sscanf(line, "siblings : %d", &val) == 1) {
240 if (val > siblings_hint) siblings_hint = val;
241 continue;
242 }
243 }
244 fclose(f);
245
246 // Handle file without trailing blank line.
247 CK_ADD_PAIR(physical_id, core_id);
248
249 #undef CK_ADD_PAIR
250
251 physical_cores = seen_count;
252 }
253
254 // Fallback: infer threads-per-core from siblings/cpu cores when pair data
255 // is missing (common in containers/VMs).
256 if (physical_cores <= 1 && logical_cores > 1) {
257 int threads_per_core = 0;
258 if (siblings_hint > 0 && cpu_cores_hint > 0 && siblings_hint >= cpu_cores_hint) {
259 threads_per_core = siblings_hint / cpu_cores_hint;
260 }
261 if (threads_per_core > 1) {
262 int inferred_physical = logical_cores / threads_per_core;
263 if (inferred_physical > 1) {
264 return inferred_physical;
265 }
266 }
267 if (cpu_cores_hint > 1 && cpu_cores_hint <= logical_cores) {
268 return cpu_cores_hint;
269 }
270 return logical_cores;
271 }
272
273 if (physical_cores > 1) {
274 return physical_cores;
275 }
276
277 return logical_cores;
278}
#define CK_ADD_PAIR(pid, cid)

Referenced by ck_set_num_threads(), ck_threadpool_create_capacity(), and global_pool_init().

◆ ck_parallel_for_worker()

static void ck_parallel_for_worker ( int  ith,
int  nth,
void *  opaque 
)
static

Definition at line 508 of file ck_threadpool.c.

509{
510 (void)ith;
511 (void)nth;
512 ck_parallel_for_work_t *work = (ck_parallel_for_work_t *)opaque;
513 for (;;) {
514 const int begin = atomic_fetch_add_explicit(
515 &work->next, work->grain_size, memory_order_relaxed);
516 if (begin >= work->end) break;
517 int end = begin + work->grain_size;
518 if (end > work->end) end = work->end;
519 work->fn(begin, end, work->args);
520 }
521}
uint32_t end
Definition utf8.c:215

References end.

Referenced by ck_threadpool_parallel_for_n().

◆ ck_set_gemm_schedule()

int ck_set_gemm_schedule ( int  policy)

Set the process-wide GEMM tile scheduling policy.

AUTO is the production default and currently selects dynamic work claiming for providers whose jobs write independent output tiles. Providers with ordered/shared reductions do not consult this policy.

Returns
0 on success, -1 for an invalid policy.

Definition at line 41 of file ck_threadpool.c.

42{
43 if (policy < CK_GEMM_SCHEDULE_AUTO || policy > CK_GEMM_SCHEDULE_DYNAMIC) {
44 return -1;
45 }
46 atomic_store_explicit(&g_gemm_schedule, policy, memory_order_release);
47 return 0;
48}

References CK_GEMM_SCHEDULE_DYNAMIC, and g_gemm_schedule.

◆ ck_threadpool_barrier()

void ck_threadpool_barrier ( ck_threadpool_t *  pool)

Barrier synchronization within a dispatched work function.

ALL participants in the current dispatch must call this at the same point. A single-worker dispatch returns immediately, even if the pool has more workers or another caller is executing a multi-worker dispatch.

Must only be called from within a work function (during dispatch).

Parameters
poolThread pool

Definition at line 549 of file ck_threadpool.c.

550{
551 /* Only participants in this multi-worker dispatch use its barrier.
552 * In particular, an overlapping single-worker callback must never touch
553 * the shared barrier belonging to a different dispatch. */
554 if (!pool || g_dispatch_context.pool != pool ||
555 g_dispatch_context.nth <= 1) return;
556 barrier_wait(&pool->barrier);
557}
static void barrier_wait(ck_barrier_t *b)
static _Thread_local ck_dispatch_context_t g_dispatch_context

References barrier_wait(), and g_dispatch_context.

◆ ck_threadpool_bounded_capacity()

int ck_threadpool_bounded_capacity ( int  default_threads,
int  logical_threads 
)

Compute the bounded capacity for an SMT-safe provider. The default width is preserved and at most half of the additional logical CPUs are reserved.

Definition at line 277 of file ck_threadpool.c.

278{
279 if (default_threads < 1) default_threads = 1;
280 if (default_threads > CK_THREADPOOL_MAX_THREADS) {
281 default_threads = CK_THREADPOOL_MAX_THREADS;
282 }
283 if (logical_threads <= default_threads) return default_threads;
284
285 int capacity = default_threads + (logical_threads - default_threads) / 2;
286 if (capacity > CK_THREADPOOL_MAX_THREADS) {
287 capacity = CK_THREADPOOL_MAX_THREADS;
288 }
289 return capacity;
290}
#define CK_THREADPOOL_MAX_THREADS

References CK_THREADPOOL_MAX_THREADS.

Referenced by global_pool_init().

◆ ck_threadpool_capacity()

int ck_threadpool_capacity ( const ck_threadpool_t *  pool)

Get the maximum worker capacity available to explicit dispatch_n calls.

Definition at line 589 of file ck_threadpool.c.

590{
591 return pool ? pool->n_threads : 1;
592}

Referenced by gemm_nt_q4_k_packed_vnni_x16_q8_k_split_min_threaded_16m(), and gemm_nt_q4_k_packed_vnni_x8_q8_k_split_min_threaded_4m().

◆ ck_threadpool_create()

ck_threadpool_t * ck_threadpool_create ( int  n_threads)

Create a thread pool with n_threads total threads. Thread 0 is the calling thread; n_threads-1 workers are spawned.

Parameters
n_threadsTotal thread count (including main). Must be >= 1. Pass 0 for auto-detect (physical cores).
Returns
Pool handle, or NULL on failure.

Definition at line 364 of file ck_threadpool.c.

365{
366 return ck_threadpool_create_capacity(n_threads, n_threads);
367}
ck_threadpool_t * ck_threadpool_create_capacity(int default_threads, int capacity_threads)

References ck_threadpool_create_capacity().

◆ ck_threadpool_create_capacity()

ck_threadpool_t * ck_threadpool_create_capacity ( int  default_threads,
int  capacity_threads 
)

Create a pool whose ordinary dispatch width is smaller than its worker capacity. Exact providers may opt into the additional workers with ck_threadpool_dispatch_n(); ordinary dispatch remains at default_threads.

Definition at line 292 of file ck_threadpool.c.

294{
295 if (default_threads <= 0) {
296 default_threads = ck_get_physical_cores();
297 if (default_threads <= 0) default_threads = 1;
298 /* Cap at reasonable default for memory-bound workloads */
299 if (default_threads > 8) default_threads = 8;
300 }
301 if (capacity_threads < default_threads) {
302 capacity_threads = default_threads;
303 }
304 if (capacity_threads > CK_THREADPOOL_MAX_THREADS) {
305 capacity_threads = CK_THREADPOOL_MAX_THREADS;
306 }
307 if (default_threads > capacity_threads) {
308 default_threads = capacity_threads;
309 }
310
311 ck_threadpool_t *pool = aligned_alloc(CK_CACHE_LINE, sizeof(ck_threadpool_t));
312 if (!pool) return NULL;
313 memset(pool, 0, sizeof(*pool));
314
315 pool->n_threads = capacity_threads;
316 pool->default_threads = default_threads;
317 atomic_store(&pool->n_dispatch, 0);
318 atomic_store(&pool->n_complete, 0);
319 atomic_store(&pool->active_threads, default_threads);
320 atomic_store(&pool->stop, 0);
321 atomic_store(&pool->paused, 0);
322 atomic_store(&pool->profile_enabled, 0);
323 pool->work_fn = NULL;
324 pool->work_args = NULL;
325
326 barrier_init(&pool->barrier, default_threads);
327
328 pthread_mutex_init(&pool->dispatch_mutex, NULL);
329 pthread_mutex_init(&pool->mutex, NULL);
330 pthread_cond_init(&pool->cond_dispatch, NULL);
331 pthread_cond_init(&pool->cond_done, NULL);
332
333 /* Slot 0 runs on the dispatch caller (no worker pthread created). */
334 pool->workers[0].id = 0;
335 pool->workers[0].pool = pool;
336 pool->workers[0].thread = pthread_self();
337
338 /* Spawn N-1 worker threads */
339 for (int i = 1; i < capacity_threads; i++) {
340 pool->workers[i].id = i;
341 pool->workers[i].pool = pool;
342
343 int rc = pthread_create(&pool->workers[i].thread, NULL,
344 worker_main, &pool->workers[i]);
345 if (rc != 0) {
346 fprintf(stderr, "[CK threadpool] Failed to create worker %d: %s\n",
347 i, strerror(rc));
348 /* Reduce thread count to what we managed to create */
349 pool->n_threads = i;
350 barrier_init(&pool->barrier, i);
351 break;
352 }
353 }
354
355 if (pool->n_threads > 1) {
356 fprintf(stderr,
357 "[CK threadpool] Created %d threads (default=%d, 1 main + %d workers)\n",
358 pool->n_threads, pool->default_threads, pool->n_threads - 1);
359 }
360
361 return pool;
362}
static void barrier_init(ck_barrier_t *b, int n_threads)
int ck_get_physical_cores(void)
static void * worker_main(void *arg)
#define CK_CACHE_LINE

References barrier_init(), CK_CACHE_LINE, ck_get_physical_cores(), CK_THREADPOOL_MAX_THREADS, and worker_main().

Referenced by ck_threadpool_create(), and global_pool_init().

◆ ck_threadpool_destroy()

void ck_threadpool_destroy ( ck_threadpool_t *  pool)

Destroy the thread pool. Signals all workers to exit and joins them. Safe to call with NULL.

Definition at line 369 of file ck_threadpool.c.

370{
371 if (!pool) return;
372
373 /* Signal shutdown */
374 atomic_store_explicit(&pool->stop, 1, memory_order_release);
375
376 /* Wake all sleeping workers */
377 pthread_mutex_lock(&pool->mutex);
378 pthread_cond_broadcast(&pool->cond_dispatch);
379 pthread_mutex_unlock(&pool->mutex);
380
381 /* Join all worker threads */
382 for (int i = 1; i < pool->n_threads; i++) {
383 pthread_join(pool->workers[i].thread, NULL);
384 }
385
386 pthread_cond_destroy(&pool->cond_dispatch);
387 pthread_cond_destroy(&pool->cond_done);
388 pthread_mutex_destroy(&pool->mutex);
389 pthread_mutex_destroy(&pool->dispatch_mutex);
390
391 free(pool);
392}

Referenced by ck_threadpool_global_destroy().

◆ ck_threadpool_dispatch()

void ck_threadpool_dispatch ( ck_threadpool_t *  pool,
ck_work_fn_t  fn,
void *  args 
)

Dispatch work to all threads and wait for completion.

  1. Sets the work function and args
  2. Bumps the dispatch counter (wakes workers)
  3. Main thread (ith=0) executes its share
  4. Waits for all worker callbacks to complete

This is a blocking call — returns when ALL threads have finished. Concurrent multi-worker dispatches on the same pool are serialized. Single-worker calls may execute concurrently because they do not publish a shared work descriptor. Do not destroy a pool while a call is active or recursively dispatch the same pool from one of its work functions.

Parameters
poolThread pool
fnWork function (called on each thread)
argsArgument passed to fn

Definition at line 494 of file ck_threadpool.c.

495{
496 if (!pool) return;
497 ck_threadpool_dispatch_n(pool, pool->default_threads, fn, args);
498}
void ck_threadpool_dispatch_n(ck_threadpool_t *pool, int active_threads, ck_work_fn_t fn, void *args)

References ck_threadpool_dispatch_n().

◆ ck_threadpool_dispatch_n()

void ck_threadpool_dispatch_n ( ck_threadpool_t *  pool,
int  active_threads,
ck_work_fn_t  fn,
void *  args 
)

Dispatch work to a subset of the pool and wait for completion.

Threads with ith >= active_threads remain idle for this dispatch. The work function sees nth == active_threads.

Parameters
poolThread pool
active_threadsNumber of active threads including the calling thread
fnWork function
argsArgument passed to fn

Definition at line 398 of file ck_threadpool.c.

399{
400 if (!pool || !fn) return;
401 if (active_threads <= 0) {
402 active_threads = 1;
403 }
404 if (active_threads > pool->n_threads) {
405 active_threads = pool->n_threads;
406 }
407
408 const int profile = atomic_load_explicit(
409 &pool->profile_enabled, memory_order_relaxed);
410 const uint64_t dispatch_start = profile ? monotonic_ns() : 0;
411
412 /* Single-thread fast path: just call directly */
413 if (active_threads == 1 || pool->n_threads == 1) {
414 run_work(pool, fn, 0, 1, args);
415 if (profile) {
416 const uint64_t dispatch_end = monotonic_ns();
417 atomic_fetch_add_explicit(&pool->profile_dispatch_count, 1, memory_order_relaxed);
418 atomic_fetch_add_explicit(
419 &pool->profile_dispatch_total_ns,
420 dispatch_end - dispatch_start,
421 memory_order_relaxed);
422 atomic_fetch_add_explicit(
423 &pool->profile_main_work_ns,
424 dispatch_end - dispatch_start,
425 memory_order_relaxed);
426 }
427 return;
428 }
429
430 /* The work descriptor and completion counter belong to one dispatch.
431 * Concurrent callers may share a pool, but their multi-worker dispatches
432 * must not overwrite one another's descriptor or barrier state. */
433 pthread_mutex_lock(&pool->dispatch_mutex);
434
435 /* Reset barrier phase for this dispatch */
436 barrier_init(&pool->barrier, active_threads);
437
438 /* Set work descriptor */
439 pthread_mutex_lock(&pool->mutex);
440 pool->work_fn = fn;
441 pool->work_args = args;
442 atomic_store_explicit(&pool->active_threads, active_threads, memory_order_release);
443 atomic_store_explicit(&pool->n_complete, 0, memory_order_release);
444
445 /* Wake workers by bumping dispatch counter */
446 atomic_fetch_add_explicit(&pool->n_dispatch, 1, memory_order_release);
447
448 /* Also signal condvar for sleeping workers */
449 pthread_cond_broadcast(&pool->cond_dispatch);
450 pthread_mutex_unlock(&pool->mutex);
451
452 /* Main thread (ith=0) does its share */
453 const uint64_t main_start = profile ? monotonic_ns() : 0;
454 run_work(pool, fn, 0, active_threads, args);
455 const uint64_t main_end = profile ? monotonic_ns() : 0;
456
457 /* Wait for all workers to complete */
458 if (active_threads > 1) {
459 int spins = 0;
460 while (atomic_load_explicit(&pool->n_complete, memory_order_acquire)
461 < active_threads - 1) {
463 spins++;
464 if (spins >= CK_THREADPOOL_SPIN_COUNT) {
465 pthread_mutex_lock(&pool->mutex);
466 if (atomic_load_explicit(&pool->n_complete, memory_order_acquire)
467 < active_threads - 1) {
468 pthread_cond_wait(&pool->cond_done, &pool->mutex);
469 }
470 pthread_mutex_unlock(&pool->mutex);
471 spins = 0;
472 }
473 }
474 }
475 if (profile) {
476 const uint64_t dispatch_end = monotonic_ns();
477 atomic_fetch_add_explicit(&pool->profile_dispatch_count, 1, memory_order_relaxed);
478 atomic_fetch_add_explicit(
479 &pool->profile_dispatch_total_ns,
480 dispatch_end - dispatch_start,
481 memory_order_relaxed);
482 atomic_fetch_add_explicit(
483 &pool->profile_main_work_ns,
484 main_end - main_start,
485 memory_order_relaxed);
486 atomic_fetch_add_explicit(
487 &pool->profile_completion_wait_ns,
488 dispatch_end - main_end,
489 memory_order_relaxed);
490 }
491 pthread_mutex_unlock(&pool->dispatch_mutex);
492}
static uint64_t monotonic_ns(void)
static void run_work(ck_threadpool_t *pool, ck_work_fn_t fn, int ith, int nth, void *args)

References barrier_init(), CK_SPIN_PAUSE, CK_THREADPOOL_SPIN_COUNT, monotonic_ns(), and run_work().

Referenced by adamw_clip_update_multi_f32(), adamw_update_f32(), attention_forward_causal_head_major_gqa_flash_strided_f16kv_workspace(), attention_forward_causal_head_major_gqa_flash_strided_sliding(), attention_forward_causal_head_major_gqa_flash_strided_sliding_gemma4_impl(), attention_forward_causal_head_major_gqa_llama_regular_strided_sliding_workspace(), attention_forward_causal_head_major_gqa_prefill_append_bf16cache_pytorch_contract_workspace(), attention_forward_causal_head_major_gqa_prefill_append_f16cache_gqa_reuse_config(), attention_forward_decode_head_major_gqa_flash_f16cache_split_partitioned(), attention_forward_head_major_gqa_flash_impl(), attention_forward_query_key_head_major_f32_decode_heads(), audio_conv1d_channel_major_grouped_f32(), ck_attention_f16_prefill_qtile64_dispatch(), ck_attention_forward_causal_head_major_gqa_prefill_segmented_f16cache_schedule_workspace(), ck_attention_forward_full_head_major_gqa_tiled_f16kv_fp32_strided(), ck_attention_forward_query_key_head_major_f32_run(), ck_attention_full_bf16_pytorch_flash(), ck_attention_full_bf16_sdpa_tiled(), ck_moe_q4k_mixed_parallel_workspace(), ck_moe_q4k_mixed_route_parallel(), ck_moe_q4k_q5k_route_parallel(), ck_moe_shared_gated_parallel_workspace(), ck_moe_shared_q4k_parallel_workspace(), ck_moe_swiglu_expert_forward_q4k_q5k_bucketed_impl(), ck_threadpool_dispatch(), ck_threadpool_parallel_for_n(), ds_mla_attention_parallel_dispatch(), gelu_erf_fp64_f32_parallel_dispatch(), gemm_backward_f32_train_parallel_dispatch(), gemm_backward_f32_train_parallel_dispatch_v2(), gemm_blocked_serial_train_parallel_dispatch(), gemm_f16_input_fp16_threadpool(), gemm_nt_bf16_amx_bf16_storage_workspace(), gemm_nt_bf16_native_bf16_storage(), gemm_nt_q4_k_packed_meta_q8_k_threaded(), gemm_nt_q4_k_packed_meta_q8_k_threaded_nsplit(), gemm_nt_q4_k_packed_meta_x16_gateup_swiglu_fused_vnni(), gemm_nt_q4_k_packed_meta_x16_q8_k_threaded_mreuse(), gemm_nt_q4_k_packed_meta_x16_q8_k_threaded_mtile(), gemm_nt_q4_k_packed_meta_x8_q8_k_split_min_threaded_4m(), gemm_nt_q4_k_packed_meta_x8_q8_k_split_min_threaded_8m(), gemm_nt_q4_k_packed_meta_x8_q8_k_split_min_threaded_mreuse(), gemm_nt_q4_k_packed_meta_x8_q8_k_threaded_mreuse(), gemm_nt_q4_k_packed_meta_x8_q8_k_threaded_mtile(), gemm_nt_q4_k_packed_meta_x8_q8_k_threaded_nsplit(), gemm_nt_q4_k_packed_u8_x16_q8_k_threaded_mtile(), gemm_nt_q4_k_packed_vnni_x16_q8_k_split_min_threaded_16m(), gemm_nt_q4_k_packed_vnni_x8_q8_k_split_min_threaded_4m(), gemm_nt_q4_k_q8_k_gateup_swiglu_fused_vnni(), gemm_q4_k_q8_k(), gradient_accumulate_f32(), gradient_accumulate_multi_f32(), gradient_clip_norm_f32(), gradient_global_norm_multi_f32(), gradient_scale_f32(), moe_swiglu_expert_forward_q4k_q5k_parallel_workspace(), and patch_projection_image_bf16_native_storage().

◆ ck_threadpool_global()

ck_threadpool_t * ck_threadpool_global ( void  )

Get or create the global thread pool. Thread-safe (uses pthread_once internally). Uses ck_get_num_threads() for the default width. In automatic mode the pool may reserve a bounded subset of SMT siblings as explicit provider capacity; ordinary dispatch remains at the default width.

Returns
Global pool, never NULL after successful first call.

Definition at line 685 of file ck_threadpool.c.

686{
687 pthread_once(&g_threadpool_once, global_pool_init);
688 return g_threadpool;
689}
static pthread_once_t g_threadpool_once
static void global_pool_init(void)
static ck_threadpool_t * g_threadpool

References g_threadpool, g_threadpool_once, and global_pool_init().

Referenced by adamw_clip_update_multi_f32(), adamw_update_f32(), attention_forward_causal_head_major_gqa_flash_strided_f16kv_workspace(), attention_forward_causal_head_major_gqa_flash_strided_sliding(), attention_forward_causal_head_major_gqa_flash_strided_sliding_gemma4_impl(), attention_forward_causal_head_major_gqa_llama_regular_strided_sliding_workspace(), attention_forward_causal_head_major_gqa_prefill_append_bf16cache_pytorch_contract_workspace(), attention_forward_causal_head_major_gqa_prefill_append_f16cache_auto_workspace(), attention_forward_causal_head_major_gqa_prefill_append_f16cache_gqa_reuse_config(), attention_forward_decode_head_major_gqa_flash_f16cache_split_partitioned(), attention_forward_head_major_gqa_flash_impl(), attention_forward_query_key_head_major_f32_decode_heads(), attn_gate_sigmoid_mul_forward(), audio_conformer_relative_attention_f32(), audio_conv1d_channel_major_grouped_f32(), audio_conv2d_whc_grouped_f32(), audio_fastconformer_block_workspace_bytes(), audio_glu_split_channel_major_f32(), audio_relative_shift_f32(), ck_attention_f16_prefill_qtile64_dispatch(), ck_attention_forward_causal_head_major_gqa_prefill_segmented_f16cache_schedule_workspace(), ck_attention_forward_full_head_major_gqa_tiled_f16kv_fp32_strided(), ck_attention_forward_query_key_head_major_f32_run(), ck_attention_full_bf16_pytorch_flash(), ck_attention_full_bf16_sdpa_tiled(), ck_gemma4_prepare_parallel(), ck_get_threadpool(), ck_moe_q4k_mixed_parallel_workspace(), ck_moe_shared_gated_parallel_workspace(), ck_moe_shared_q4k_parallel_workspace(), ck_moe_swiglu_expert_forward_q4k_q5k_bucketed_impl(), ck_threadpool_init(), deepseek_mla_kv_decompress_bf16_parallel_dispatch(), ds_mla_attention_parallel_dispatch(), gelu_erf_fp64_f32_parallel_dispatch(), gemm_backward_f32_train_parallel_dispatch(), gemm_backward_f32_train_parallel_dispatch_v2(), gemm_blocked_serial_train_parallel_dispatch(), gemm_f16_input_fp16_threadpool(), gemm_nt_bf16_accurate_storage_parallel_dispatch(), gemm_nt_bf16_amx_bf16_storage_workspace(), gemm_nt_bf16_bf16_storage_parallel_dispatch(), gemm_nt_bf16_native_bf16_storage(), gemm_nt_bf16_parallel_dispatch(), gemm_nt_fp32_exact_parallel_dispatch(), gemm_nt_q4_k_packed_meta_q8_k_threaded(), gemm_nt_q4_k_packed_meta_q8_k_threaded_nsplit(), gemm_nt_q4_k_packed_meta_x16_gateup_swiglu_fused_vnni(), gemm_nt_q4_k_packed_meta_x16_q8_k_threaded_mreuse(), gemm_nt_q4_k_packed_meta_x16_q8_k_threaded_mtile(), gemm_nt_q4_k_packed_meta_x8_q8_k_split_min_threaded_4m(), gemm_nt_q4_k_packed_meta_x8_q8_k_split_min_threaded_8m(), gemm_nt_q4_k_packed_meta_x8_q8_k_split_min_threaded_mreuse(), gemm_nt_q4_k_packed_meta_x8_q8_k_threaded_mreuse(), gemm_nt_q4_k_packed_meta_x8_q8_k_threaded_mtile(), gemm_nt_q4_k_packed_meta_x8_q8_k_threaded_nsplit(), gemm_nt_q4_k_packed_u8_x16_q8_k_threaded_mtile(), gemm_nt_q4_k_packed_vnni_x16_q8_k_split_min_threaded_16m(), gemm_nt_q4_k_packed_vnni_x8_q8_k_split_min_threaded_4m(), gemm_nt_q4_k_q8_k_gateup_swiglu_fused_vnni(), gemm_q4_k_q8_k(), gemma4_per_layer_embed_forward(), gemv_bf16_bf16_storage_parallel_dispatch(), gemv_bf16_parallel_dispatch(), gemv_nvfp4_q8_0_uniform(), gradient_accumulate_f32(), gradient_accumulate_multi_f32(), gradient_clip_norm_f32(), gradient_global_norm_multi_f32(), gradient_scale_f32(), moe_swiglu_expert_forward_q4k_q5k_parallel_workspace(), patch_projection_image_bf16_native_storage(), recurrent_conv_state_update_forward(), recurrent_qk_l2_norm_forward(), recurrent_silu_forward_ggml(), ssm_conv1d_forward_llama_fma(), ssm_conv1d_forward_llama_production(), swiglu_forward_exact(), and swiglu_forward_ggml().

◆ ck_threadpool_global_destroy()

void ck_threadpool_global_destroy ( void  )

Destroy the global thread pool. Called during engine shutdown.

Definition at line 691 of file ck_threadpool.c.

692{
693 if (g_threadpool) {
695 g_threadpool = NULL;
696 /* Reset once control so pool can be re-created if needed */
697 g_threadpool_once = PTHREAD_ONCE_INIT;
698 }
699}
void ck_threadpool_destroy(ck_threadpool_t *pool)

References ck_threadpool_destroy(), g_threadpool, and g_threadpool_once.

Referenced by ck_threadpool_shutdown().

◆ ck_threadpool_n_threads()

int ck_threadpool_n_threads ( const ck_threadpool_t *  pool)

Get the ordinary/default dispatch width (including the calling thread).

Definition at line 584 of file ck_threadpool.c.

585{
586 return pool ? pool->default_threads : 1;
587}

Referenced by adamw_clip_update_multi_f32(), adamw_update_f32(), attention_forward_causal_head_major_gqa_flash_strided_f16kv_workspace(), attention_forward_causal_head_major_gqa_llama_regular_strided_sliding_workspace(), attention_forward_causal_head_major_gqa_prefill_append_bf16cache_pytorch_contract_workspace(), attention_forward_causal_head_major_gqa_prefill_append_f16cache_auto_workspace(), attention_forward_causal_head_major_gqa_prefill_append_f16cache_gqa_reuse_config(), attention_forward_decode_head_major_gqa_flash_f16cache_split_partitioned(), attention_forward_query_key_head_major_f32_decode_heads(), attn_gate_sigmoid_mul_forward(), audio_conformer_relative_attention_f32(), audio_conv1d_channel_major_grouped_f32(), audio_conv2d_whc_grouped_f32(), audio_fastconformer_block_workspace_bytes(), audio_glu_split_channel_major_f32(), audio_relative_shift_f32(), ck_attention_f16_prefill_qtile64_dispatch(), ck_attention_forward_causal_head_major_gqa_prefill_segmented_f16cache_schedule_workspace(), ck_attention_forward_full_head_major_gqa_tiled_f16kv_fp32_strided(), ck_attention_forward_query_key_head_major_f32_run(), ck_attention_full_bf16_pytorch_flash(), ck_attention_full_bf16_sdpa_tiled(), ck_attention_pick_active_threads(), ck_gemm_f16_pick_active_threads(), ck_gemma4_prepare_parallel(), ck_moe_q4k_mixed_parallel_workspace(), ck_moe_q4k_mixed_route_parallel(), ck_moe_q4k_q5k_route_parallel(), ck_moe_shared_gated_parallel_workspace(), ck_moe_shared_q4k_parallel_workspace(), ck_moe_swiglu_expert_forward_q4k_q5k_bucketed_impl(), ck_sliding_attention_pick_threads(), deepseek_mla_kv_decompress_bf16_parallel_dispatch(), ds_mla_attention_parallel_dispatch(), gelu_erf_fp64_f32_parallel_dispatch(), gemm_backward_f32_train_parallel_dispatch(), gemm_backward_f32_train_parallel_dispatch_v2(), gemm_blocked_serial_train_parallel_dispatch(), gemm_nt_bf16_accurate_storage_parallel_dispatch(), gemm_nt_bf16_amx_bf16_storage_workspace(), gemm_nt_bf16_bf16_storage_parallel_dispatch(), gemm_nt_bf16_native_bf16_storage(), gemm_nt_bf16_parallel_dispatch(), gemm_nt_fp32_exact_parallel_dispatch(), gemm_nt_q4_k_packed_meta_q8_k_threaded(), gemm_nt_q4_k_packed_meta_q8_k_threaded_nsplit(), gemm_nt_q4_k_packed_meta_x16_gateup_swiglu_fused_vnni(), gemm_nt_q4_k_packed_meta_x16_q8_k_threaded_mreuse(), gemm_nt_q4_k_packed_meta_x16_q8_k_threaded_mtile(), gemm_nt_q4_k_packed_meta_x8_q8_k_split_min_threaded_4m(), gemm_nt_q4_k_packed_meta_x8_q8_k_split_min_threaded_8m(), gemm_nt_q4_k_packed_meta_x8_q8_k_split_min_threaded_mreuse(), gemm_nt_q4_k_packed_meta_x8_q8_k_threaded_mreuse(), gemm_nt_q4_k_packed_meta_x8_q8_k_threaded_mtile(), gemm_nt_q4_k_packed_meta_x8_q8_k_threaded_nsplit(), gemm_nt_q4_k_packed_u8_x16_q8_k_threaded_mtile(), gemm_nt_q4_k_q8_k_gateup_swiglu_fused_vnni(), gemm_q4_k_q8_k(), gemma4_per_layer_embed_forward(), gemv_bf16_bf16_storage_parallel_dispatch(), gemv_bf16_parallel_dispatch(), gemv_nvfp4_q8_0_uniform(), gradient_accumulate_f32(), gradient_accumulate_multi_f32(), gradient_clip_norm_f32(), gradient_global_norm_multi_f32(), gradient_scale_f32(), moe_swiglu_expert_forward_q4k_q5k_parallel_workspace(), patch_projection_image_bf16_native_storage(), recurrent_conv_state_update_forward(), recurrent_qk_l2_norm_forward(), recurrent_silu_forward_ggml(), ssm_conv1d_forward_llama_fma(), ssm_conv1d_forward_llama_production(), swiglu_forward_exact(), and swiglu_forward_ggml().

◆ ck_threadpool_parallel_for_n()

void ck_threadpool_parallel_for_n ( ck_threadpool_t *  pool,
int  active_threads,
int  begin,
int  end,
int  grain_size,
ck_range_fn_t  fn,
void *  args 
)

Dynamically distribute independent ranges through the persistent pool.

Workers claim grain_size consecutive indices until [begin, end) is empty. This changes ownership only; callers remain responsible for ensuring that ranges write disjoint outputs and preserve each output's reduction order. Do not use this helper for unordered shared reductions.

Definition at line 523 of file ck_threadpool.c.

530{
531 if (!fn || begin >= end) return;
532 if (grain_size <= 0) grain_size = 1;
533 if (!pool || active_threads <= 1) {
534 fn(begin, end, args);
535 return;
536 }
537
538 ck_parallel_for_work_t work = {
539 .end = end,
540 .grain_size = grain_size,
541 .fn = fn,
542 .args = args,
543 };
544 atomic_init(&work.next, begin);
546 pool, active_threads, ck_parallel_for_worker, &work);
547}
static void ck_parallel_for_worker(int ith, int nth, void *opaque)

References ck_parallel_for_worker(), ck_threadpool_dispatch_n(), and end.

Referenced by attn_gate_sigmoid_mul_forward(), audio_conformer_relative_attention_f32(), audio_conv2d_whc_grouped_f32(), audio_glu_split_channel_major_f32(), audio_relative_shift_f32(), ck_gemma4_prepare_parallel(), ck_swiglu_dispatch_rows(), deepseek_mla_kv_decompress_bf16_parallel_dispatch(), gemm_nt_bf16_accurate_storage_parallel_dispatch(), gemm_nt_bf16_bf16_storage_parallel_dispatch(), gemm_nt_bf16_parallel_dispatch(), gemm_nt_fp32_exact_parallel_dispatch(), gemm_nt_q4_k_packed_vnni_x8_q8_k_split_min_threaded_4m(), gemma4_per_layer_embed_forward(), gemv_bf16_bf16_storage_parallel_dispatch(), gemv_bf16_parallel_dispatch(), gemv_nvfp4_q8_0_uniform(), recurrent_conv_state_update_forward(), recurrent_qk_l2_norm_forward(), recurrent_silu_forward_ggml(), ssm_conv1d_forward_llama_fma(), and ssm_conv1d_forward_llama_production().

◆ ck_threadpool_pause()

void ck_threadpool_pause ( ck_threadpool_t *  pool)

Pause workers — they sleep on condvar (0% CPU). Call between batches or during interactive waiting. Workers wake on next dispatch or resume.

Definition at line 563 of file ck_threadpool.c.

564{
565 if (!pool) return;
566 atomic_store_explicit(&pool->paused, 1, memory_order_release);
567}

◆ ck_threadpool_profile_reset()

void ck_threadpool_profile_reset ( ck_threadpool_t *  pool)

Enable profiling and reset cumulative dispatch timing counters.

Definition at line 607 of file ck_threadpool.c.

608{
609 if (!pool) return;
610 atomic_store_explicit(&pool->profile_dispatch_count, 0, memory_order_relaxed);
611 atomic_store_explicit(&pool->profile_dispatch_total_ns, 0, memory_order_relaxed);
612 atomic_store_explicit(&pool->profile_main_work_ns, 0, memory_order_relaxed);
613 atomic_store_explicit(&pool->profile_completion_wait_ns, 0, memory_order_relaxed);
614 atomic_store_explicit(&pool->profile_enabled, 1, memory_order_release);
615}

◆ ck_threadpool_profile_snapshot()

void ck_threadpool_profile_snapshot ( const ck_threadpool_t *  pool,
ck_threadpool_profile_t *  profile 
)

Snapshot cumulative dispatch timing counters without stopping workers.

Definition at line 617 of file ck_threadpool.c.

619{
620 if (!profile) return;
621 memset(profile, 0, sizeof(*profile));
622 if (!pool) return;
623 profile->dispatch_count = atomic_load_explicit(
624 &pool->profile_dispatch_count, memory_order_relaxed);
625 profile->dispatch_total_ns = atomic_load_explicit(
626 &pool->profile_dispatch_total_ns, memory_order_relaxed);
627 profile->main_work_ns = atomic_load_explicit(
628 &pool->profile_main_work_ns, memory_order_relaxed);
629 profile->completion_wait_ns = atomic_load_explicit(
630 &pool->profile_completion_wait_ns, memory_order_relaxed);
631}

References ck_threadpool_profile_t::completion_wait_ns, ck_threadpool_profile_t::dispatch_count, ck_threadpool_profile_t::dispatch_total_ns, and ck_threadpool_profile_t::main_work_ns.

◆ ck_threadpool_resume()

void ck_threadpool_resume ( ck_threadpool_t *  pool)

Resume workers — transition from sleep to spin-wait. Call before starting a new batch of work.

Definition at line 569 of file ck_threadpool.c.

570{
571 if (!pool) return;
572 atomic_store_explicit(&pool->paused, 0, memory_order_release);
573
574 /* Wake sleeping workers */
575 pthread_mutex_lock(&pool->mutex);
576 pthread_cond_broadcast(&pool->cond_dispatch);
577 pthread_mutex_unlock(&pool->mutex);
578}

◆ ck_threadpool_thread_id()

int ck_threadpool_thread_id ( const ck_threadpool_t *  pool)

Get the current callback's ith for this pool. Outside a callback, return the physical worker index (or the pool creator's legacy index 0), and -1 for an unrelated thread. The current dispatch caller is always index 0, including callers that did not create the pool.

Definition at line 594 of file ck_threadpool.c.

595{
596 if (!pool) return -1;
597 if (g_dispatch_context.pool == pool) return g_dispatch_context.ith;
598 pthread_t self = pthread_self();
599 for (int i = 0; i < pool->n_threads; i++) {
600 if (pthread_equal(self, pool->workers[i].thread)) {
601 return i;
602 }
603 }
604 return -1;
605}

References g_dispatch_context.

Referenced by attention_forward_causal_head_major_gqa_prefill_append_bf16cache_pytorch_contract_workspace(), attention_forward_causal_head_major_gqa_prefill_append_f16cache_gqa_reuse_config(), attn_gate_sigmoid_mul_forward(), ck_attention_f16_prefill_qtile64_dispatch(), ck_attention_forward_causal_head_major_gqa_prefill_segmented_f16cache_schedule_workspace(), ck_audio_conformer_relative_attention_f32_row_range(), ck_sliding_attention_pick_threads(), ck_swiglu_dispatch_rows(), gelu_erf_fp64_f32_parallel_dispatch(), recurrent_conv_state_update_forward(), recurrent_qk_l2_norm_forward(), and recurrent_silu_forward_ggml().

◆ global_pool_init()

static void global_pool_init ( void  )
static

Definition at line 658 of file ck_threadpool.c.

659{
660 const int available_threads = ck_available_logical_cpus();
661 int physical_threads = ck_get_physical_cores();
662 if (physical_threads > available_threads) physical_threads = available_threads;
663 int default_threads = ck_get_num_threads();
664 if (default_threads > available_threads) {
665 default_threads = available_threads;
666 }
667 int capacity_threads = default_threads;
668 const char *capacity_env = getenv("CK_THREADPOOL_CAPACITY");
669 if (capacity_env && atoi(capacity_env) > 0) {
670 capacity_threads = atoi(capacity_env);
671 } else if (!getenv("CK_NUM_THREADS") &&
672 default_threads == physical_threads) {
673 /* Generated runtimes set OMP_NUM_THREADS=1 to keep OpenMP dormant and
674 * configure the CK pool separately. Do not mistake that isolation
675 * setting for a CK capacity cap. A non-physical default remains an
676 * explicit width and does not gain automatic SMT workers. */
677 capacity_threads = ck_threadpool_bounded_capacity(
678 default_threads, available_threads);
679 }
680 if (capacity_threads > available_threads) capacity_threads = available_threads;
682 default_threads, capacity_threads);
683}
static int ck_available_logical_cpus(void)
int ck_threadpool_bounded_capacity(int default_threads, int logical_threads)
int ck_get_num_threads(void)

References ck_available_logical_cpus(), ck_get_num_threads(), ck_get_physical_cores(), ck_threadpool_bounded_capacity(), ck_threadpool_create_capacity(), and g_threadpool.

Referenced by ck_threadpool_global().

◆ monotonic_ns()

static uint64_t monotonic_ns ( void  )
static

Definition at line 134 of file ck_threadpool.c.

135{
136 struct timespec now;
137 clock_gettime(CLOCK_MONOTONIC, &now);
138 return (uint64_t)now.tv_sec * UINT64_C(1000000000) + (uint64_t)now.tv_nsec;
139}

Referenced by ck_threadpool_dispatch_n().

◆ run_work()

static void run_work ( ck_threadpool_t *  pool,
ck_work_fn_t  fn,
int  ith,
int  nth,
void *  args 
)
static

Definition at line 125 of file ck_threadpool.c.

127{
128 const ck_dispatch_context_t previous = g_dispatch_context;
129 g_dispatch_context = (ck_dispatch_context_t){pool, ith, nth};
130 fn(ith, nth, args);
131 g_dispatch_context = previous;
132}

References g_dispatch_context.

Referenced by ck_threadpool_dispatch_n(), and worker_main().

◆ worker_main()

static void * worker_main ( void *  arg)
static

Definition at line 184 of file ck_threadpool.c.

185{
186 ck_worker_t *w = (ck_worker_t *)arg;
187 ck_threadpool_t *pool = w->pool;
188 const int ith = w->id;
189 int last_dispatch = 0;
190
191 for (;;) {
192 /* Spin-wait for new dispatch */
193 int spins = 0;
194 int active = 0;
195 ck_work_fn_t fn = NULL;
196 void *args = NULL;
197 for (;;) {
198 /* Check shutdown */
199 if (atomic_load_explicit(&pool->stop, memory_order_acquire)) {
200 return NULL;
201 }
202
203 /* Check for new work */
204 int current = atomic_load_explicit(&pool->n_dispatch, memory_order_acquire);
205 active = atomic_load_explicit(&pool->active_threads, memory_order_acquire);
206 if (current != last_dispatch) {
207 /* Snapshot the epoch and descriptor together. An inactive
208 * worker may still be catching up with an older dispatch. */
209 pthread_mutex_lock(&pool->mutex);
210 current = atomic_load_explicit(&pool->n_dispatch, memory_order_acquire);
211 active = atomic_load_explicit(&pool->active_threads, memory_order_acquire);
212 if (current != last_dispatch) {
213 last_dispatch = current;
214 if (ith < active) {
215 fn = pool->work_fn;
216 args = pool->work_args;
217 pthread_mutex_unlock(&pool->mutex);
218 break;
219 }
220 }
221 pthread_mutex_unlock(&pool->mutex);
222 spins = 0;
223 }
224
225 /* Threads outside the active subset sleep instead of spinning. */
226 if (ith >= active || spins >= CK_THREADPOOL_SPIN_COUNT) {
227 pthread_mutex_lock(&pool->mutex);
228 for (;;) {
229 if (atomic_load_explicit(&pool->stop, memory_order_acquire)) {
230 pthread_mutex_unlock(&pool->mutex);
231 return NULL;
232 }
233 current = atomic_load_explicit(&pool->n_dispatch, memory_order_acquire);
234 active = atomic_load_explicit(&pool->active_threads, memory_order_acquire);
235 if (current != last_dispatch) {
236 last_dispatch = current;
237 if (ith < active) {
238 fn = pool->work_fn;
239 args = pool->work_args;
240 pthread_mutex_unlock(&pool->mutex);
241 goto worker_have_work;
242 }
243 }
244 pthread_cond_wait(&pool->cond_dispatch, &pool->mutex);
245 }
246 }
247
249 spins++;
250 }
251
252worker_have_work:
253 /* Execute work */
254 if (fn) {
255 run_work(pool, fn, ith, active, args);
256 }
257
258 /* Signal completion */
259 if (atomic_fetch_add_explicit(&pool->n_complete, 1, memory_order_acq_rel)
260 == active - 2) {
261 /* Last worker done — wake the calling thread if it is waiting */
262 pthread_mutex_lock(&pool->mutex);
263 pthread_cond_signal(&pool->cond_done);
264 pthread_mutex_unlock(&pool->mutex);
265 }
266 }
267
268 return NULL;
269}
void(* ck_work_fn_t)(int ith, int nth, void *args)

References CK_SPIN_PAUSE, CK_THREADPOOL_SPIN_COUNT, and run_work().

Referenced by ck_threadpool_create_capacity().

Variable Documentation

◆ g_dispatch_context

_Thread_local ck_dispatch_context_t g_dispatch_context
static

Definition at line 123 of file ck_threadpool.c.

Referenced by ck_threadpool_barrier(), ck_threadpool_thread_id(), and run_work().

◆ g_gemm_schedule

atomic_int g_gemm_schedule = CK_GEMM_SCHEDULE_AUTO
static

Definition at line 39 of file ck_threadpool.c.

Referenced by ck_get_gemm_schedule(), and ck_set_gemm_schedule().

◆ g_threadpool

ck_threadpool_t* g_threadpool = NULL
static

◆ g_threadpool_once

pthread_once_t g_threadpool_once = PTHREAD_ONCE_INIT
static

Definition at line 638 of file ck_threadpool.c.

Referenced by ck_threadpool_global(), and ck_threadpool_global_destroy().