Add the Seq module, a simple seqlock implementation.
This allows fast reader-writer concurrency in cases where writers are rare. The immediate use case is for the hooking implementaiton.
This commit is contained in:
parent
c7a87e0e0b
commit
06a8c40b36
@ -197,6 +197,7 @@ TESTS_UNIT := \
|
||||
$(srcroot)test/unit/rb.c \
|
||||
$(srcroot)test/unit/retained.c \
|
||||
$(srcroot)test/unit/rtree.c \
|
||||
$(srcroot)test/unit/seq.c \
|
||||
$(srcroot)test/unit/SFMT.c \
|
||||
$(srcroot)test/unit/size_classes.c \
|
||||
$(srcroot)test/unit/slab.c \
|
||||
|
55
include/jemalloc/internal/seq.h
Normal file
55
include/jemalloc/internal/seq.h
Normal file
@ -0,0 +1,55 @@
|
||||
#ifndef JEMALLOC_INTERNAL_SEQ_H
|
||||
#define JEMALLOC_INTERNAL_SEQ_H
|
||||
|
||||
#include "jemalloc/internal/atomic.h"
|
||||
|
||||
/*
|
||||
* A simple seqlock implementation.
|
||||
*/
|
||||
|
||||
#define seq_define(type, short_type) \
|
||||
typedef struct { \
|
||||
atomic_zu_t seq; \
|
||||
atomic_zu_t data[ \
|
||||
(sizeof(type) + sizeof(size_t) - 1) / sizeof(size_t)]; \
|
||||
} seq_##short_type##_t; \
|
||||
\
|
||||
/* \
|
||||
* No internal synchronization -- the caller must ensure that there's \
|
||||
* only a single writer at a time. \
|
||||
*/ \
|
||||
static inline void \
|
||||
seq_store_##short_type(seq_##short_type##_t *dst, type *src) { \
|
||||
size_t buf[sizeof(dst->data) / sizeof(size_t)]; \
|
||||
buf[sizeof(buf) / sizeof(size_t) - 1] = 0; \
|
||||
memcpy(buf, src, sizeof(type)); \
|
||||
size_t old_seq = atomic_load_zu(&dst->seq, ATOMIC_RELAXED); \
|
||||
atomic_store_zu(&dst->seq, old_seq + 1, ATOMIC_RELAXED); \
|
||||
atomic_fence(ATOMIC_RELEASE); \
|
||||
for (size_t i = 0; i < sizeof(buf) / sizeof(size_t); i++) { \
|
||||
atomic_store_zu(&dst->data[i], buf[i], ATOMIC_RELAXED); \
|
||||
} \
|
||||
atomic_store_zu(&dst->seq, old_seq + 2, ATOMIC_RELEASE); \
|
||||
} \
|
||||
\
|
||||
/* Returns whether or not the read was consistent. */ \
|
||||
static inline bool \
|
||||
seq_try_load_##short_type(type *dst, seq_##short_type##_t *src) { \
|
||||
size_t buf[sizeof(src->data) / sizeof(size_t)]; \
|
||||
size_t seq1 = atomic_load_zu(&src->seq, ATOMIC_ACQUIRE); \
|
||||
if (seq1 % 2 != 0) { \
|
||||
return false; \
|
||||
} \
|
||||
for (size_t i = 0; i < sizeof(buf) / sizeof(size_t); i++) { \
|
||||
buf[i] = atomic_load_zu(&src->data[i], ATOMIC_RELAXED); \
|
||||
} \
|
||||
atomic_fence(ATOMIC_ACQUIRE); \
|
||||
size_t seq2 = atomic_load_zu(&src->seq, ATOMIC_RELAXED); \
|
||||
if (seq1 != seq2) { \
|
||||
return false; \
|
||||
} \
|
||||
memcpy(dst, buf, sizeof(type)); \
|
||||
return true; \
|
||||
}
|
||||
|
||||
#endif /* JEMALLOC_INTERNAL_SEQ_H */
|
95
test/unit/seq.c
Normal file
95
test/unit/seq.c
Normal file
@ -0,0 +1,95 @@
|
||||
#include "test/jemalloc_test.h"
|
||||
|
||||
#include "jemalloc/internal/seq.h"
|
||||
|
||||
typedef struct data_s data_t;
|
||||
struct data_s {
|
||||
int arr[10];
|
||||
};
|
||||
|
||||
static void
|
||||
set_data(data_t *data, int num) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
data->arr[i] = num;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
assert_data(data_t *data) {
|
||||
int num = data->arr[0];
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assert_d_eq(num, data->arr[i], "Data consistency error");
|
||||
}
|
||||
}
|
||||
|
||||
seq_define(data_t, data)
|
||||
|
||||
typedef struct thd_data_s thd_data_t;
|
||||
struct thd_data_s {
|
||||
seq_data_t data;
|
||||
};
|
||||
|
||||
static void *
|
||||
seq_reader_thd(void *arg) {
|
||||
thd_data_t *thd_data = (thd_data_t *)arg;
|
||||
int iter = 0;
|
||||
data_t local_data;
|
||||
while (iter < 1000 * 1000 - 1) {
|
||||
bool success = seq_try_load_data(&local_data, &thd_data->data);
|
||||
if (success) {
|
||||
assert_data(&local_data);
|
||||
assert_d_le(iter, local_data.arr[0],
|
||||
"Seq read went back in time.");
|
||||
iter = local_data.arr[0];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void *
|
||||
seq_writer_thd(void *arg) {
|
||||
thd_data_t *thd_data = (thd_data_t *)arg;
|
||||
data_t local_data;
|
||||
memset(&local_data, 0, sizeof(local_data));
|
||||
for (int i = 0; i < 1000 * 1000; i++) {
|
||||
set_data(&local_data, i);
|
||||
seq_store_data(&thd_data->data, &local_data);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
TEST_BEGIN(test_seq_threaded) {
|
||||
thd_data_t thd_data;
|
||||
memset(&thd_data, 0, sizeof(thd_data));
|
||||
|
||||
thd_t reader;
|
||||
thd_t writer;
|
||||
|
||||
thd_create(&reader, seq_reader_thd, &thd_data);
|
||||
thd_create(&writer, seq_writer_thd, &thd_data);
|
||||
|
||||
thd_join(reader, NULL);
|
||||
thd_join(writer, NULL);
|
||||
}
|
||||
TEST_END
|
||||
|
||||
TEST_BEGIN(test_seq_simple) {
|
||||
data_t data;
|
||||
seq_data_t seq;
|
||||
memset(&seq, 0, sizeof(seq));
|
||||
for (int i = 0; i < 1000 * 1000; i++) {
|
||||
set_data(&data, i);
|
||||
seq_store_data(&seq, &data);
|
||||
set_data(&data, 0);
|
||||
bool success = seq_try_load_data(&data, &seq);
|
||||
assert_b_eq(success, true, "Failed non-racing read");
|
||||
assert_data(&data);
|
||||
}
|
||||
}
|
||||
TEST_END
|
||||
|
||||
int main(void) {
|
||||
return test_no_reentrancy(
|
||||
test_seq_simple,
|
||||
test_seq_threaded);
|
||||
}
|
Loading…
Reference in New Issue
Block a user