From 0a2e133ab9d6fcb55ccc4a31a187f29469fb4fff Mon Sep 17 00:00:00 2001 From: Andrew Noyes Date: Mon, 9 Sep 2024 17:27:58 -0700 Subject: [PATCH] Add InterleavingTest to explore #5 --- CMakeLists.txt | 2 + InterleavingTest.cpp | 145 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 InterleavingTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d282400..03670de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -345,6 +345,8 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING) add_executable(server_bench ServerBench.cpp) target_link_libraries(server_bench PRIVATE ${PROJECT_NAME}) set_target_properties(server_bench PROPERTIES SKIP_BUILD_RPATH ON) + + add_executable(interleaving_test InterleavingTest.cpp) endif() # packaging diff --git a/InterleavingTest.cpp b/InterleavingTest.cpp new file mode 100644 index 0000000..41a90be --- /dev/null +++ b/InterleavingTest.cpp @@ -0,0 +1,145 @@ +#include +#include + +#define ANKERL_NANOBENCH_IMPLEMENT +#include "third_party/nanobench.h" + +struct Job { + int input; +}; + +bool stepJob(void *x) { + auto *j = (Job *)x; + return --j->input == 0; +} + +// So we can look at the disassembly more easily + +extern "C" { +void sequential(void **jobs, bool (*step)(void *), int count) { + for (int i = 0; i < count; ++i) { + while (!step(jobs[i])) + ; + } +} + +void interleaveSwapping(void **jobs, bool (*step)(void *), int remaining) { + int current = 0; + while (remaining > 0) { + bool done = step(jobs[current]); + if (done) { + jobs[current] = jobs[remaining - 1]; + --remaining; + } else { + ++current; + } + if (current == remaining) { + current = 0; + } + } +} + +void interleaveBoundedCyclicList(void **jobs, bool (*step)(void *), int count) { + if (count == 0) { + return; + } + + constexpr int kConcurrent = 16; + void *inProgress[kConcurrent]; + int nextJob[kConcurrent]; + + int started = std::min(kConcurrent, count); + for (int i = 0; i < kConcurrent; i++) { + inProgress[i] = jobs[i]; + nextJob[i] = i + 1; + } + nextJob[started - 1] = 0; + + int prevJob = started - 1; + int job = 0; + for (;;) { + bool done = step(inProgress[job]); + if (done) { + if (started == count) { + if (prevJob == job) + break; + nextJob[prevJob] = nextJob[job]; + job = prevJob; + } else { + int temp = started++; + inProgress[job] = jobs[temp]; + } + } + prevJob = job; + job = nextJob[job]; + } +} + +void interleaveCyclicList(void **jobs, bool (*step)(void *), int count) { + auto *nextJob = (int *)alloca(sizeof(int) * count); + + for (int i = 0; i < count - 1; ++i) { + nextJob[i] = i + 1; + } + nextJob[count - 1] = 0; + + int prevJob = count - 1; + int job = 0; + for (;;) { + bool done = step(jobs[job]); + if (done) { + if (prevJob == job) + break; + nextJob[prevJob] = nextJob[job]; + job = prevJob; + } + prevJob = job; + job = nextJob[job]; + } +} +} + +int main() { + ankerl::nanobench::Bench bench; + + constexpr int kNumJobs = 100; + bench.relative(true); + + Job jobs[kNumJobs]; + Job jobsCopy[kNumJobs]; + int iters = 0; + for (int i = 0; i < kNumJobs; ++i) { + jobs[i].input = rand() % 5 + 3; + iters += jobs[i].input; + } + bench.batch(iters); + + for (auto [scheduler, name] : + {std::make_pair(sequential, "sequential"), + std::make_pair(interleaveSwapping, "interleavingSwapping"), + std::make_pair(interleaveBoundedCyclicList, + "interleaveBoundedCyclicList"), + std::make_pair(interleaveCyclicList, "interleaveCyclicList")}) { + memcpy(jobsCopy, jobs, sizeof(jobs)); + void *ps[kNumJobs]; + for (int i = 0; i < kNumJobs; ++i) { + ps[i] = jobsCopy + i; + } + scheduler(ps, stepJob, kNumJobs); + for (int i = 0; i < kNumJobs; ++i) { + if (jobsCopy[i].input != 0) { + fprintf(stderr, "%s failed\n", name); + abort(); + } + } + + bench.run(name, [&]() { + memcpy(jobsCopy, jobs, sizeof(jobs)); + void *ps[kNumJobs]; + for (int i = 0; i < kNumJobs; ++i) { + ps[i] = jobsCopy + i; + } + scheduler(ps, stepJob, kNumJobs); + }); + } +}