34#define CK_SPIN_PAUSE() _mm_pause()
36#define CK_SPIN_PAUSE() ((void)0)
69 struct ck_threadpool *pool;
102 pthread_mutex_t mutex;
103 pthread_cond_t cond_dispatch;
104 pthread_cond_t cond_done;
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;
116 clock_gettime(CLOCK_MONOTONIC, &now);
117 return (uint64_t)now.tv_sec * UINT64_C(1000000000) + (uint64_t)now.tv_nsec;
126 atomic_store(&b->n_arrived, 0);
127 atomic_store(&b->n_phase, 0);
128 b->n_threads = n_threads;
137 const int n = b->n_threads;
138 const int phase = atomic_load_explicit(&b->n_phase, memory_order_relaxed);
140 if (atomic_fetch_add_explicit(&b->n_arrived, 1, memory_order_acq_rel) == n - 1) {
142 atomic_store_explicit(&b->n_arrived, 0, memory_order_relaxed);
143 atomic_store_explicit(&b->n_phase, phase + 1, memory_order_release);
147 while (atomic_load_explicit(&b->n_phase, memory_order_acquire) == phase) {
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;
178 if (atomic_load_explicit(&pool->stop, memory_order_acquire)) {
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) {
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;
195 args = pool->work_args;
196 pthread_mutex_unlock(&pool->mutex);
200 pthread_mutex_unlock(&pool->mutex);
206 pthread_mutex_lock(&pool->mutex);
208 if (atomic_load_explicit(&pool->stop, memory_order_acquire)) {
209 pthread_mutex_unlock(&pool->mutex);
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;
218 args = pool->work_args;
219 pthread_mutex_unlock(&pool->mutex);
220 goto worker_have_work;
223 pthread_cond_wait(&pool->cond_dispatch, &pool->mutex);
234 fn(ith, active, args);
238 if (atomic_fetch_add_explicit(&pool->n_complete, 1, memory_order_acq_rel)
241 pthread_mutex_lock(&pool->mutex);
242 pthread_cond_signal(&pool->cond_done);
243 pthread_mutex_unlock(&pool->mutex);
258 if (default_threads < 1) default_threads = 1;
262 if (logical_threads <= default_threads)
return default_threads;
264 int capacity = default_threads + (logical_threads - default_threads) / 2;
272 int capacity_threads)
274 if (default_threads <= 0) {
276 if (default_threads <= 0) default_threads = 1;
278 if (default_threads > 8) default_threads = 8;
280 if (capacity_threads < default_threads) {
281 capacity_threads = default_threads;
286 if (default_threads > capacity_threads) {
287 default_threads = capacity_threads;
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));
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;
307 pthread_mutex_init(&pool->mutex, NULL);
308 pthread_cond_init(&pool->cond_dispatch, NULL);
309 pthread_cond_init(&pool->cond_done, NULL);
312 pool->workers[0].id = 0;
313 pool->workers[0].pool = pool;
314 pool->workers[0].thread = pthread_self();
317 for (
int i = 1; i < capacity_threads; i++) {
318 pool->workers[i].id = i;
319 pool->workers[i].pool = pool;
321 int rc = pthread_create(&pool->workers[i].thread, NULL,
324 fprintf(stderr,
"[CK threadpool] Failed to create worker %d: %s\n",
333 if (pool->n_threads > 1) {
335 "[CK threadpool] Created %d threads (default=%d, 1 main + %d workers)\n",
336 pool->n_threads, pool->default_threads, pool->n_threads - 1);
352 atomic_store_explicit(&pool->stop, 1, memory_order_release);
355 pthread_mutex_lock(&pool->mutex);
356 pthread_cond_broadcast(&pool->cond_dispatch);
357 pthread_mutex_unlock(&pool->mutex);
360 for (
int i = 1; i < pool->n_threads; i++) {
361 pthread_join(pool->workers[i].thread, NULL);
364 pthread_cond_destroy(&pool->cond_dispatch);
365 pthread_cond_destroy(&pool->cond_done);
366 pthread_mutex_destroy(&pool->mutex);
377 if (!pool || !fn)
return;
378 if (active_threads <= 0) {
381 if (active_threads > pool->n_threads) {
382 active_threads = pool->n_threads;
385 const int profile = atomic_load_explicit(
386 &pool->profile_enabled, memory_order_relaxed);
387 const uint64_t dispatch_start = profile ?
monotonic_ns() : 0;
390 if (active_threads == 1 || pool->n_threads == 1) {
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);
411 pthread_mutex_lock(&pool->mutex);
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);
418 atomic_fetch_add_explicit(&pool->n_dispatch, 1, memory_order_release);
421 pthread_cond_broadcast(&pool->cond_dispatch);
422 pthread_mutex_unlock(&pool->mutex);
425 const uint64_t main_start = profile ?
monotonic_ns() : 0;
426 fn(0, active_threads, args);
430 if (active_threads > 1) {
432 while (atomic_load_explicit(&pool->n_complete, memory_order_acquire)
433 < active_threads - 1) {
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);
442 pthread_mutex_unlock(&pool->mutex);
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);
477} ck_parallel_for_work_t;
483 ck_parallel_for_work_t *work = (ck_parallel_for_work_t *)opaque;
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);
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);
509 ck_parallel_for_work_t work = {
511 .grain_size = grain_size,
515 atomic_init(&work.next, begin);
522 if (!pool || pool->n_threads <= 1)
return;
533 atomic_store_explicit(&pool->paused, 1, memory_order_release);
539 atomic_store_explicit(&pool->paused, 0, memory_order_release);
542 pthread_mutex_lock(&pool->mutex);
543 pthread_cond_broadcast(&pool->cond_dispatch);
544 pthread_mutex_unlock(&pool->mutex);
553 return pool ? pool->default_threads : 1;
558 return pool ? pool->n_threads : 1;
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)) {
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);
586 if (!profile)
return;
587 memset(profile, 0,
sizeof(*profile));
590 &pool->profile_dispatch_count, memory_order_relaxed);
592 &pool->profile_dispatch_total_ns, memory_order_relaxed);
594 &pool->profile_main_work_ns, memory_order_relaxed);
596 &pool->profile_completion_wait_ns, memory_order_relaxed);
612 if (sched_getaffinity(0,
sizeof(allowed), &allowed) == 0) {
614 for (
int cpu = 0; cpu < CPU_SETSIZE; ++cpu) {
615 if (CPU_ISSET(cpu, &allowed)) ++count;
617 if (count > 0)
return count;
620 const long online = sysconf(_SC_NPROCESSORS_ONLN);
621 return online > 0 ? (int)online : 1;
628 if (physical_threads > available_threads) physical_threads = available_threads;
630 if (default_threads > available_threads) {
631 default_threads = available_threads;
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) {
644 default_threads, available_threads);
646 if (capacity_threads > available_threads) capacity_threads = available_threads;
648 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)
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