#include "test/jemalloc_test.h" #include "jemalloc/internal/batcher.h" TEST_BEGIN(test_simple) { enum { NELEMS_MAX = 10, DATA_BASE_VAL = 100, NRUNS = 5 }; batcher_t batcher; size_t data[NELEMS_MAX]; for (size_t nelems = 0; nelems < NELEMS_MAX; nelems++) { batcher_init(&batcher, nelems); for (int run = 0; run < NRUNS; run++) { for (int i = 0; i < NELEMS_MAX; i++) { data[i] = (size_t)-1; } for (size_t i = 0; i < nelems; i++) { size_t idx = batcher_push_begin(TSDN_NULL, &batcher, 1); assert_zu_eq(i, idx, "Wrong index"); assert_zu_eq((size_t)-1, data[idx], "Expected uninitialized slot"); data[idx] = DATA_BASE_VAL + i; batcher_push_end(TSDN_NULL, &batcher); } if (nelems > 0) { size_t idx = batcher_push_begin(TSDN_NULL, &batcher, 1); assert_zu_eq(BATCHER_NO_IDX, idx, "Shouldn't be able to push into a full " "batcher"); } size_t npop = batcher_pop_begin(TSDN_NULL, &batcher); if (nelems == 0) { assert_zu_eq(npop, BATCHER_NO_IDX, "Shouldn't get any items out of an empty " "batcher"); } else { assert_zu_eq(npop, nelems, "Wrong number of elements popped"); } for (size_t i = 0; i < nelems; i++) { assert_zu_eq(data[i], DATA_BASE_VAL + i, "Item popped out of order!"); } if (nelems != 0) { batcher_pop_end(TSDN_NULL, &batcher); } } } } TEST_END TEST_BEGIN(test_multi_push) { size_t idx, nelems; batcher_t batcher; batcher_init(&batcher, 11); /* Push two at a time, 5 times, for 10 total. */ for (int i = 0; i < 5; i++) { idx = batcher_push_begin(TSDN_NULL, &batcher, 2); assert_zu_eq(2 * i, idx, "Should push in order"); batcher_push_end(TSDN_NULL, &batcher); } /* Pushing two more should fail -- would put us at 12 elems. */ idx = batcher_push_begin(TSDN_NULL, &batcher, 2); assert_zu_eq(BATCHER_NO_IDX, idx, "Should be out of space"); /* But one more should work */ idx = batcher_push_begin(TSDN_NULL, &batcher, 1); assert_zu_eq(10, idx, "Should be out of space"); batcher_push_end(TSDN_NULL, &batcher); nelems = batcher_pop_begin(TSDN_NULL, &batcher); batcher_pop_end(TSDN_NULL, &batcher); assert_zu_eq(11, nelems, "Should have popped everything"); } TEST_END enum { STRESS_TEST_ELEMS = 10, STRESS_TEST_THREADS = 4, STRESS_TEST_OPS = 1000 * 1000, STRESS_TEST_PUSH_TO_POP_RATIO = 5, }; typedef struct stress_test_data_s stress_test_data_t; struct stress_test_data_s { batcher_t batcher; mtx_t pop_mtx; atomic_u32_t thread_id; uint32_t elems_data[STRESS_TEST_ELEMS]; size_t push_count[STRESS_TEST_ELEMS]; size_t pop_count[STRESS_TEST_ELEMS]; atomic_zu_t atomic_push_count[STRESS_TEST_ELEMS]; atomic_zu_t atomic_pop_count[STRESS_TEST_ELEMS]; }; /* * Note: 0-indexed. If one element is set and you want to find it, you call * get_nth_set(elems, 0). */ static size_t get_nth_set(bool elems_owned[STRESS_TEST_ELEMS], size_t n) { size_t ntrue = 0; for (size_t i = 0; i < STRESS_TEST_ELEMS; i++) { if (elems_owned[i]) { ntrue++; } if (ntrue > n) { return i; } } assert_not_reached("Asked for the %zu'th set element when < %zu are " "set", n, n); /* Just to silence a compiler warning. */ return 0; } static void * stress_test_thd(void *arg) { stress_test_data_t *data = arg; size_t prng = atomic_fetch_add_u32(&data->thread_id, 1, ATOMIC_RELAXED); size_t nelems_owned = 0; bool elems_owned[STRESS_TEST_ELEMS] = {0}; size_t local_push_count[STRESS_TEST_ELEMS] = {0}; size_t local_pop_count[STRESS_TEST_ELEMS] = {0}; for (int i = 0; i < STRESS_TEST_OPS; i++) { size_t rnd = prng_range_zu(&prng, STRESS_TEST_PUSH_TO_POP_RATIO); if (rnd == 0 || nelems_owned == 0) { size_t nelems = batcher_pop_begin(TSDN_NULL, &data->batcher); if (nelems == BATCHER_NO_IDX) { continue; } for (size_t i = 0; i < nelems; i++) { uint32_t elem = data->elems_data[i]; assert_false(elems_owned[elem], "Shouldn't already own what we just " "popped"); elems_owned[elem] = true; nelems_owned++; local_pop_count[elem]++; data->pop_count[elem]++; } batcher_pop_end(TSDN_NULL, &data->batcher); } else { size_t elem_to_push_idx = prng_range_zu(&prng, nelems_owned); size_t elem = get_nth_set(elems_owned, elem_to_push_idx); assert_true( elems_owned[elem], "Should own element we're about to pop"); elems_owned[elem] = false; local_push_count[elem]++; data->push_count[elem]++; nelems_owned--; size_t idx = batcher_push_begin(TSDN_NULL, &data->batcher, 1); assert_zu_ne(idx, BATCHER_NO_IDX, "Batcher can't be full -- we have one of its " "elems!"); data->elems_data[idx] = (uint32_t)elem; batcher_push_end(TSDN_NULL, &data->batcher); } } /* Push all local elems back, flush local counts to the shared ones. */ size_t push_idx = 0; if (nelems_owned != 0) { push_idx = batcher_push_begin(TSDN_NULL, &data->batcher, nelems_owned); assert_zu_ne(BATCHER_NO_IDX, push_idx, "Should be space to push"); } for (size_t i = 0; i < STRESS_TEST_ELEMS; i++) { if (elems_owned[i]) { data->elems_data[push_idx] = (uint32_t)i; push_idx++; local_push_count[i]++; data->push_count[i]++; } atomic_fetch_add_zu( &data->atomic_push_count[i], local_push_count[i], ATOMIC_RELAXED); atomic_fetch_add_zu( &data->atomic_pop_count[i], local_pop_count[i], ATOMIC_RELAXED); } if (nelems_owned != 0) { batcher_push_end(TSDN_NULL, &data->batcher); } return NULL; } TEST_BEGIN(test_stress) { stress_test_data_t data; batcher_init(&data.batcher, STRESS_TEST_ELEMS); bool err = mtx_init(&data.pop_mtx); assert_false(err, "mtx_init failure"); atomic_store_u32(&data.thread_id, 0, ATOMIC_RELAXED); for (int i = 0; i < STRESS_TEST_ELEMS; i++) { data.push_count[i] = 0; data.pop_count[i] = 0; atomic_store_zu(&data.atomic_push_count[i], 0, ATOMIC_RELAXED); atomic_store_zu(&data.atomic_pop_count[i], 0, ATOMIC_RELAXED); size_t idx = batcher_push_begin(TSDN_NULL, &data.batcher, 1); assert_zu_eq(i, idx, "Should push in order"); data.elems_data[idx] = i; batcher_push_end(TSDN_NULL, &data.batcher); } thd_t threads[STRESS_TEST_THREADS]; for (int i = 0; i < STRESS_TEST_THREADS; i++) { thd_create(&threads[i], stress_test_thd, &data); } for (int i = 0; i < STRESS_TEST_THREADS; i++) { thd_join(threads[i], NULL); } for (int i = 0; i < STRESS_TEST_ELEMS; i++) { assert_zu_ne(0, data.push_count[i], "Should have done something!"); assert_zu_eq(data.push_count[i], data.pop_count[i], "every element should be pushed and popped an equal number " "of times"); assert_zu_eq(data.push_count[i], atomic_load_zu(&data.atomic_push_count[i], ATOMIC_RELAXED), "atomic and non-atomic count should be equal given proper " "synchronization"); assert_zu_eq(data.pop_count[i], atomic_load_zu(&data.atomic_pop_count[i], ATOMIC_RELAXED), "atomic and non-atomic count should be equal given proper " "synchronization"); } } TEST_END int main(void) { return test_no_reentrancy(test_simple, test_multi_push, test_stress); }