← Back to C-Kernel-Engine Docs Doxygen Source Documentation
 
Loading...
Searching...
No Matches
ck_threadpool.c
Go to the documentation of this file.
1#ifndef _GNU_SOURCE
2#define _GNU_SOURCE
3#endif
4
5/**
6 * @file ck_threadpool.c
7 * @brief Persistent pthread thread pool for CK-Engine inference
8 *
9 * Architecture:
10 * - N-1 worker pthreads created at startup, main thread is thread 0
11 * - Workers spin on atomic dispatch counter waiting for work
12 * - Barriers use atomic counter + spin-wait with _mm_pause()
13 * - Hybrid polling: spin CK_THREADPOOL_SPIN_COUNT rounds, then condvar
14 * - All atomics on separate cache lines to avoid false sharing
15 *
16 * Based on the ggml_threadpool design from llama.cpp, adapted for
17 * CK-Engine's kernel dispatch model.
18 */
19
20#include "ck_threadpool.h"
21
22#include <stdlib.h>
23#include <string.h>
24#include <stdio.h>
25#include <errno.h>
26#include <unistd.h>
27#include <time.h>
28#ifdef __linux__
29#include <sched.h>
30#endif
31
32#ifdef __x86_64__
33#include <immintrin.h>
34#define CK_SPIN_PAUSE() _mm_pause()
35#else
36#define CK_SPIN_PAUSE() ((void)0)
37#endif
38
40
41int ck_set_gemm_schedule(int policy)
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}
49
51{
52 return atomic_load_explicit(&g_gemm_schedule, memory_order_acquire);
53}
54
56{
57 const int policy = ck_get_gemm_schedule();
58 return policy == CK_GEMM_SCHEDULE_AUTO || policy == CK_GEMM_SCHEDULE_DYNAMIC;
59}
60
61/* ============================================================================
62 * Internal Structures (cache-line aligned)
63 * ============================================================================ */
64
65/** Per-worker state */
66typedef struct {
67 pthread_t thread;
68 int id; /* 0 = main, 1..n-1 = workers */
69 struct ck_threadpool *pool;
70} ck_worker_t;
71
72/** Barrier state — all fields on separate cache lines */
73typedef struct {
74 _Alignas(CK_CACHE_LINE) atomic_int n_arrived;
75 _Alignas(CK_CACHE_LINE) atomic_int n_phase;
76 int n_threads;
77 char _pad[CK_CACHE_LINE - sizeof(int)];
78} ck_barrier_t;
79
80/** Thread pool (opaque) */
81struct ck_threadpool {
82 /* Dispatch state — cache-line aligned */
83 _Alignas(CK_CACHE_LINE) atomic_int n_dispatch; /* bumped to wake workers */
84 _Alignas(CK_CACHE_LINE) atomic_int n_complete; /* workers signal completion */
85 _Alignas(CK_CACHE_LINE) atomic_int active_threads; /* active threads for current dispatch */
86 _Alignas(CK_CACHE_LINE) ck_work_fn_t work_fn; /* current work function */
87 void *work_args; /* current work arguments */
88
89 /* Barrier for intra-dispatch synchronization */
90 ck_barrier_t barrier;
91
92 /* Worker management */
93 int n_threads; /* worker capacity (including main) */
94 int default_threads; /* ordinary dispatch width */
95 ck_worker_t workers[CK_THREADPOOL_MAX_THREADS];
96
97 /* Shutdown / pause signals */
98 _Alignas(CK_CACHE_LINE) atomic_int stop;
99 _Alignas(CK_CACHE_LINE) atomic_int paused;
100
101 /* Condvar for sleep/wake (hybrid polling) */
102 pthread_mutex_t mutex;
103 pthread_cond_t cond_dispatch; /* workers wait here when sleeping */
104 pthread_cond_t cond_done; /* main waits here for completion */
105
106 _Alignas(CK_CACHE_LINE) atomic_int profile_enabled;
107 atomic_uint_fast64_t profile_dispatch_count;
108 atomic_uint_fast64_t profile_dispatch_total_ns;
109 atomic_uint_fast64_t profile_main_work_ns;
110 atomic_uint_fast64_t profile_completion_wait_ns;
111};
112
113static uint64_t monotonic_ns(void)
114{
115 struct timespec now;
116 clock_gettime(CLOCK_MONOTONIC, &now);
117 return (uint64_t)now.tv_sec * UINT64_C(1000000000) + (uint64_t)now.tv_nsec;
118}
119
120/* ============================================================================
121 * Barrier Implementation
122 * ============================================================================ */
123
124static void barrier_init(ck_barrier_t *b, int n_threads)
125{
126 atomic_store(&b->n_arrived, 0);
127 atomic_store(&b->n_phase, 0);
128 b->n_threads = n_threads;
129}
130
131/**
132 * Spin-wait barrier. All threads must call this.
133 * Uses phase counter to allow re-use without reset.
134 */
135static void barrier_wait(ck_barrier_t *b)
136{
137 const int n = b->n_threads;
138 const int phase = atomic_load_explicit(&b->n_phase, memory_order_relaxed);
139
140 if (atomic_fetch_add_explicit(&b->n_arrived, 1, memory_order_acq_rel) == n - 1) {
141 /* Last thread to arrive — reset and advance phase */
142 atomic_store_explicit(&b->n_arrived, 0, memory_order_relaxed);
143 atomic_store_explicit(&b->n_phase, phase + 1, memory_order_release);
144 } else {
145 /* Spin until phase advances */
146 int spins = 0;
147 while (atomic_load_explicit(&b->n_phase, memory_order_acquire) == phase) {
149 spins++;
150 /* After many spins, yield to avoid wasting CPU on oversubscribed systems */
151 if (spins > CK_THREADPOOL_SPIN_COUNT * 16) {
152 sched_yield();
153 spins = 0;
154 }
155 }
156 }
157}
158
159/* ============================================================================
160 * Worker Thread
161 * ============================================================================ */
162
163static void *worker_main(void *arg)
164{
165 ck_worker_t *w = (ck_worker_t *)arg;
166 ck_threadpool_t *pool = w->pool;
167 const int ith = w->id;
168 int last_dispatch = 0;
169
170 for (;;) {
171 /* Spin-wait for new dispatch */
172 int spins = 0;
173 int active = 0;
174 ck_work_fn_t fn = NULL;
175 void *args = NULL;
176 for (;;) {
177 /* Check shutdown */
178 if (atomic_load_explicit(&pool->stop, memory_order_acquire)) {
179 return NULL;
180 }
181
182 /* Check for new work */
183 int current = atomic_load_explicit(&pool->n_dispatch, memory_order_acquire);
184 active = atomic_load_explicit(&pool->active_threads, memory_order_acquire);
185 if (current != last_dispatch) {
186 /* Snapshot the epoch and descriptor together. An inactive
187 * worker may still be catching up with an older dispatch. */
188 pthread_mutex_lock(&pool->mutex);
189 current = atomic_load_explicit(&pool->n_dispatch, memory_order_acquire);
190 active = atomic_load_explicit(&pool->active_threads, memory_order_acquire);
191 if (current != last_dispatch) {
192 last_dispatch = current;
193 if (ith < active) {
194 fn = pool->work_fn;
195 args = pool->work_args;
196 pthread_mutex_unlock(&pool->mutex);
197 break;
198 }
199 }
200 pthread_mutex_unlock(&pool->mutex);
201 spins = 0;
202 }
203
204 /* Threads outside the active subset sleep instead of spinning. */
205 if (ith >= active || spins >= CK_THREADPOOL_SPIN_COUNT) {
206 pthread_mutex_lock(&pool->mutex);
207 for (;;) {
208 if (atomic_load_explicit(&pool->stop, memory_order_acquire)) {
209 pthread_mutex_unlock(&pool->mutex);
210 return NULL;
211 }
212 current = atomic_load_explicit(&pool->n_dispatch, memory_order_acquire);
213 active = atomic_load_explicit(&pool->active_threads, memory_order_acquire);
214 if (current != last_dispatch) {
215 last_dispatch = current;
216 if (ith < active) {
217 fn = pool->work_fn;
218 args = pool->work_args;
219 pthread_mutex_unlock(&pool->mutex);
220 goto worker_have_work;
221 }
222 }
223 pthread_cond_wait(&pool->cond_dispatch, &pool->mutex);
224 }
225 }
226
228 spins++;
229 }
230
231worker_have_work:
232 /* Execute work */
233 if (fn) {
234 fn(ith, active, args);
235 }
236
237 /* Signal completion */
238 if (atomic_fetch_add_explicit(&pool->n_complete, 1, memory_order_acq_rel)
239 == active - 2) {
240 /* Last worker done — wake main thread if it's waiting */
241 pthread_mutex_lock(&pool->mutex);
242 pthread_cond_signal(&pool->cond_done);
243 pthread_mutex_unlock(&pool->mutex);
244 }
245 }
246
247 return NULL;
248}
249
250/* ============================================================================
251 * Lifecycle
252 * ============================================================================ */
253
254extern int ck_get_physical_cores(void);
255
256int ck_threadpool_bounded_capacity(int default_threads, int logical_threads)
257{
258 if (default_threads < 1) default_threads = 1;
259 if (default_threads > CK_THREADPOOL_MAX_THREADS) {
260 default_threads = CK_THREADPOOL_MAX_THREADS;
261 }
262 if (logical_threads <= default_threads) return default_threads;
263
264 int capacity = default_threads + (logical_threads - default_threads) / 2;
265 if (capacity > CK_THREADPOOL_MAX_THREADS) {
266 capacity = CK_THREADPOOL_MAX_THREADS;
267 }
268 return capacity;
269}
270
271ck_threadpool_t *ck_threadpool_create_capacity(int default_threads,
272 int capacity_threads)
273{
274 if (default_threads <= 0) {
275 default_threads = ck_get_physical_cores();
276 if (default_threads <= 0) default_threads = 1;
277 /* Cap at reasonable default for memory-bound workloads */
278 if (default_threads > 8) default_threads = 8;
279 }
280 if (capacity_threads < default_threads) {
281 capacity_threads = default_threads;
282 }
283 if (capacity_threads > CK_THREADPOOL_MAX_THREADS) {
284 capacity_threads = CK_THREADPOOL_MAX_THREADS;
285 }
286 if (default_threads > capacity_threads) {
287 default_threads = capacity_threads;
288 }
289
290 ck_threadpool_t *pool = aligned_alloc(CK_CACHE_LINE, sizeof(ck_threadpool_t));
291 if (!pool) return NULL;
292 memset(pool, 0, sizeof(*pool));
293
294 pool->n_threads = capacity_threads;
295 pool->default_threads = default_threads;
296 atomic_store(&pool->n_dispatch, 0);
297 atomic_store(&pool->n_complete, 0);
298 atomic_store(&pool->active_threads, default_threads);
299 atomic_store(&pool->stop, 0);
300 atomic_store(&pool->paused, 0);
301 atomic_store(&pool->profile_enabled, 0);
302 pool->work_fn = NULL;
303 pool->work_args = NULL;
304
305 barrier_init(&pool->barrier, default_threads);
306
307 pthread_mutex_init(&pool->mutex, NULL);
308 pthread_cond_init(&pool->cond_dispatch, NULL);
309 pthread_cond_init(&pool->cond_done, NULL);
310
311 /* Thread 0 = main thread (no pthread created) */
312 pool->workers[0].id = 0;
313 pool->workers[0].pool = pool;
314 pool->workers[0].thread = pthread_self();
315
316 /* Spawn N-1 worker threads */
317 for (int i = 1; i < capacity_threads; i++) {
318 pool->workers[i].id = i;
319 pool->workers[i].pool = pool;
320
321 int rc = pthread_create(&pool->workers[i].thread, NULL,
322 worker_main, &pool->workers[i]);
323 if (rc != 0) {
324 fprintf(stderr, "[CK threadpool] Failed to create worker %d: %s\n",
325 i, strerror(rc));
326 /* Reduce thread count to what we managed to create */
327 pool->n_threads = i;
328 barrier_init(&pool->barrier, i);
329 break;
330 }
331 }
332
333 if (pool->n_threads > 1) {
334 fprintf(stderr,
335 "[CK threadpool] Created %d threads (default=%d, 1 main + %d workers)\n",
336 pool->n_threads, pool->default_threads, pool->n_threads - 1);
337 }
338
339 return pool;
340}
341
342ck_threadpool_t *ck_threadpool_create(int n_threads)
343{
344 return ck_threadpool_create_capacity(n_threads, n_threads);
345}
346
347void ck_threadpool_destroy(ck_threadpool_t *pool)
348{
349 if (!pool) return;
350
351 /* Signal shutdown */
352 atomic_store_explicit(&pool->stop, 1, memory_order_release);
353
354 /* Wake all sleeping workers */
355 pthread_mutex_lock(&pool->mutex);
356 pthread_cond_broadcast(&pool->cond_dispatch);
357 pthread_mutex_unlock(&pool->mutex);
358
359 /* Join all worker threads */
360 for (int i = 1; i < pool->n_threads; i++) {
361 pthread_join(pool->workers[i].thread, NULL);
362 }
363
364 pthread_cond_destroy(&pool->cond_dispatch);
365 pthread_cond_destroy(&pool->cond_done);
366 pthread_mutex_destroy(&pool->mutex);
367
368 free(pool);
369}
370
371/* ============================================================================
372 * Dispatch & Synchronization
373 * ============================================================================ */
374
375void ck_threadpool_dispatch_n(ck_threadpool_t *pool, int active_threads, ck_work_fn_t fn, void *args)
376{
377 if (!pool || !fn) return;
378 if (active_threads <= 0) {
379 active_threads = 1;
380 }
381 if (active_threads > pool->n_threads) {
382 active_threads = pool->n_threads;
383 }
384
385 const int profile = atomic_load_explicit(
386 &pool->profile_enabled, memory_order_relaxed);
387 const uint64_t dispatch_start = profile ? monotonic_ns() : 0;
388
389 /* Single-thread fast path: just call directly */
390 if (active_threads == 1 || pool->n_threads == 1) {
391 fn(0, 1, args);
392 if (profile) {
393 const uint64_t dispatch_end = monotonic_ns();
394 atomic_fetch_add_explicit(&pool->profile_dispatch_count, 1, memory_order_relaxed);
395 atomic_fetch_add_explicit(
396 &pool->profile_dispatch_total_ns,
397 dispatch_end - dispatch_start,
398 memory_order_relaxed);
399 atomic_fetch_add_explicit(
400 &pool->profile_main_work_ns,
401 dispatch_end - dispatch_start,
402 memory_order_relaxed);
403 }
404 return;
405 }
406
407 /* Reset barrier phase for this dispatch */
408 barrier_init(&pool->barrier, active_threads);
409
410 /* Set work descriptor */
411 pthread_mutex_lock(&pool->mutex);
412 pool->work_fn = fn;
413 pool->work_args = args;
414 atomic_store_explicit(&pool->active_threads, active_threads, memory_order_release);
415 atomic_store_explicit(&pool->n_complete, 0, memory_order_release);
416
417 /* Wake workers by bumping dispatch counter */
418 atomic_fetch_add_explicit(&pool->n_dispatch, 1, memory_order_release);
419
420 /* Also signal condvar for sleeping workers */
421 pthread_cond_broadcast(&pool->cond_dispatch);
422 pthread_mutex_unlock(&pool->mutex);
423
424 /* Main thread (ith=0) does its share */
425 const uint64_t main_start = profile ? monotonic_ns() : 0;
426 fn(0, active_threads, args);
427 const uint64_t main_end = profile ? monotonic_ns() : 0;
428
429 /* Wait for all workers to complete */
430 if (active_threads > 1) {
431 int spins = 0;
432 while (atomic_load_explicit(&pool->n_complete, memory_order_acquire)
433 < active_threads - 1) {
435 spins++;
436 if (spins >= CK_THREADPOOL_SPIN_COUNT) {
437 pthread_mutex_lock(&pool->mutex);
438 if (atomic_load_explicit(&pool->n_complete, memory_order_acquire)
439 < active_threads - 1) {
440 pthread_cond_wait(&pool->cond_done, &pool->mutex);
441 }
442 pthread_mutex_unlock(&pool->mutex);
443 spins = 0;
444 }
445 }
446 }
447 if (profile) {
448 const uint64_t dispatch_end = monotonic_ns();
449 atomic_fetch_add_explicit(&pool->profile_dispatch_count, 1, memory_order_relaxed);
450 atomic_fetch_add_explicit(
451 &pool->profile_dispatch_total_ns,
452 dispatch_end - dispatch_start,
453 memory_order_relaxed);
454 atomic_fetch_add_explicit(
455 &pool->profile_main_work_ns,
456 main_end - main_start,
457 memory_order_relaxed);
458 atomic_fetch_add_explicit(
459 &pool->profile_completion_wait_ns,
460 dispatch_end - main_end,
461 memory_order_relaxed);
462 }
463}
464
465void ck_threadpool_dispatch(ck_threadpool_t *pool, ck_work_fn_t fn, void *args)
466{
467 if (!pool) return;
468 ck_threadpool_dispatch_n(pool, pool->default_threads, fn, args);
469}
470
471typedef struct {
472 _Alignas(CK_CACHE_LINE) atomic_int next;
473 int end;
474 int grain_size;
475 ck_range_fn_t fn;
476 void *args;
477} ck_parallel_for_work_t;
478
479static void ck_parallel_for_worker(int ith, int nth, void *opaque)
480{
481 (void)ith;
482 (void)nth;
483 ck_parallel_for_work_t *work = (ck_parallel_for_work_t *)opaque;
484 for (;;) {
485 const int begin = atomic_fetch_add_explicit(
486 &work->next, work->grain_size, memory_order_relaxed);
487 if (begin >= work->end) break;
488 int end = begin + work->grain_size;
489 if (end > work->end) end = work->end;
490 work->fn(begin, end, work->args);
491 }
492}
493
494void ck_threadpool_parallel_for_n(ck_threadpool_t *pool,
495 int active_threads,
496 int begin,
497 int end,
498 int grain_size,
499 ck_range_fn_t fn,
500 void *args)
501{
502 if (!fn || begin >= end) return;
503 if (grain_size <= 0) grain_size = 1;
504 if (!pool || active_threads <= 1) {
505 fn(begin, end, args);
506 return;
507 }
508
509 ck_parallel_for_work_t work = {
510 .end = end,
511 .grain_size = grain_size,
512 .fn = fn,
513 .args = args,
514 };
515 atomic_init(&work.next, begin);
517 pool, active_threads, ck_parallel_for_worker, &work);
518}
519
520void ck_threadpool_barrier(ck_threadpool_t *pool)
521{
522 if (!pool || pool->n_threads <= 1) return;
523 barrier_wait(&pool->barrier);
524}
525
526/* ============================================================================
527 * Power Management
528 * ============================================================================ */
529
530void ck_threadpool_pause(ck_threadpool_t *pool)
531{
532 if (!pool) return;
533 atomic_store_explicit(&pool->paused, 1, memory_order_release);
534}
535
536void ck_threadpool_resume(ck_threadpool_t *pool)
537{
538 if (!pool) return;
539 atomic_store_explicit(&pool->paused, 0, memory_order_release);
540
541 /* Wake sleeping workers */
542 pthread_mutex_lock(&pool->mutex);
543 pthread_cond_broadcast(&pool->cond_dispatch);
544 pthread_mutex_unlock(&pool->mutex);
545}
546
547/* ============================================================================
548 * Queries
549 * ============================================================================ */
550
551int ck_threadpool_n_threads(const ck_threadpool_t *pool)
552{
553 return pool ? pool->default_threads : 1;
554}
555
556int ck_threadpool_capacity(const ck_threadpool_t *pool)
557{
558 return pool ? pool->n_threads : 1;
559}
560
561int ck_threadpool_thread_id(const ck_threadpool_t *pool)
562{
563 if (!pool) return -1;
564 pthread_t self = pthread_self();
565 for (int i = 0; i < pool->n_threads; i++) {
566 if (pthread_equal(self, pool->workers[i].thread)) {
567 return i;
568 }
569 }
570 return -1;
571}
572
573void ck_threadpool_profile_reset(ck_threadpool_t *pool)
574{
575 if (!pool) return;
576 atomic_store_explicit(&pool->profile_dispatch_count, 0, memory_order_relaxed);
577 atomic_store_explicit(&pool->profile_dispatch_total_ns, 0, memory_order_relaxed);
578 atomic_store_explicit(&pool->profile_main_work_ns, 0, memory_order_relaxed);
579 atomic_store_explicit(&pool->profile_completion_wait_ns, 0, memory_order_relaxed);
580 atomic_store_explicit(&pool->profile_enabled, 1, memory_order_release);
581}
582
584 const ck_threadpool_t *pool, ck_threadpool_profile_t *profile)
585{
586 if (!profile) return;
587 memset(profile, 0, sizeof(*profile));
588 if (!pool) return;
589 profile->dispatch_count = atomic_load_explicit(
590 &pool->profile_dispatch_count, memory_order_relaxed);
591 profile->dispatch_total_ns = atomic_load_explicit(
592 &pool->profile_dispatch_total_ns, memory_order_relaxed);
593 profile->main_work_ns = atomic_load_explicit(
594 &pool->profile_main_work_ns, memory_order_relaxed);
595 profile->completion_wait_ns = atomic_load_explicit(
596 &pool->profile_completion_wait_ns, memory_order_relaxed);
597}
598
599/* ============================================================================
600 * Global Thread Pool
601 * ============================================================================ */
602
603static ck_threadpool_t *g_threadpool = NULL;
604static pthread_once_t g_threadpool_once = PTHREAD_ONCE_INIT;
605
606extern int ck_get_num_threads(void);
607
609{
610#ifdef __linux__
611 cpu_set_t allowed;
612 if (sched_getaffinity(0, sizeof(allowed), &allowed) == 0) {
613 int count = 0;
614 for (int cpu = 0; cpu < CPU_SETSIZE; ++cpu) {
615 if (CPU_ISSET(cpu, &allowed)) ++count;
616 }
617 if (count > 0) return count;
618 }
619#endif
620 const long online = sysconf(_SC_NPROCESSORS_ONLN);
621 return online > 0 ? (int)online : 1;
622}
623
624static void global_pool_init(void)
625{
626 const int available_threads = ck_available_logical_cpus();
627 int physical_threads = ck_get_physical_cores();
628 if (physical_threads > available_threads) physical_threads = available_threads;
629 int default_threads = ck_get_num_threads();
630 if (default_threads > available_threads) {
631 default_threads = available_threads;
632 }
633 int capacity_threads = default_threads;
634 const char *capacity_env = getenv("CK_THREADPOOL_CAPACITY");
635 if (capacity_env && atoi(capacity_env) > 0) {
636 capacity_threads = atoi(capacity_env);
637 } else if (!getenv("CK_NUM_THREADS") &&
638 default_threads == physical_threads) {
639 /* Generated runtimes set OMP_NUM_THREADS=1 to keep OpenMP dormant and
640 * configure the CK pool separately. Do not mistake that isolation
641 * setting for a CK capacity cap. A non-physical default remains an
642 * explicit width and does not gain automatic SMT workers. */
643 capacity_threads = ck_threadpool_bounded_capacity(
644 default_threads, available_threads);
645 }
646 if (capacity_threads > available_threads) capacity_threads = available_threads;
648 default_threads, capacity_threads);
649}
650
651ck_threadpool_t *ck_threadpool_global(void)
652{
653 pthread_once(&g_threadpool_once, global_pool_init);
654 return g_threadpool;
655}
656
658{
659 if (g_threadpool) {
661 g_threadpool = NULL;
662 /* Reset once control so pool can be re-created if needed */
663 g_threadpool_once = PTHREAD_ONCE_INIT;
664 }
665}
void ck_threadpool_pause(ck_threadpool_t *pool)
int ck_threadpool_capacity(const ck_threadpool_t *pool)
static void barrier_init(ck_barrier_t *b, int n_threads)
static void barrier_wait(ck_barrier_t *b)
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_profile_reset(ck_threadpool_t *pool)
void ck_threadpool_resume(ck_threadpool_t *pool)
static int ck_available_logical_cpus(void)
int ck_set_gemm_schedule(int policy)
static pthread_once_t g_threadpool_once
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)
static void global_pool_init(void)
void ck_threadpool_destroy(ck_threadpool_t *pool)
void ck_threadpool_barrier(ck_threadpool_t *pool)
void ck_threadpool_profile_snapshot(const ck_threadpool_t *pool, ck_threadpool_profile_t *profile)
int ck_get_physical_cores(void)
static void ck_parallel_for_worker(int ith, int nth, void *opaque)
static ck_threadpool_t * g_threadpool
ck_threadpool_t * ck_threadpool_create(int n_threads)
ck_threadpool_t * ck_threadpool_create_capacity(int default_threads, int capacity_threads)
static uint64_t monotonic_ns(void)
int ck_gemm_dynamic_schedule_enabled(void)
static void * worker_main(void *arg)
void ck_threadpool_dispatch(ck_threadpool_t *pool, ck_work_fn_t fn, void *args)
int ck_threadpool_bounded_capacity(int default_threads, int logical_threads)
int ck_threadpool_thread_id(const ck_threadpool_t *pool)
#define CK_SPIN_PAUSE()
int ck_get_gemm_schedule(void)
static atomic_int g_gemm_schedule
int ck_get_num_threads(void)
int ck_threadpool_n_threads(const ck_threadpool_t *pool)
Persistent pthread thread pool for CK-Engine inference.
@ CK_GEMM_SCHEDULE_AUTO
@ CK_GEMM_SCHEDULE_DYNAMIC
#define CK_THREADPOOL_MAX_THREADS
#define CK_CACHE_LINE
#define CK_THREADPOOL_SPIN_COUNT
void(* ck_range_fn_t)(int begin, int end, void *args)
void(* ck_work_fn_t)(int ith, int nth, void *args)
int32_t id
Definition tokenizer.h:316
uint32_t end
Definition utf8.c:215