123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- #include "test/jemalloc_test.h"
- #include "jemalloc/internal/mpsc_queue.h"
- typedef struct elem_s elem_t;
- typedef ql_head(elem_t) elem_list_t;
- typedef mpsc_queue(elem_t) elem_mpsc_queue_t;
- struct elem_s {
- int thread;
- int idx;
- ql_elm(elem_t) link;
- };
- /* Include both proto and gen to make sure they match up. */
- mpsc_queue_proto(static, elem_mpsc_queue_, elem_mpsc_queue_t, elem_t,
- elem_list_t);
- mpsc_queue_gen(static, elem_mpsc_queue_, elem_mpsc_queue_t, elem_t,
- elem_list_t, link);
- static void
- init_elems_simple(elem_t *elems, int nelems, int thread) {
- for (int i = 0; i < nelems; i++) {
- elems[i].thread = thread;
- elems[i].idx = i;
- ql_elm_new(&elems[i], link);
- }
- }
- static void
- check_elems_simple(elem_list_t *list, int nelems, int thread) {
- elem_t *elem;
- int next_idx = 0;
- ql_foreach(elem, list, link) {
- expect_d_lt(next_idx, nelems, "Too many list items");
- expect_d_eq(thread, elem->thread, "");
- expect_d_eq(next_idx, elem->idx, "List out of order");
- next_idx++;
- }
- }
- TEST_BEGIN(test_simple) {
- enum {NELEMS = 10};
- elem_t elems[NELEMS];
- elem_list_t list;
- elem_mpsc_queue_t queue;
- /* Pop empty queue onto empty list -> empty list */
- ql_new(&list);
- elem_mpsc_queue_new(&queue);
- elem_mpsc_queue_pop_batch(&queue, &list);
- expect_true(ql_empty(&list), "");
- /* Pop empty queue onto nonempty list -> list unchanged */
- ql_new(&list);
- elem_mpsc_queue_new(&queue);
- init_elems_simple(elems, NELEMS, 0);
- for (int i = 0; i < NELEMS; i++) {
- ql_tail_insert(&list, &elems[i], link);
- }
- elem_mpsc_queue_pop_batch(&queue, &list);
- check_elems_simple(&list, NELEMS, 0);
- /* Pop nonempty queue onto empty list -> list takes queue contents */
- ql_new(&list);
- elem_mpsc_queue_new(&queue);
- init_elems_simple(elems, NELEMS, 0);
- for (int i = 0; i < NELEMS; i++) {
- elem_mpsc_queue_push(&queue, &elems[i]);
- }
- elem_mpsc_queue_pop_batch(&queue, &list);
- check_elems_simple(&list, NELEMS, 0);
- /* Pop nonempty queue onto nonempty list -> list gains queue contents */
- ql_new(&list);
- elem_mpsc_queue_new(&queue);
- init_elems_simple(elems, NELEMS, 0);
- for (int i = 0; i < NELEMS / 2; i++) {
- ql_tail_insert(&list, &elems[i], link);
- }
- for (int i = NELEMS / 2; i < NELEMS; i++) {
- elem_mpsc_queue_push(&queue, &elems[i]);
- }
- elem_mpsc_queue_pop_batch(&queue, &list);
- check_elems_simple(&list, NELEMS, 0);
- }
- TEST_END
- TEST_BEGIN(test_push_single_or_batch) {
- enum {
- BATCH_MAX = 10,
- /*
- * We'll push i items one-at-a-time, then i items as a batch,
- * then i items as a batch again, as i ranges from 1 to
- * BATCH_MAX. So we need 3 times the sum of the numbers from 1
- * to BATCH_MAX elements total.
- */
- NELEMS = 3 * BATCH_MAX * (BATCH_MAX - 1) / 2
- };
- elem_t elems[NELEMS];
- init_elems_simple(elems, NELEMS, 0);
- elem_list_t list;
- ql_new(&list);
- elem_mpsc_queue_t queue;
- elem_mpsc_queue_new(&queue);
- int next_idx = 0;
- for (int i = 1; i < 10; i++) {
- /* Push i items 1 at a time. */
- for (int j = 0; j < i; j++) {
- elem_mpsc_queue_push(&queue, &elems[next_idx]);
- next_idx++;
- }
- /* Push i items in batch. */
- for (int j = 0; j < i; j++) {
- ql_tail_insert(&list, &elems[next_idx], link);
- next_idx++;
- }
- elem_mpsc_queue_push_batch(&queue, &list);
- expect_true(ql_empty(&list), "Batch push should empty source");
- /*
- * Push i items in batch, again. This tests two batches
- * proceeding one after the other.
- */
- for (int j = 0; j < i; j++) {
- ql_tail_insert(&list, &elems[next_idx], link);
- next_idx++;
- }
- elem_mpsc_queue_push_batch(&queue, &list);
- expect_true(ql_empty(&list), "Batch push should empty source");
- }
- expect_d_eq(NELEMS, next_idx, "Miscomputed number of elems to push.");
- expect_true(ql_empty(&list), "");
- elem_mpsc_queue_pop_batch(&queue, &list);
- check_elems_simple(&list, NELEMS, 0);
- }
- TEST_END
- TEST_BEGIN(test_multi_op) {
- enum {NELEMS = 20};
- elem_t elems[NELEMS];
- init_elems_simple(elems, NELEMS, 0);
- elem_list_t push_list;
- ql_new(&push_list);
- elem_list_t result_list;
- ql_new(&result_list);
- elem_mpsc_queue_t queue;
- elem_mpsc_queue_new(&queue);
- int next_idx = 0;
- /* Push first quarter 1-at-a-time. */
- for (int i = 0; i < NELEMS / 4; i++) {
- elem_mpsc_queue_push(&queue, &elems[next_idx]);
- next_idx++;
- }
- /* Push second quarter in batch. */
- for (int i = NELEMS / 4; i < NELEMS / 2; i++) {
- ql_tail_insert(&push_list, &elems[next_idx], link);
- next_idx++;
- }
- elem_mpsc_queue_push_batch(&queue, &push_list);
- /* Batch pop all pushed elements. */
- elem_mpsc_queue_pop_batch(&queue, &result_list);
- /* Push third quarter in batch. */
- for (int i = NELEMS / 2; i < 3 * NELEMS / 4; i++) {
- ql_tail_insert(&push_list, &elems[next_idx], link);
- next_idx++;
- }
- elem_mpsc_queue_push_batch(&queue, &push_list);
- /* Push last quarter one-at-a-time. */
- for (int i = 3 * NELEMS / 4; i < NELEMS; i++) {
- elem_mpsc_queue_push(&queue, &elems[next_idx]);
- next_idx++;
- }
- /* Pop them again. Order of existing list should be preserved. */
- elem_mpsc_queue_pop_batch(&queue, &result_list);
- check_elems_simple(&result_list, NELEMS, 0);
- }
- TEST_END
- typedef struct pusher_arg_s pusher_arg_t;
- struct pusher_arg_s {
- elem_mpsc_queue_t *queue;
- int thread;
- elem_t *elems;
- int nelems;
- };
- typedef struct popper_arg_s popper_arg_t;
- struct popper_arg_s {
- elem_mpsc_queue_t *queue;
- int npushers;
- int nelems_per_pusher;
- int *pusher_counts;
- };
- static void *
- thd_pusher(void *void_arg) {
- pusher_arg_t *arg = (pusher_arg_t *)void_arg;
- int next_idx = 0;
- while (next_idx < arg->nelems) {
- /* Push 10 items in batch. */
- elem_list_t list;
- ql_new(&list);
- int limit = next_idx + 10;
- while (next_idx < arg->nelems && next_idx < limit) {
- ql_tail_insert(&list, &arg->elems[next_idx], link);
- next_idx++;
- }
- elem_mpsc_queue_push_batch(arg->queue, &list);
- /* Push 10 items one-at-a-time. */
- limit = next_idx + 10;
- while (next_idx < arg->nelems && next_idx < limit) {
- elem_mpsc_queue_push(arg->queue, &arg->elems[next_idx]);
- next_idx++;
- }
- }
- return NULL;
- }
- static void *
- thd_popper(void *void_arg) {
- popper_arg_t *arg = (popper_arg_t *)void_arg;
- int done_pushers = 0;
- while (done_pushers < arg->npushers) {
- elem_list_t list;
- ql_new(&list);
- elem_mpsc_queue_pop_batch(arg->queue, &list);
- elem_t *elem;
- ql_foreach(elem, &list, link) {
- int thread = elem->thread;
- int idx = elem->idx;
- expect_d_eq(arg->pusher_counts[thread], idx,
- "Thread's pushes reordered");
- arg->pusher_counts[thread]++;
- if (arg->pusher_counts[thread]
- == arg->nelems_per_pusher) {
- done_pushers++;
- }
- }
- }
- return NULL;
- }
- TEST_BEGIN(test_multiple_threads) {
- enum {
- NPUSHERS = 4,
- NELEMS_PER_PUSHER = 1000*1000,
- };
- thd_t pushers[NPUSHERS];
- pusher_arg_t pusher_arg[NPUSHERS];
- thd_t popper;
- popper_arg_t popper_arg;
- elem_mpsc_queue_t queue;
- elem_mpsc_queue_new(&queue);
- elem_t *elems = calloc(NPUSHERS * NELEMS_PER_PUSHER, sizeof(elem_t));
- elem_t *elem_iter = elems;
- for (int i = 0; i < NPUSHERS; i++) {
- pusher_arg[i].queue = &queue;
- pusher_arg[i].thread = i;
- pusher_arg[i].elems = elem_iter;
- pusher_arg[i].nelems = NELEMS_PER_PUSHER;
- init_elems_simple(elem_iter, NELEMS_PER_PUSHER, i);
- elem_iter += NELEMS_PER_PUSHER;
- }
- popper_arg.queue = &queue;
- popper_arg.npushers = NPUSHERS;
- popper_arg.nelems_per_pusher = NELEMS_PER_PUSHER;
- int pusher_counts[NPUSHERS] = {0};
- popper_arg.pusher_counts = pusher_counts;
- thd_create(&popper, thd_popper, (void *)&popper_arg);
- for (int i = 0; i < NPUSHERS; i++) {
- thd_create(&pushers[i], thd_pusher, &pusher_arg[i]);
- }
- thd_join(popper, NULL);
- for (int i = 0; i < NPUSHERS; i++) {
- thd_join(pushers[i], NULL);
- }
- for (int i = 0; i < NPUSHERS; i++) {
- expect_d_eq(NELEMS_PER_PUSHER, pusher_counts[i], "");
- }
- free(elems);
- }
- TEST_END
- int
- main(void) {
- return test_no_reentrancy(
- test_simple,
- test_push_single_or_batch,
- test_multi_op,
- test_multiple_threads);
- }
|