void ck_barrier_mcs(struct ck_barrier_mcs *barrier, struct ck_barrier_mcs_state *state) { /* * Wait until all children have reached the barrier and are done waiting * for their children. */ while (ck_barrier_mcs_check_children(barrier[state->vpid].childnotready) == false) ck_pr_stall(); /* Reinitialize for next barrier. */ ck_barrier_mcs_reinitialize_children(&barrier[state->vpid]); /* Inform parent thread and its children have arrived at the barrier. */ ck_pr_store_uint(barrier[state->vpid].parent, 0); /* Wait until parent indicates all threads have arrived at the barrier. */ if (state->vpid != 0) { while (ck_pr_load_uint(&barrier[state->vpid].parentsense) != state->sense) ck_pr_stall(); } /* Inform children of successful barrier. */ ck_pr_store_uint(barrier[state->vpid].children[0], state->sense); ck_pr_store_uint(barrier[state->vpid].children[1], state->sense); state->sense = ~state->sense; return; }
void ck_barrier_centralized(struct ck_barrier_centralized *barrier, struct ck_barrier_centralized_state *state, unsigned int n_threads) { unsigned int sense, value; /* * Every execution context has a sense associated with it. * This sense is reversed when the barrier is entered. Every * thread will spin on the global sense until the last thread * reverses it. */ sense = state->sense = ~state->sense; value = ck_pr_faa_uint(&barrier->value, 1); if (value == n_threads - 1) { ck_pr_store_uint(&barrier->value, 0); ck_pr_fence_memory(); ck_pr_store_uint(&barrier->sense, sense); return; } ck_pr_fence_load(); while (sense != ck_pr_load_uint(&barrier->sense)) ck_pr_stall(); ck_pr_fence_memory(); return; }
static void rwlock_test(pthread_t *p, int d, uint64_t *latency, void *(*f)(void *), const char *label) { int t; ck_pr_store_int(&barrier, 0); ck_pr_store_uint(&flag, 0); affinity.delta = d; affinity.request = 0; fprintf(stderr, "Creating threads (%s)...", label); for (t = 0; t < threads; t++) { if (pthread_create(&p[t], NULL, f, latency + t) != 0) { ck_error("ERROR: Could not create thread %d\n", t); } } fprintf(stderr, "done\n"); common_sleep(10); ck_pr_store_uint(&flag, 1); fprintf(stderr, "Waiting for threads to finish acquisition regression..."); for (t = 0; t < threads; t++) pthread_join(p[t], NULL); fprintf(stderr, "done\n\n"); for (t = 1; t <= threads; t++) printf("%10u %20" PRIu64 "\n", t, latency[t - 1]); fprintf(stderr, "\n"); return; }
CK_CC_INLINE static void ck_barrier_mcs_reinitialize_children(struct ck_barrier_mcs *node) { ck_pr_store_uint(&node->childnotready[0], node->havechild[0]); ck_pr_store_uint(&node->childnotready[1], node->havechild[1]); ck_pr_store_uint(&node->childnotready[2], node->havechild[2]); ck_pr_store_uint(&node->childnotready[3], node->havechild[3]); return; }
// want to verify that we can observe the correct end counter value // in the face of concurrent updates static void concurrentCounters(void) { int i, num_threads = 4; struct counter_data data = { 0, 100000, 0, NULL }; pthread_t threads[num_threads]; data.scope = ph_counter_scope_define(NULL, "testConcurrentCounters", 1); is_true(data.scope != NULL); data.slot = ph_counter_scope_register_counter(data.scope, "dummy"); is(0, data.slot); for (i = 0; i < num_threads; i++) { pthread_create(&threads[i], NULL, spin_and_count, &data); } /* unleash the threads on the data */ ck_pr_store_uint(&data.barrier, 1); for (i = 0; i < num_threads; i++) { void *unused; pthread_join(threads[i], &unused); } is(num_threads * data.iters, ph_counter_scope_get(data.scope, data.slot)); }
void ck_barrier_dissemination(struct ck_barrier_dissemination *barrier, struct ck_barrier_dissemination_state *state) { unsigned int i; unsigned int size = barrier->size; for (i = 0; i < size; ++i) { /* Unblock current partner. */ ck_pr_store_uint(barrier[state->tid].flags[state->parity][i].pflag, state->sense); /* Wait until some other thread unblocks this one. */ while (ck_pr_load_uint(&barrier[state->tid].flags[state->parity][i].tflag) != state->sense) ck_pr_stall(); } /* * Dissemination barriers use two sets of flags to prevent race conditions * between successive calls to the barrier. Parity indicates which set will * be used for the next barrier. They also use a sense reversal technique * to avoid re-initialization of the flags for every two calls to the barrier. */ if (state->parity == 1) state->sense = ~state->sense; state->parity = 1 - state->parity; return; }
static CK_CC_INLINE void rwlock_init(rwlock_t *rw) { ck_pr_store_uint(&rw->readers, 0); ck_spinlock_fas_init(&rw->writer); return; }
int main(int argc, char *argv[]) { int t; pthread_t *p; uint64_t *latency; if (argc != 3) { fprintf(stderr, "Usage: throughput <delta> <threads>\n"); exit(EXIT_FAILURE); } threads = atoi(argv[2]); if (threads <= 0) { fprintf(stderr, "ERROR: Threads must be a value > 0.\n"); exit(EXIT_FAILURE); } p = malloc(sizeof(pthread_t) * threads); if (p == NULL) { fprintf(stderr, "ERROR: Failed to initialize thread.\n"); exit(EXIT_FAILURE); } latency = malloc(sizeof(uint64_t) * threads); if (latency == NULL) { fprintf(stderr, "ERROR: Failed to create latency buffer.\n"); exit(EXIT_FAILURE); } affinity.delta = atoi(argv[1]); affinity.request = 0; fprintf(stderr, "Creating threads (brlock)..."); for (t = 0; t < threads; t++) { if (pthread_create(&p[t], NULL, thread_brlock, latency + t) != 0) { fprintf(stderr, "ERROR: Could not create thread %d\n", t); exit(EXIT_FAILURE); } } fprintf(stderr, "done\n"); sleep(10); ck_pr_store_uint(&flag, 1); fprintf(stderr, "Waiting for threads to finish acquisition regression..."); for (t = 0; t < threads; t++) pthread_join(p[t], NULL); fprintf(stderr, "done\n\n"); for (t = 1; t <= threads; t++) printf("%10u %20" PRIu64 "\n", t, latency[t - 1]); return (0); }
bool ck_array_put(struct ck_array *array, void *value) { struct _ck_array *target; unsigned int size; /* * If no transaction copy has been necessary, attempt to do in-place * modification of the array. */ if (array->transaction == NULL) { target = array->active; if (array->n_entries == target->length) { size = target->length << 1; target = array->allocator->realloc(target, sizeof(struct _ck_array) + sizeof(void *) * array->n_entries, sizeof(struct _ck_array) + sizeof(void *) * size, true); if (target == NULL) return false; ck_pr_store_uint(&target->length, size); /* Serialize with respect to contents. */ ck_pr_fence_store(); ck_pr_store_ptr(&array->active, target); } target->values[array->n_entries++] = value; return true; } target = array->transaction; if (array->n_entries == target->length) { size = target->length << 1; target = array->allocator->realloc(target, sizeof(struct _ck_array) + sizeof(void *) * array->n_entries, sizeof(struct _ck_array) + sizeof(void *) * size, true); if (target == NULL) return false; target->length = size; array->transaction = target; } target->values[array->n_entries++] = value; return false; }
static void ck_barrier_combining_aux(struct ck_barrier_combining *barrier, struct ck_barrier_combining_group *tnode, unsigned int sense) { /* * If this is the last thread in the group, it moves on to the parent group. * Otherwise, it spins on this group's sense. */ if (ck_pr_faa_uint(&tnode->count, 1) == tnode->k - 1) { /* * If we are and will be the last thread entering the barrier for the * current group then signal the parent group if one exists. */ if (tnode->parent != NULL) ck_barrier_combining_aux(barrier, tnode->parent, sense); /* * Once the thread returns from its parent(s), it reinitializes the group's * arrival count and signals other threads to continue by flipping the group * sense. Order of these operations is not important since we assume a static * number of threads are members of a barrier for the lifetime of the barrier. * Since count is explicitly reinitialized, it is guaranteed that at any point * tnode->count is equivalent to tnode->k if and only if that many threads * are at the barrier. */ ck_pr_store_uint(&tnode->count, 0); ck_pr_fence_store(); ck_pr_store_uint(&tnode->sense, ~tnode->sense); } else { ck_pr_fence_memory(); while (sense != ck_pr_load_uint(&tnode->sense)) ck_pr_stall(); } return; }
void ck_barrier_dissemination_init(struct ck_barrier_dissemination *barrier, struct ck_barrier_dissemination_flag **barrier_internal, unsigned int nthr) { unsigned int i, j, k, size, offset; bool p = nthr & (nthr - 1); barrier->nthr = nthr; barrier->size = size = ck_internal_log(ck_internal_power_2(nthr)); ck_pr_store_uint(&barrier->tid, 0); for (i = 0; i < nthr; ++i) { barrier[i].flags[0] = barrier_internal[i]; barrier[i].flags[1] = barrier_internal[i] + size; } for (i = 0; i < nthr; ++i) { for (k = 0, offset = 1; k < size; ++k, offset <<= 1) { /* * Determine the thread's partner, j, for the current round, k. * Partners are chosen such that by the completion of the barrier, * every thread has been directly (having one of its flag set) or * indirectly (having one of its partners's flags set) signaled * by every other thread in the barrier. */ if (p == false) j = (i + offset) & (nthr - 1); else j = (i + offset) % nthr; /* Set the thread's partner for round k. */ barrier[i].flags[0][k].pflag = &barrier[j].flags[0][k].tflag; barrier[i].flags[1][k].pflag = &barrier[j].flags[1][k].tflag; /* Set the thread's flags to false. */ barrier[i].flags[0][k].tflag = barrier[i].flags[1][k].tflag = 0; } } return; }
void ck_barrier_mcs_init(struct ck_barrier_mcs *barrier, unsigned int nthr) { unsigned int i, j; ck_pr_store_uint(&barrier->tid, 0); for (i = 0; i < nthr; ++i) { for (j = 0; j < 4; ++j) { /* * If there are still threads that don't have parents, * add it as a child. */ barrier[i].havechild[j] = ((i << 2) + j < nthr - 1) ? ~0 : 0; /* * childnotready is initialized to havechild to ensure * a thread does not wait for a child that does not exist. */ barrier[i].childnotready[j] = barrier[i].havechild[j]; } /* The root thread does not have a parent. */ barrier[i].parent = (i == 0) ? &barrier[i].dummy : &barrier[(i - 1) >> 2].childnotready[(i - 1) & 3]; /* Leaf threads do not have any children. */ barrier[i].children[0] = ((i << 1) + 1 >= nthr) ? &barrier[i].dummy : &barrier[(i << 1) + 1].parentsense; barrier[i].children[1] = ((i << 1) + 2 >= nthr) ? &barrier[i].dummy : &barrier[(i << 1) + 2].parentsense; barrier[i].parentsense = 0; } return; }
void ck_barrier_tournament_init(struct ck_barrier_tournament *barrier, struct ck_barrier_tournament_round **rounds, unsigned int nthr) { unsigned int i, k, size, twok, twokm1, imod2k; ck_pr_store_uint(&barrier->tid, 0); size = ck_barrier_tournament_size(nthr); for (i = 0; i < nthr; ++i) { /* The first role is always CK_BARRIER_TOURNAMENT_DROPOUT. */ rounds[i][0].flag = 0; rounds[i][0].role = CK_BARRIER_TOURNAMENT_DROPOUT; for (k = 1, twok = 2, twokm1 = 1; k < size; ++k, twokm1 = twok, twok <<= 1) { rounds[i][k].flag = 0; imod2k = i & (twok - 1); if (imod2k == 0) { if ((i + twokm1 < nthr) && (twok < nthr)) rounds[i][k].role = CK_BARRIER_TOURNAMENT_WINNER; else if (i + twokm1 >= nthr) rounds[i][k].role = CK_BARRIER_TOURNAMENT_BYE; } if (imod2k == twokm1) rounds[i][k].role = CK_BARRIER_TOURNAMENT_LOSER; else if ((i == 0) && (twok >= nthr)) rounds[i][k].role = CK_BARRIER_TOURNAMENT_CHAMPION; if (rounds[i][k].role == CK_BARRIER_TOURNAMENT_LOSER) rounds[i][k].opponent = &rounds[i - twokm1][k].flag; else if (rounds[i][k].role == CK_BARRIER_TOURNAMENT_WINNER || rounds[i][k].role == CK_BARRIER_TOURNAMENT_CHAMPION) rounds[i][k].opponent = &rounds[i + twokm1][k].flag; } } ck_pr_store_ptr(&barrier->rounds, rounds); return; }
bool ck_array_commit(ck_array_t *array) { struct _ck_array *m = array->transaction; if (m != NULL) { struct _ck_array *p; m->n_committed = array->n_entries; ck_pr_fence_store(); p = array->active; ck_pr_store_ptr(&array->active, m); array->allocator->free(p, sizeof(struct _ck_array) + p->length * sizeof(void *), true); array->transaction = NULL; return true; } ck_pr_fence_store(); ck_pr_store_uint(&array->active->n_committed, array->n_entries); return true; }
static inline void ck_rhs_map_bound_set(struct ck_rhs_map *m, unsigned long h, unsigned long n_probes) { unsigned long offset = h & m->mask; struct ck_rhs_entry_desc *desc; if (n_probes > m->probe_maximum) ck_pr_store_uint(&m->probe_maximum, n_probes); if (!(m->read_mostly)) { desc = &m->entries.descs[offset]; if (desc->probe_bound < n_probes) { if (n_probes > CK_RHS_WORD_MAX) n_probes = CK_RHS_WORD_MAX; CK_RHS_STORE(&desc->probe_bound, n_probes); ck_pr_fence_store(); } } return; }
void ck_barrier_tournament(struct ck_barrier_tournament *barrier, struct ck_barrier_tournament_state *state) { struct ck_barrier_tournament_round **rounds = ck_pr_load_ptr(&barrier->rounds); int round = 1; for (;; ++round) { switch (rounds[state->vpid][round].role) { // MIGHT NEED TO USE CK_PR_LOAD*** case CK_BARRIER_TOURNAMENT_BYE: break; case CK_BARRIER_TOURNAMENT_CHAMPION: /* * The CK_BARRIER_TOURNAMENT_CHAMPION waits until it wins the tournament; it then * sets the final flag before the wakeup phase of the barrier. */ while (ck_pr_load_uint(&rounds[state->vpid][round].flag) != state->sense) ck_pr_stall(); ck_pr_store_uint(rounds[state->vpid][round].opponent, state->sense); goto wakeup; case CK_BARRIER_TOURNAMENT_DROPOUT: /* NOTREACHED */ break; case CK_BARRIER_TOURNAMENT_LOSER: /* * CK_BARRIER_TOURNAMENT_LOSERs set the flags of their opponents and wait until * their opponents release them after the tournament is over. */ ck_pr_store_uint(rounds[state->vpid][round].opponent, state->sense); while (ck_pr_load_uint(&rounds[state->vpid][round].flag) != state->sense) ck_pr_stall(); goto wakeup; case CK_BARRIER_TOURNAMENT_WINNER: /* * CK_BARRIER_TOURNAMENT_WINNERs wait until their current opponent sets their flag; they then * continue to the next round of the tournament. */ while (ck_pr_load_uint(&rounds[state->vpid][round].flag) != state->sense) ck_pr_stall(); break; } } wakeup: for (round -= 1 ;; --round) { switch (rounds[state->vpid][round].role) { // MIGHT NEED TO USE CK_PR_LOAD*** case CK_BARRIER_TOURNAMENT_BYE: break; case CK_BARRIER_TOURNAMENT_CHAMPION: /* NOTREACHED */ break; case CK_BARRIER_TOURNAMENT_DROPOUT: goto leave; break; case CK_BARRIER_TOURNAMENT_LOSER: /* NOTREACHED */ break; case CK_BARRIER_TOURNAMENT_WINNER: /* * Winners inform their old opponents the tournament is over * by setting their flags. */ ck_pr_store_uint(rounds[state->vpid][round].opponent, state->sense); break; } } leave: state->sense = ~state->sense; return; }
bool ck_bag_put_spmc(struct ck_bag *bag, void *entry) { struct ck_bag_block *cursor, *new_block, *new_block_prev, *new_tail; uint16_t n_entries_block; size_t blocks_alloc, i; uintptr_t next = 0; new_block = new_block_prev = new_tail = NULL; /* * Blocks with available entries are stored in * the bag's available list. */ cursor = bag->avail_head; if (cursor != NULL) { n_entries_block = ck_bag_block_count(cursor); } else { /* The bag is full, allocate a new set of blocks */ if (bag->alloc_strat == CK_BAG_ALLOCATE_GEOMETRIC) blocks_alloc = (bag->n_blocks + 1) << 1; else blocks_alloc = 1; for (i = 0; i < blocks_alloc-1; i++) { new_block = allocator.malloc(bag->info.bytes); if (new_block == NULL) return false; /* * First node is the tail of the Bag. * Second node is the new tail of the Available list. */ if (i == 0) new_tail = new_block; #ifndef __x86_64__ new_block->next.n_entries = 0; #endif new_block->next.ptr = new_block_prev; new_block->avail_next = new_block_prev; if (new_block_prev != NULL) new_block_prev->avail_prev = new_block; new_block_prev = new_block; } /* * Insert entry into last allocated block. * cursor is new head of available list. */ cursor = allocator.malloc(bag->info.bytes); cursor->avail_next = new_block; cursor->avail_prev = NULL; new_block->avail_prev = cursor; n_entries_block = 0; bag->n_blocks += blocks_alloc; /* n_blocks and n_avail_blocks? */ } /* Update the available list */ if (new_block != NULL) { if (bag->avail_tail != NULL) { cursor->avail_prev = bag->avail_tail; bag->avail_tail->avail_next = cursor; } else { /* Available list was previously empty */ bag->avail_head = cursor; } bag->avail_tail = new_tail; } else if (n_entries_block == bag->info.max-1) { /* New entry will fill up block, remove from avail list */ if (cursor->avail_prev != NULL) cursor->avail_prev->avail_next = cursor->avail_next; if (cursor->avail_next != NULL) cursor->avail_next->avail_prev = cursor->avail_prev; if (bag->avail_head == cursor) bag->avail_head = cursor->avail_next; if (bag->avail_tail == cursor) bag->avail_tail = cursor->avail_prev; /* For debugging purposes */ cursor->avail_next = NULL; cursor->avail_prev = NULL; } /* Update array and block->n_entries */ cursor->array[n_entries_block++] = entry; ck_pr_fence_store(); #ifdef __x86_64__ next = ((uintptr_t)n_entries_block << 48); #endif /* Update bag's list */ if (n_entries_block == 1) { if (bag->head != NULL) { #ifdef __x86_64__ next += ((uintptr_t)(void *)ck_bag_block_next(bag->head)); #else next = (uintptr_t)(void *)ck_bag_block_next(bag->head); #endif } #ifndef __x86_64__ ck_pr_store_ptr(&cursor->next.n_entries, (void *)(uintptr_t)n_entries_block); #endif ck_pr_store_ptr(&cursor->next.ptr, (void *)next); ck_pr_store_ptr(&bag->head, cursor); } else { #ifdef __x86_64__ next += ((uintptr_t)(void *)ck_bag_block_next(cursor->next.ptr)); ck_pr_store_ptr(&cursor->next, (void *)next); #else ck_pr_store_ptr(&cursor->next.n_entries, (void *)(uintptr_t)n_entries_block); #endif } ck_pr_store_uint(&bag->n_entries, bag->n_entries + 1); return true; }
bool ck_bag_remove_spmc(struct ck_bag *bag, void *entry) { struct ck_bag_block *cursor, *copy, *prev; uint16_t block_index, n_entries; cursor = bag->head; prev = NULL; while (cursor != NULL) { n_entries = ck_bag_block_count(cursor); for (block_index = 0; block_index < n_entries; block_index++) { if (cursor->array[block_index] == entry) goto found; } prev = cursor; cursor = ck_bag_block_next(cursor->next.ptr); } return true; found: /* Cursor points to containing block, block_index is index of deletion */ if (n_entries == 1) { /* If a block's single entry is being removed, remove the block. */ if (prev == NULL) { struct ck_bag_block *new_head = ck_bag_block_next(cursor->next.ptr); ck_pr_store_ptr(&bag->head, new_head); } else { uintptr_t next; #ifdef __x86_64__ next = ((uintptr_t)prev->next.ptr & (CK_BAG_BLOCK_ENTRIES_MASK)) | (uintptr_t)(void *)ck_bag_block_next(cursor->next.ptr); #else next = (uintptr_t)(void *)cursor->next.ptr; #endif ck_pr_store_ptr(&prev->next.ptr, (struct ck_bag_block *)next); } /* Remove block from available list */ if (cursor->avail_prev != NULL) cursor->avail_prev->avail_next = cursor->avail_next; if (cursor->avail_next != NULL) cursor->avail_next->avail_prev = cursor->avail_prev; bag->n_blocks--; copy = cursor->avail_next; } else { uintptr_t next_ptr; copy = allocator.malloc(bag->info.bytes); if (copy == NULL) return false; memcpy(copy, cursor, bag->info.bytes); copy->array[block_index] = copy->array[--n_entries]; next_ptr = (uintptr_t)(void *)ck_bag_block_next(copy->next.ptr); #ifdef __x86_64__ copy->next.ptr = (void *)(((uintptr_t)n_entries << 48) | next_ptr); #else copy->next.n_entries = n_entries; copy->next.ptr = (struct ck_bag_block *)next_ptr; #endif ck_pr_fence_store(); if (prev == NULL) { ck_pr_store_ptr(&bag->head, copy); } else { #ifdef __x86_64__ uintptr_t next = ((uintptr_t)prev->next.ptr & (CK_BAG_BLOCK_ENTRIES_MASK)) | (uintptr_t)(void *)ck_bag_block_next(copy); ck_pr_store_ptr(&prev->next.ptr, (struct ck_bag_block *)next); #else ck_pr_store_ptr(&prev->next.ptr, copy); #endif } if (n_entries == bag->info.max - 1) { /* Block was previously fully, add to head of avail. list */ copy->avail_next = bag->avail_head; copy->avail_prev = NULL; bag->avail_head = copy; } } /* Update available list. */ if (bag->avail_head == cursor) bag->avail_head = copy; if (bag->avail_tail == cursor) bag->avail_tail = copy; allocator.free(cursor, sizeof(struct ck_bag_block), true); ck_pr_store_uint(&bag->n_entries, bag->n_entries - 1); return true; }
int main(int argc, char *argv[]) { uint64_t v, d; unsigned int i; pthread_t *threads; struct block *context; ck_spinlock_t *local_lock; if (argc != 5) { ck_error("Usage: ck_cohort <number of cohorts> <threads per cohort> " "<affinity delta> <critical section>\n"); } n_cohorts = atoi(argv[1]); if (n_cohorts <= 0) { ck_error("ERROR: Number of cohorts must be greater than 0\n"); } nthr = n_cohorts * atoi(argv[2]); if (nthr <= 0) { ck_error("ERROR: Number of threads must be greater than 0\n"); } critical = atoi(argv[4]); if (critical < 0) { ck_error("ERROR: critical section cannot be negative\n"); } threads = malloc(sizeof(pthread_t) * nthr); if (threads == NULL) { ck_error("ERROR: Could not allocate thread structures\n"); } cohorts = malloc(sizeof(struct cohort_record) * n_cohorts); if (cohorts == NULL) { ck_error("ERROR: Could not allocate cohort structures\n"); } context = malloc(sizeof(struct block) * nthr); if (context == NULL) { ck_error("ERROR: Could not allocate thread contexts\n"); } a.delta = atoi(argv[2]); a.request = 0; count = malloc(sizeof(*count) * nthr); if (count == NULL) { ck_error("ERROR: Could not create acquisition buffer\n"); } memset(count, 0, sizeof(*count) * nthr); fprintf(stderr, "Creating cohorts..."); for (i = 0 ; i < n_cohorts ; i++) { local_lock = malloc(max(CK_MD_CACHELINE, sizeof(ck_spinlock_t))); if (local_lock == NULL) { ck_error("ERROR: Could not allocate local lock\n"); } CK_COHORT_INIT(basic, &((cohorts + i)->cohort), &global_lock, local_lock, CK_COHORT_DEFAULT_LOCAL_PASS_LIMIT); local_lock = NULL; } fprintf(stderr, "done\n"); fprintf(stderr, "Creating threads (fairness)..."); for (i = 0; i < nthr; i++) { context[i].tid = i; if (pthread_create(&threads[i], NULL, fairness, context + i)) { ck_error("ERROR: Could not create thread %d\n", i); } } fprintf(stderr, "done\n"); ck_pr_store_uint(&ready, 1); common_sleep(10); ck_pr_store_uint(&ready, 0); fprintf(stderr, "Waiting for threads to finish acquisition regression..."); for (i = 0; i < nthr; i++) pthread_join(threads[i], NULL); fprintf(stderr, "done\n\n"); for (i = 0, v = 0; i < nthr; i++) { printf("%d %15" PRIu64 "\n", i, count[i].value); v += count[i].value; } printf("\n# total : %15" PRIu64 "\n", v); printf("# throughput : %15" PRIu64 " a/s\n", (v /= nthr) / 10); for (i = 0, d = 0; i < nthr; i++) d += (count[i].value - v) * (count[i].value - v); printf("# average : %15" PRIu64 "\n", v); printf("# deviation : %.2f (%.2f%%)\n\n", sqrt(d / nthr), (sqrt(d / nthr) / v) * 100.00); return 0; }