247 lines
7.1 KiB
C
247 lines
7.1 KiB
C
#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);
|
|
}
|