34#define CK_SPIN_PAUSE() _mm_pause()
36#define CK_SPIN_PAUSE() ((void)0)
69 struct ck_threadpool *pool;
102 pthread_mutex_t dispatch_mutex;
103 pthread_mutex_t mutex;
104 pthread_cond_t cond_dispatch;
105 pthread_cond_t cond_done;
108 atomic_uint_fast64_t profile_dispatch_count;
109 atomic_uint_fast64_t profile_dispatch_total_ns;
110 atomic_uint_fast64_t profile_main_work_ns;
111 atomic_uint_fast64_t profile_completion_wait_ns;
118 const ck_threadpool_t *pool;
121} ck_dispatch_context_t;
126 int ith,
int nth,
void *args)
137 clock_gettime(CLOCK_MONOTONIC, &now);
138 return (uint64_t)now.tv_sec * UINT64_C(1000000000) + (uint64_t)now.tv_nsec;
147 atomic_store(&b->n_arrived, 0);
148 atomic_store(&b->n_phase, 0);
149 b->n_threads = n_threads;
158 const int n = b->n_threads;
159 const int phase = atomic_load_explicit(&b->n_phase, memory_order_relaxed);
161 if (atomic_fetch_add_explicit(&b->n_arrived, 1, memory_order_acq_rel) == n - 1) {
163 atomic_store_explicit(&b->n_arrived, 0, memory_order_relaxed);
164 atomic_store_explicit(&b->n_phase, phase + 1, memory_order_release);
168 while (atomic_load_explicit(&b->n_phase, memory_order_acquire) == phase) {
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;
199 if (atomic_load_explicit(&pool->stop, memory_order_acquire)) {
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) {
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;
216 args = pool->work_args;
217 pthread_mutex_unlock(&pool->mutex);
221 pthread_mutex_unlock(&pool->mutex);
227 pthread_mutex_lock(&pool->mutex);
229 if (atomic_load_explicit(&pool->stop, memory_order_acquire)) {
230 pthread_mutex_unlock(&pool->mutex);
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;
239 args = pool->work_args;
240 pthread_mutex_unlock(&pool->mutex);
241 goto worker_have_work;
244 pthread_cond_wait(&pool->cond_dispatch, &pool->mutex);
255 run_work(pool, fn, ith, active, args);
259 if (atomic_fetch_add_explicit(&pool->n_complete, 1, memory_order_acq_rel)
262 pthread_mutex_lock(&pool->mutex);
263 pthread_cond_signal(&pool->cond_done);
264 pthread_mutex_unlock(&pool->mutex);
279 if (default_threads < 1) default_threads = 1;
283 if (logical_threads <= default_threads)
return default_threads;
285 int capacity = default_threads + (logical_threads - default_threads) / 2;
293 int capacity_threads)
295 if (default_threads <= 0) {
297 if (default_threads <= 0) default_threads = 1;
299 if (default_threads > 8) default_threads = 8;
301 if (capacity_threads < default_threads) {
302 capacity_threads = default_threads;
307 if (default_threads > capacity_threads) {
308 default_threads = capacity_threads;
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));
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;
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);
334 pool->workers[0].id = 0;
335 pool->workers[0].pool = pool;
336 pool->workers[0].thread = pthread_self();
339 for (
int i = 1; i < capacity_threads; i++) {
340 pool->workers[i].id = i;
341 pool->workers[i].pool = pool;
343 int rc = pthread_create(&pool->workers[i].thread, NULL,
346 fprintf(stderr,
"[CK threadpool] Failed to create worker %d: %s\n",
355 if (pool->n_threads > 1) {
357 "[CK threadpool] Created %d threads (default=%d, 1 main + %d workers)\n",
358 pool->n_threads, pool->default_threads, pool->n_threads - 1);
374 atomic_store_explicit(&pool->stop, 1, memory_order_release);
377 pthread_mutex_lock(&pool->mutex);
378 pthread_cond_broadcast(&pool->cond_dispatch);
379 pthread_mutex_unlock(&pool->mutex);
382 for (
int i = 1; i < pool->n_threads; i++) {
383 pthread_join(pool->workers[i].thread, NULL);
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);
400 if (!pool || !fn)
return;
401 if (active_threads <= 0) {
404 if (active_threads > pool->n_threads) {
405 active_threads = pool->n_threads;
408 const int profile = atomic_load_explicit(
409 &pool->profile_enabled, memory_order_relaxed);
410 const uint64_t dispatch_start = profile ?
monotonic_ns() : 0;
413 if (active_threads == 1 || pool->n_threads == 1) {
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);
433 pthread_mutex_lock(&pool->dispatch_mutex);
439 pthread_mutex_lock(&pool->mutex);
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);
446 atomic_fetch_add_explicit(&pool->n_dispatch, 1, memory_order_release);
449 pthread_cond_broadcast(&pool->cond_dispatch);
450 pthread_mutex_unlock(&pool->mutex);
453 const uint64_t main_start = profile ?
monotonic_ns() : 0;
454 run_work(pool, fn, 0, active_threads, args);
458 if (active_threads > 1) {
460 while (atomic_load_explicit(&pool->n_complete, memory_order_acquire)
461 < active_threads - 1) {
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);
470 pthread_mutex_unlock(&pool->mutex);
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);
491 pthread_mutex_unlock(&pool->dispatch_mutex);
506} ck_parallel_for_work_t;
512 ck_parallel_for_work_t *work = (ck_parallel_for_work_t *)opaque;
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);
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);
538 ck_parallel_for_work_t work = {
540 .grain_size = grain_size,
544 atomic_init(&work.next, begin);
566 atomic_store_explicit(&pool->paused, 1, memory_order_release);
572 atomic_store_explicit(&pool->paused, 0, memory_order_release);
575 pthread_mutex_lock(&pool->mutex);
576 pthread_cond_broadcast(&pool->cond_dispatch);
577 pthread_mutex_unlock(&pool->mutex);
586 return pool ? pool->default_threads : 1;
591 return pool ? pool->n_threads : 1;
596 if (!pool)
return -1;
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)) {
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);
620 if (!profile)
return;
621 memset(profile, 0,
sizeof(*profile));
624 &pool->profile_dispatch_count, memory_order_relaxed);
626 &pool->profile_dispatch_total_ns, memory_order_relaxed);
628 &pool->profile_main_work_ns, memory_order_relaxed);
630 &pool->profile_completion_wait_ns, memory_order_relaxed);
646 if (sched_getaffinity(0,
sizeof(allowed), &allowed) == 0) {
648 for (
int cpu = 0; cpu < CPU_SETSIZE; ++cpu) {
649 if (CPU_ISSET(cpu, &allowed)) ++
count;
654 const long online = sysconf(_SC_NPROCESSORS_ONLN);
655 return online > 0 ? (int)online : 1;
662 if (physical_threads > available_threads) physical_threads = available_threads;
664 if (default_threads > available_threads) {
665 default_threads = available_threads;
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) {
678 default_threads, available_threads);
680 if (capacity_threads > available_threads) capacity_threads = available_threads;
682 default_threads, capacity_threads);
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)
static _Thread_local ck_dispatch_context_t g_dispatch_context
static void run_work(ck_threadpool_t *pool, ck_work_fn_t fn, int ith, int nth, void *args)
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_DYNAMIC
#define CK_THREADPOOL_MAX_THREADS
#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)
uint64_t completion_wait_ns
uint64_t dispatch_total_ns
const uint8_t size_t count