Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ed67486077 | |||
| b376f6fdd5 | |||
| 6de63dd3fe | |||
| 3e5f13bf54 | |||
| e7e1d1f7f5 | |||
| 442658e983 | |||
| 26f602215e | |||
| 98236f81cb | |||
| 3593b72880 | |||
| 814aac4ea7 | |||
| 0550fa0016 | |||
| fe5cfb0336 | |||
| 82203515a0 | |||
| 465372c734 | |||
| 867136ff1b | |||
| 4b8f7320d3 | |||
| 6628092384 | |||
| a0a4f1afea | |||
| ca479c03ce | |||
| 0a2e133ab9 | |||
| b0b31419b0 | |||
| 5c0cc1edf5 | |||
| de47aa53b0 | |||
| 56893f9702 | |||
| e2234be10f | |||
| ce853680f2 | |||
| 5c39c1d64f | |||
| 55b73c8ddb | |||
| b9503f8258 | |||
| c4c4531bd3 | |||
| 2037d37c66 | |||
| 6fe6a244af | |||
| 8a4b370e2a | |||
| 394f09f9fb | |||
| 5e06a30357 | |||
| cb6e4292f2 | |||
| 154a48ded0 | |||
| c11b4714b5 | |||
| bc13094406 | |||
| c9d742b696 | |||
| 795ae7cb01 | |||
| 849e2d3e5c | |||
| 1560037680 | |||
| 764c31bbc8 | |||
| ee3361952a | |||
| 8a04e57353 | |||
| 7f86fdee66 | |||
| 442755d0a6 | |||
| e15b3bb137 | |||
| 311794c37e | |||
| dfa178ba19 | |||
| a16d18edfe | |||
| 2b60287448 | |||
| 0a9ac59676 | |||
| e3a77ed773 | |||
| cdf9a8a7b0 | |||
| 305dfdd52f | |||
| 7261c91492 | |||
| f11720f5ae | |||
| e2b7298af5 | |||
| 8e1e344f4b | |||
| 3634b6a59b | |||
| a3cc14c807 | |||
| 55b3275434 | |||
| 3a5b86ed9e | |||
| 159f2eef74 | |||
| 2952abe811 |
@@ -24,6 +24,14 @@ repos:
|
|||||||
entry: "^#define SHOW_MEMORY 1$"
|
entry: "^#define SHOW_MEMORY 1$"
|
||||||
language: pygrep
|
language: pygrep
|
||||||
types: [c++]
|
types: [c++]
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: sim cache misses check
|
||||||
|
name: disallow checking in SIM_CACHE_MISSES=1
|
||||||
|
description: disallow checking in SIM_CACHE_MISSES=1
|
||||||
|
entry: "^#define SIM_CACHE_MISSES 1$"
|
||||||
|
language: pygrep
|
||||||
|
types: [c++]
|
||||||
- repo: https://github.com/shellcheck-py/shellcheck-py
|
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||||
rev: a23f6b85d0fdd5bb9d564e2579e678033debbdff # frozen: v0.10.0.1
|
rev: a23f6b85d0fdd5bb9d564e2579e678033debbdff # frozen: v0.10.0.1
|
||||||
hooks:
|
hooks:
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
void showMemory(const ConflictSet &cs);
|
void showMemory(const ConflictSet &cs);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define ANKERL_NANOBENCH_IMPLEMENT
|
|
||||||
#include "third_party/nanobench.h"
|
#include "third_party/nanobench.h"
|
||||||
|
|
||||||
constexpr int kNumKeys = 1000000;
|
constexpr int kNumKeys = 1000000;
|
||||||
|
|||||||
+22
-11
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.18)
|
cmake_minimum_required(VERSION 3.18)
|
||||||
project(
|
project(
|
||||||
conflict-set
|
conflict-set
|
||||||
VERSION 0.0.12
|
VERSION 0.0.14
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
|
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
|
||||||
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
|
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
|
||||||
@@ -72,12 +72,6 @@ else()
|
|||||||
add_link_options(-Wl,--gc-sections)
|
add_link_options(-Wl,--gc-sections)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(EMSCRIPTEN)
|
|
||||||
# https://github.com/emscripten-core/emscripten/issues/15377#issuecomment-1285167486
|
|
||||||
add_link_options(-lnodefs.js -lnoderawfs.js)
|
|
||||||
add_link_options(-s ALLOW_MEMORY_GROWTH)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT USE_SIMD_FALLBACK)
|
if(NOT USE_SIMD_FALLBACK)
|
||||||
cmake_push_check_state()
|
cmake_push_check_state()
|
||||||
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
||||||
@@ -144,6 +138,8 @@ include(CTest)
|
|||||||
# disable tests if this is being used through e.g. FetchContent
|
# disable tests if this is being used through e.g. FetchContent
|
||||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
||||||
|
|
||||||
|
add_library(nanobench ${CMAKE_CURRENT_SOURCE_DIR}/nanobench.cpp)
|
||||||
|
|
||||||
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
||||||
|
|
||||||
# corpus tests, which are tests curated by libfuzzer. The goal is to get broad
|
# corpus tests, which are tests curated by libfuzzer. The goal is to get broad
|
||||||
@@ -191,6 +187,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
target_include_directories(conflict_set_main
|
target_include_directories(conflict_set_main
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
target_compile_definitions(conflict_set_main PRIVATE ENABLE_MAIN)
|
target_compile_definitions(conflict_set_main PRIVATE ENABLE_MAIN)
|
||||||
|
target_link_libraries(conflict_set_main PRIVATE nanobench)
|
||||||
|
|
||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
# libfuzzer target, to generate/manage corpus
|
# libfuzzer target, to generate/manage corpus
|
||||||
@@ -276,9 +273,15 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
|
|
||||||
find_program(VALGRIND_EXE valgrind)
|
find_program(VALGRIND_EXE valgrind)
|
||||||
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
|
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
|
||||||
add_test(NAME conflict_set_blackbox_valgrind
|
list(LENGTH CORPUS_TESTS len)
|
||||||
COMMAND ${VALGRIND_EXE} --error-exitcode=99 --
|
math(EXPR last "${len} - 1")
|
||||||
$<TARGET_FILE:driver> ${CORPUS_TESTS})
|
set(partition_size 100)
|
||||||
|
foreach(i RANGE 0 ${last} ${partition_size})
|
||||||
|
list(SUBLIST CORPUS_TESTS ${i} ${partition_size} partition)
|
||||||
|
add_test(NAME conflict_set_blackbox_valgrind_${i}
|
||||||
|
COMMAND ${VALGRIND_EXE} --error-exitcode=99 --
|
||||||
|
$<TARGET_FILE:driver> ${partition})
|
||||||
|
endforeach()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# api smoke tests
|
# api smoke tests
|
||||||
@@ -330,7 +333,7 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
|
|
||||||
# bench
|
# bench
|
||||||
add_executable(conflict_set_bench Bench.cpp)
|
add_executable(conflict_set_bench Bench.cpp)
|
||||||
target_link_libraries(conflict_set_bench PRIVATE ${PROJECT_NAME})
|
target_link_libraries(conflict_set_bench PRIVATE ${PROJECT_NAME} nanobench)
|
||||||
set_target_properties(conflict_set_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
set_target_properties(conflict_set_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||||
add_executable(real_data_bench RealDataBench.cpp)
|
add_executable(real_data_bench RealDataBench.cpp)
|
||||||
target_link_libraries(real_data_bench PRIVATE ${PROJECT_NAME})
|
target_link_libraries(real_data_bench PRIVATE ${PROJECT_NAME})
|
||||||
@@ -345,6 +348,14 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
add_executable(server_bench ServerBench.cpp)
|
add_executable(server_bench ServerBench.cpp)
|
||||||
target_link_libraries(server_bench PRIVATE ${PROJECT_NAME})
|
target_link_libraries(server_bench PRIVATE ${PROJECT_NAME})
|
||||||
set_target_properties(server_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
set_target_properties(server_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||||
|
|
||||||
|
add_executable(interleaving_test InterleavingTest.cpp)
|
||||||
|
# work around lack of musttail for gcc
|
||||||
|
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
target_compile_options(interleaving_test PRIVATE -Og
|
||||||
|
-foptimize-sibling-calls)
|
||||||
|
endif()
|
||||||
|
target_link_libraries(interleaving_test PRIVATE nanobench)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# packaging
|
# packaging
|
||||||
|
|||||||
+604
-365
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,256 @@
|
|||||||
|
#include <alloca.h>
|
||||||
|
#include <cassert>
|
||||||
|
#ifdef __x86_64__
|
||||||
|
#include <immintrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "third_party/nanobench.h"
|
||||||
|
|
||||||
|
struct Job {
|
||||||
|
int *input;
|
||||||
|
// Returned void* is a function pointer to the next continuation. We have to
|
||||||
|
// use void* because otherwise the type would be recursive.
|
||||||
|
typedef void *(*continuation)(Job *);
|
||||||
|
continuation next;
|
||||||
|
};
|
||||||
|
|
||||||
|
void *stepJob(Job *j) {
|
||||||
|
auto done = --(*j->input) == 0;
|
||||||
|
#ifdef __x86_64__
|
||||||
|
_mm_clflush(j->input);
|
||||||
|
#endif
|
||||||
|
return done ? nullptr : (void *)stepJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
void sequential(Job **jobs, int count) {
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
do {
|
||||||
|
jobs[i]->next = (Job::continuation)jobs[i]->next(jobs[i]);
|
||||||
|
} while (jobs[i]->next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sequentialNoFuncPtr(Job **jobs, int count) {
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
while (stepJob(jobs[i]))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void interleaveSwapping(Job **jobs, int remaining) {
|
||||||
|
int current = 0;
|
||||||
|
while (remaining > 0) {
|
||||||
|
auto next = (Job::continuation)jobs[current]->next(jobs[current]);
|
||||||
|
jobs[current]->next = next;
|
||||||
|
if (next == nullptr) {
|
||||||
|
jobs[current] = jobs[remaining - 1];
|
||||||
|
--remaining;
|
||||||
|
} else {
|
||||||
|
++current;
|
||||||
|
}
|
||||||
|
if (current == remaining) {
|
||||||
|
current = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void interleaveBoundedCyclicList(Job **jobs, int count) {
|
||||||
|
if (count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int kConcurrent = 32;
|
||||||
|
Job *inProgress[kConcurrent];
|
||||||
|
int nextJob[kConcurrent];
|
||||||
|
|
||||||
|
int started = std::min(kConcurrent, count);
|
||||||
|
for (int i = 0; i < started; i++) {
|
||||||
|
inProgress[i] = jobs[i];
|
||||||
|
nextJob[i] = i + 1;
|
||||||
|
}
|
||||||
|
nextJob[started - 1] = 0;
|
||||||
|
|
||||||
|
int prevJob = started - 1;
|
||||||
|
int job = 0;
|
||||||
|
for (;;) {
|
||||||
|
auto next = (Job::continuation)inProgress[job]->next(inProgress[job]);
|
||||||
|
inProgress[job]->next = next;
|
||||||
|
if (next == nullptr) {
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef __has_attribute
|
||||||
|
#define __has_attribute(x) 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if __has_attribute(musttail)
|
||||||
|
#define MUSTTAIL __attribute__((musttail))
|
||||||
|
#else
|
||||||
|
#define MUSTTAIL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
constexpr static int kConcurrent = 32;
|
||||||
|
Job **jobs;
|
||||||
|
Job *inProgress[kConcurrent];
|
||||||
|
void (*continuation[kConcurrent])(Context *, int64_t prevJob, int64_t job,
|
||||||
|
int64_t started, int64_t count);
|
||||||
|
int nextJob[kConcurrent];
|
||||||
|
};
|
||||||
|
|
||||||
|
void keepGoing(Context *context, int64_t prevJob, int64_t job, int64_t started,
|
||||||
|
int64_t count) {
|
||||||
|
prevJob = job;
|
||||||
|
job = context->nextJob[job];
|
||||||
|
MUSTTAIL return context->continuation[job](context, prevJob, job, started,
|
||||||
|
count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stepJobTailCall(Context *context, int64_t prevJob, int64_t job,
|
||||||
|
int64_t started, int64_t count);
|
||||||
|
|
||||||
|
void complete(Context *context, int64_t prevJob, int64_t job, int64_t started,
|
||||||
|
int64_t count) {
|
||||||
|
if (started == count) {
|
||||||
|
if (prevJob == job) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context->nextJob[prevJob] = context->nextJob[job];
|
||||||
|
job = prevJob;
|
||||||
|
} else {
|
||||||
|
context->inProgress[job] = context->jobs[started++];
|
||||||
|
context->continuation[job] = stepJobTailCall;
|
||||||
|
}
|
||||||
|
prevJob = job;
|
||||||
|
job = context->nextJob[job];
|
||||||
|
MUSTTAIL return context->continuation[job](context, prevJob, job, started,
|
||||||
|
count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stepJobTailCall(Context *context, int64_t prevJob, int64_t job,
|
||||||
|
int64_t started, int64_t count) {
|
||||||
|
auto *j = context->inProgress[job];
|
||||||
|
auto done = --(*j->input) == 0;
|
||||||
|
#ifdef __x86_64__
|
||||||
|
_mm_clflush(j->input);
|
||||||
|
#endif
|
||||||
|
if (done) {
|
||||||
|
MUSTTAIL return complete(context, prevJob, job, started, count);
|
||||||
|
} else {
|
||||||
|
context->continuation[job] = stepJobTailCall;
|
||||||
|
MUSTTAIL return keepGoing(context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void useTailCalls(Job **jobs, int count) {
|
||||||
|
if (count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Context context;
|
||||||
|
context.jobs = jobs;
|
||||||
|
int64_t started = std::min(Context::kConcurrent, count);
|
||||||
|
for (int i = 0; i < started; i++) {
|
||||||
|
context.inProgress[i] = jobs[i];
|
||||||
|
context.nextJob[i] = i + 1;
|
||||||
|
context.continuation[i] = stepJobTailCall;
|
||||||
|
}
|
||||||
|
context.nextJob[started - 1] = 0;
|
||||||
|
int prevJob = started - 1;
|
||||||
|
int job = 0;
|
||||||
|
return context.continuation[job](&context, prevJob, job, started, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void interleaveCyclicList(Job **jobs, 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 (;;) {
|
||||||
|
auto next = (Job::continuation)jobs[job]->next(jobs[job]);
|
||||||
|
jobs[job]->next = next;
|
||||||
|
if (next == nullptr) {
|
||||||
|
if (prevJob == job)
|
||||||
|
break;
|
||||||
|
nextJob[prevJob] = nextJob[job];
|
||||||
|
job = prevJob;
|
||||||
|
}
|
||||||
|
prevJob = job;
|
||||||
|
job = nextJob[job];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
ankerl::nanobench::Bench bench;
|
||||||
|
|
||||||
|
constexpr int kNumJobs = 10000;
|
||||||
|
bench.relative(true);
|
||||||
|
|
||||||
|
Job jobs[kNumJobs];
|
||||||
|
Job jobsCopy[kNumJobs];
|
||||||
|
int iters = 0;
|
||||||
|
int originalInput[kNumJobs];
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
originalInput[i] = rand() % 5 + 3;
|
||||||
|
jobs[i].input = new int{originalInput[i]};
|
||||||
|
jobs[i].next = stepJob;
|
||||||
|
iters += *jobs[i].input;
|
||||||
|
}
|
||||||
|
bench.batch(iters);
|
||||||
|
|
||||||
|
for (auto [scheduler, name] :
|
||||||
|
{std::make_pair(sequentialNoFuncPtr, "sequentialNoFuncPtr"),
|
||||||
|
std::make_pair(sequential, "sequential"),
|
||||||
|
std::make_pair(useTailCalls, "useTailCalls"),
|
||||||
|
std::make_pair(interleaveSwapping, "interleavingSwapping"),
|
||||||
|
std::make_pair(interleaveBoundedCyclicList,
|
||||||
|
"interleaveBoundedCyclicList"),
|
||||||
|
std::make_pair(interleaveCyclicList, "interleaveCyclicList")}) {
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
*jobs[i].input = originalInput[i];
|
||||||
|
}
|
||||||
|
memcpy(jobsCopy, jobs, sizeof(jobs));
|
||||||
|
Job *ps[kNumJobs];
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
ps[i] = jobsCopy + i;
|
||||||
|
}
|
||||||
|
scheduler(ps, kNumJobs);
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
if (*jobsCopy[i].input != 0) {
|
||||||
|
fprintf(stderr, "%s failed\n", name);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bench.run(name, [&]() {
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
*jobs[i].input = originalInput[i];
|
||||||
|
}
|
||||||
|
memcpy(jobsCopy, jobs, sizeof(jobs));
|
||||||
|
Job *ps[kNumJobs];
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
ps[i] = jobsCopy + i;
|
||||||
|
}
|
||||||
|
scheduler(ps, kNumJobs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for (int i = 0; i < kNumJobs; ++i) {
|
||||||
|
delete jobs[i].input;
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -20,7 +20,6 @@ using namespace weaselab;
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <callgrind.h>
|
#include <callgrind.h>
|
||||||
|
|
||||||
@@ -748,7 +747,10 @@ struct TestDriver {
|
|||||||
fprintf(stderr, "%p Set oldest version: %" PRId64 "\n", this,
|
fprintf(stderr, "%p Set oldest version: %" PRId64 "\n", this,
|
||||||
oldestVersion);
|
oldestVersion);
|
||||||
#endif
|
#endif
|
||||||
|
CALLGRIND_START_INSTRUMENTATION;
|
||||||
cs.setOldestVersion(oldestVersion);
|
cs.setOldestVersion(oldestVersion);
|
||||||
|
CALLGRIND_STOP_INSTRUMENTATION;
|
||||||
|
|
||||||
if constexpr (kEnableAssertions) {
|
if constexpr (kEnableAssertions) {
|
||||||
refImpl.setOldestVersion(oldestVersion);
|
refImpl.setOldestVersion(oldestVersion);
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+12
-1
@@ -48,6 +48,17 @@ pipeline {
|
|||||||
recordIssues(tools: [clang()])
|
recordIssues(tools: [clang()])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stage('64 bit versions') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1")
|
||||||
|
}
|
||||||
|
}
|
||||||
stage('Debug') {
|
stage('Debug') {
|
||||||
agent {
|
agent {
|
||||||
dockerfile {
|
dockerfile {
|
||||||
@@ -118,7 +129,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
script {
|
script {
|
||||||
filter_args = "-f ConflictSet.cpp -f LongestCommonPrefix.h"
|
filter_args = "-f ConflictSet.cpp -f LongestCommonPrefix.h -f Metrics.h"
|
||||||
}
|
}
|
||||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON")
|
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON")
|
||||||
sh """
|
sh """
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ConflictSet.h"
|
||||||
|
#include "Internal.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
struct Metric {
|
||||||
|
Metric *prev;
|
||||||
|
const char *name;
|
||||||
|
const char *help;
|
||||||
|
weaselab::ConflictSet::MetricsV1::Type type;
|
||||||
|
std::atomic<int64_t> value;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Metric(Metric *&metricList, int &metricsCount, const char *name,
|
||||||
|
const char *help, weaselab::ConflictSet::MetricsV1::Type type)
|
||||||
|
: prev(std::exchange(metricList, this)), name(name), help(help),
|
||||||
|
type(type), value(0) {
|
||||||
|
++metricsCount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Gauge : private Metric {
|
||||||
|
Gauge(Metric *&metricList, int &metricsCount, const char *name,
|
||||||
|
const char *help)
|
||||||
|
: Metric(metricList, metricsCount, name, help,
|
||||||
|
weaselab::ConflictSet::MetricsV1::Gauge) {}
|
||||||
|
|
||||||
|
void set(int64_t value) {
|
||||||
|
this->value.store(value, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Counter : private Metric {
|
||||||
|
Counter(Metric *&metricList, int &metricsCount, const char *name,
|
||||||
|
const char *help)
|
||||||
|
: Metric(metricList, metricsCount, name, help,
|
||||||
|
weaselab::ConflictSet::MetricsV1::Counter) {}
|
||||||
|
// Expensive. Accumulate locally and then call add instead of repeatedly
|
||||||
|
// calling add.
|
||||||
|
void add(int64_t value) {
|
||||||
|
assert(value >= 0);
|
||||||
|
static_assert(std::atomic<int64_t>::is_always_lock_free);
|
||||||
|
this->value.fetch_add(value, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline weaselab::ConflictSet::MetricsV1 *initMetrics(Metric *metricsList,
|
||||||
|
int metricsCount) {
|
||||||
|
weaselab::ConflictSet::MetricsV1 *metrics =
|
||||||
|
(weaselab::ConflictSet::MetricsV1 *)safe_malloc(metricsCount *
|
||||||
|
sizeof(metrics[0]));
|
||||||
|
for (auto [i, m] = std::make_tuple(metricsCount - 1, metricsList); i >= 0;
|
||||||
|
--i, m = m->prev) {
|
||||||
|
metrics[i].name = m->name;
|
||||||
|
metrics[i].help = m->help;
|
||||||
|
metrics[i].p = m;
|
||||||
|
metrics[i].type = m->type;
|
||||||
|
}
|
||||||
|
return metrics;
|
||||||
|
}
|
||||||
@@ -24,15 +24,15 @@ Hardware for all benchmarks is an AMD Ryzen 9 7900 with (2x32GB) 5600MT/s CL28-3
|
|||||||
|
|
||||||
| ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|
| ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|
||||||
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
||||||
| 11.04 | 90,614,308.12 | 0.8% | 180.38 | 55.13 | 3.272 | 41.51 | 0.4% | 0.01 | `point reads`
|
| 12.88 | 77,653,350.77 | 0.5% | 185.37 | 64.45 | 2.876 | 41.51 | 0.4% | 0.01 | `point reads`
|
||||||
| 14.96 | 66,843,629.12 | 0.4% | 274.41 | 74.73 | 3.672 | 55.05 | 0.3% | 0.01 | `prefix reads`
|
| 14.67 | 68,179,354.49 | 0.1% | 271.44 | 73.40 | 3.698 | 53.70 | 0.3% | 0.01 | `prefix reads`
|
||||||
| 37.06 | 26,982,847.61 | 0.2% | 791.04 | 185.28 | 4.269 | 142.67 | 0.2% | 0.01 | `range reads`
|
| 34.84 | 28,701,444.36 | 0.3% | 715.74 | 175.27 | 4.084 | 127.30 | 0.2% | 0.01 | `range reads`
|
||||||
| 17.89 | 55,887,365.73 | 0.6% | 335.54 | 89.79 | 3.737 | 43.84 | 0.4% | 0.01 | `point writes`
|
| 17.12 | 58,422,988.28 | 0.2% | 314.30 | 86.11 | 3.650 | 39.82 | 0.4% | 0.01 | `point writes`
|
||||||
| 31.85 | 31,394,336.65 | 0.3% | 615.32 | 159.63 | 3.855 | 87.69 | 0.2% | 0.01 | `prefix writes`
|
| 31.42 | 31,830,804.65 | 0.1% | 591.06 | 158.07 | 3.739 | 82.67 | 0.2% | 0.01 | `prefix writes`
|
||||||
| 36.17 | 27,647,221.45 | 0.6% | 705.11 | 182.80 | 3.857 | 100.62 | 0.1% | 0.01 | `range writes`
|
| 37.37 | 26,759,432.70 | 2.2% | 681.98 | 188.95 | 3.609 | 96.10 | 0.1% | 0.01 | `range writes`
|
||||||
| 79.01 | 12,656,457.78 | 0.7% | 1,498.35 | 402.46 | 3.723 | 270.50 | 0.1% | 0.01 | `monotonic increasing point writes`
|
| 76.72 | 13,035,140.63 | 2.3% | 1,421.28 | 387.17 | 3.671 | 257.76 | 0.1% | 0.01 | `monotonic increasing point writes`
|
||||||
| 303,667.50 | 3,293.08 | 1.1% | 3,931,273.00 | 1,612,702.50 | 2.438 | 806,223.33 | 0.0% | 0.01 | `worst case for radix tree`
|
| 297,452.00 | 3,361.89 | 0.9% | 3,508,083.00 | 1,500,834.67 | 2.337 | 727,525.33 | 0.1% | 0.01 | `worst case for radix tree`
|
||||||
| 83.70 | 11,947,443.83 | 0.7% | 1,738.03 | 429.06 | 4.051 | 270.01 | 0.0% | 0.01 | `create and destroy`
|
| 87.70 | 11,402,490.60 | 1.0% | 1,795.00 | 442.09 | 4.060 | 297.00 | 0.0% | 0.01 | `create and destroy`
|
||||||
|
|
||||||
# "Real data" test
|
# "Real data" test
|
||||||
|
|
||||||
@@ -47,7 +47,7 @@ Check: 4.47891 seconds, 364.05 MB/s, Add: 4.55599 seconds, 123.058 MB/s, Gc rati
|
|||||||
## radix tree
|
## radix tree
|
||||||
|
|
||||||
```
|
```
|
||||||
Check: 0.958985 seconds, 1700.28 MB/s, Add: 1.35083 seconds, 415.044 MB/s, Gc ratio: 44.4768%, Peak idle memory: 2.33588e+06
|
Check: 0.953012 seconds, 1710.94 MB/s, Add: 1.30025 seconds, 431.188 MB/s, Gc ratio: 43.9816%, Peak idle memory: 2.28375e+06
|
||||||
```
|
```
|
||||||
|
|
||||||
## hash table
|
## hash table
|
||||||
|
|||||||
+63
-82
@@ -1,4 +1,5 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -6,8 +7,10 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
#include <sys/resource.h>
|
#include <sys/resource.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
#include <sys/uio.h>
|
#include <sys/uio.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -19,78 +22,55 @@
|
|||||||
|
|
||||||
std::atomic<int64_t> transactions;
|
std::atomic<int64_t> transactions;
|
||||||
|
|
||||||
constexpr int kBaseSearchDepth = 32;
|
|
||||||
constexpr int kWindowSize = 10000000;
|
constexpr int kWindowSize = 10000000;
|
||||||
|
|
||||||
std::basic_string<uint8_t> numToKey(int64_t num) {
|
constexpr int kNumPrefixes = 250000;
|
||||||
std::basic_string<uint8_t> result;
|
|
||||||
result.resize(kBaseSearchDepth + sizeof(int64_t));
|
std::string makeKey(int64_t num, int suffixLen) {
|
||||||
memset(result.data(), 0, kBaseSearchDepth);
|
std::string result;
|
||||||
|
result.resize(sizeof(int64_t) + suffixLen);
|
||||||
int64_t be = __builtin_bswap64(num);
|
int64_t be = __builtin_bswap64(num);
|
||||||
memcpy(result.data() + kBaseSearchDepth, &be, sizeof(int64_t));
|
memcpy(result.data(), &be, sizeof(int64_t));
|
||||||
|
memset(result.data() + sizeof(int64_t), 0, suffixLen);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void workload(weaselab::ConflictSet *cs) {
|
void workload(weaselab::ConflictSet *cs) {
|
||||||
int64_t version = kWindowSize;
|
int64_t version = kWindowSize;
|
||||||
cs->addWrites(nullptr, 0, version);
|
for (int i = 0; i < kNumPrefixes; ++i) {
|
||||||
|
for (int j = 0; j < 50; ++j) {
|
||||||
|
weaselab::ConflictSet::WriteRange wr;
|
||||||
|
auto k = makeKey(i, j);
|
||||||
|
wr.begin.p = (const uint8_t *)k.data();
|
||||||
|
wr.begin.len = k.size();
|
||||||
|
wr.end.len = 0;
|
||||||
|
cs->addWrites(&wr, 1, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
++version;
|
||||||
|
for (int i = 0; i < kNumPrefixes; ++i) {
|
||||||
|
weaselab::ConflictSet::WriteRange wr;
|
||||||
|
auto k = makeKey(i, 50);
|
||||||
|
wr.begin.p = (const uint8_t *)k.data();
|
||||||
|
wr.begin.len = k.size();
|
||||||
|
wr.end.len = 0;
|
||||||
|
cs->addWrites(&wr, 1, version);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<weaselab::ConflictSet::Result> results(10);
|
||||||
for (;; transactions.fetch_add(1, std::memory_order_relaxed)) {
|
for (;; transactions.fetch_add(1, std::memory_order_relaxed)) {
|
||||||
// Reads
|
std::vector<std::string> keys(10);
|
||||||
{
|
for (auto &k : keys) {
|
||||||
auto beginK = numToKey(version - kWindowSize);
|
k = makeKey(rand() % kNumPrefixes, 49);
|
||||||
auto endK = numToKey(version - 1);
|
|
||||||
auto pointRv = version - kWindowSize + rand() % kWindowSize + 1;
|
|
||||||
auto pointK = numToKey(pointRv);
|
|
||||||
weaselab::ConflictSet::ReadRange reads[] = {
|
|
||||||
{
|
|
||||||
{pointK.data(), int(pointK.size())},
|
|
||||||
{nullptr, 0},
|
|
||||||
pointRv,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{beginK.data(), int(beginK.size())},
|
|
||||||
{endK.data(), int(endK.size())},
|
|
||||||
version - 2,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
weaselab::ConflictSet::Result result[sizeof(reads) / sizeof(reads[0])];
|
|
||||||
cs->check(reads, result, sizeof(reads) / sizeof(reads[0]));
|
|
||||||
// for (int i = 0; i < sizeof(reads) / sizeof(reads[0]); ++i) {
|
|
||||||
// if (result[i] != weaselab::ConflictSet::Commit) {
|
|
||||||
// fprintf(stderr, "Unexpected conflict: [%s, %s) @ %" PRId64 "\n",
|
|
||||||
// printable(reads[i].begin).c_str(),
|
|
||||||
// printable(reads[i].end).c_str(), reads[i].readVersion);
|
|
||||||
// abort();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
// Writes
|
std::vector<weaselab::ConflictSet::ReadRange> reads(10);
|
||||||
{
|
for (int i = 0; i < reads.size(); ++i) {
|
||||||
weaselab::ConflictSet::WriteRange w;
|
reads[i].begin.p = (const uint8_t *)(keys[i].data());
|
||||||
auto k = numToKey(version);
|
reads[i].begin.len = keys[i].size();
|
||||||
w.begin.p = k.data();
|
reads[i].end.len = 0;
|
||||||
w.end.len = 0;
|
reads[i].readVersion = version - 1;
|
||||||
if (version % (kWindowSize / 2) == 0) {
|
|
||||||
for (int l = 0; l <= k.size(); ++l) {
|
|
||||||
w.begin.len = l;
|
|
||||||
cs->addWrites(&w, 1, version);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
w.begin.len = k.size();
|
|
||||||
cs->addWrites(&w, 1, version);
|
|
||||||
int64_t beginN = version - kWindowSize + rand() % kWindowSize;
|
|
||||||
auto b = numToKey(beginN);
|
|
||||||
auto e = numToKey(beginN + 1000);
|
|
||||||
w.begin.p = b.data();
|
|
||||||
w.begin.len = b.size();
|
|
||||||
w.end.p = e.data();
|
|
||||||
w.end.len = e.size();
|
|
||||||
cs->addWrites(&w, 1, version);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// GC
|
cs->check(reads.data(), results.data(), 10);
|
||||||
cs->setOldestVersion(version - kWindowSize);
|
|
||||||
++version;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,18 +146,11 @@ double toSeconds(timeval t) {
|
|||||||
return double(t.tv_sec) + double(t.tv_usec) * 1e-6;
|
return double(t.tv_sec) + double(t.tv_usec) * 1e-6;
|
||||||
}
|
}
|
||||||
|
|
||||||
#include <linux/perf_event.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#include <sys/syscall.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
#include <linux/perf_event.h>
|
||||||
struct PerfCounter {
|
struct PerfCounter {
|
||||||
PerfCounter(int type, int config, const std::string &labels = {})
|
PerfCounter(int type, int config, const std::string &labels = {},
|
||||||
|
int groupLeaderFd = -1)
|
||||||
: labels(labels) {
|
: labels(labels) {
|
||||||
struct perf_event_attr pe;
|
struct perf_event_attr pe;
|
||||||
|
|
||||||
@@ -189,7 +162,10 @@ struct PerfCounter {
|
|||||||
pe.exclude_kernel = 1;
|
pe.exclude_kernel = 1;
|
||||||
pe.exclude_hv = 1;
|
pe.exclude_hv = 1;
|
||||||
|
|
||||||
fd = perf_event_open(&pe, 0, -1, -1, 0);
|
fd = perf_event_open(&pe, 0, -1, groupLeaderFd, 0);
|
||||||
|
if (fd < 0 && errno != ENOENT && errno != EINVAL) {
|
||||||
|
perror(labels.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t total() const {
|
int64_t total() const {
|
||||||
@@ -217,6 +193,7 @@ struct PerfCounter {
|
|||||||
|
|
||||||
bool ok() const { return fd >= 0; }
|
bool ok() const { return fd >= 0; }
|
||||||
const std::string &getLabels() const { return labels; }
|
const std::string &getLabels() const { return labels; }
|
||||||
|
int getFd() const { return fd; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int fd;
|
int fd;
|
||||||
@@ -229,11 +206,6 @@ private:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
#else
|
|
||||||
struct PerfCounter {
|
|
||||||
PerfCounter(int, int) {}
|
|
||||||
int64_t total() { return 0; }
|
|
||||||
};
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
@@ -248,8 +220,10 @@ int main(int argc, char **argv) {
|
|||||||
int metricsCount;
|
int metricsCount;
|
||||||
cs.getMetricsV1(&metrics, &metricsCount);
|
cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
PerfCounter instructions{PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS};
|
PerfCounter instructions{PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS};
|
||||||
PerfCounter cycles{PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES};
|
PerfCounter cycles{PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES, "",
|
||||||
|
instructions.getFd()};
|
||||||
|
|
||||||
std::vector<PerfCounter> cacheCounters;
|
std::vector<PerfCounter> cacheCounters;
|
||||||
for (auto [id, idStr] : std::initializer_list<std::pair<int, std::string>>{
|
for (auto [id, idStr] : std::initializer_list<std::pair<int, std::string>>{
|
||||||
@@ -257,8 +231,7 @@ int main(int argc, char **argv) {
|
|||||||
{PERF_COUNT_HW_CACHE_L1I, "l1i"},
|
{PERF_COUNT_HW_CACHE_L1I, "l1i"},
|
||||||
{PERF_COUNT_HW_CACHE_LL, "ll"},
|
{PERF_COUNT_HW_CACHE_LL, "ll"},
|
||||||
{PERF_COUNT_HW_CACHE_DTLB, "dtlb"},
|
{PERF_COUNT_HW_CACHE_DTLB, "dtlb"},
|
||||||
// Somehow was showing a miss rate > 1 /shrug
|
{PERF_COUNT_HW_CACHE_ITLB, "itlb"},
|
||||||
// {PERF_COUNT_HW_CACHE_ITLB, "itlb"},
|
|
||||||
{PERF_COUNT_HW_CACHE_BPU, "bpu"},
|
{PERF_COUNT_HW_CACHE_BPU, "bpu"},
|
||||||
{PERF_COUNT_HW_CACHE_NODE, "node"},
|
{PERF_COUNT_HW_CACHE_NODE, "node"},
|
||||||
}) {
|
}) {
|
||||||
@@ -268,6 +241,7 @@ int main(int argc, char **argv) {
|
|||||||
{PERF_COUNT_HW_CACHE_OP_WRITE, "write"},
|
{PERF_COUNT_HW_CACHE_OP_WRITE, "write"},
|
||||||
{PERF_COUNT_HW_CACHE_OP_PREFETCH, "prefetch"},
|
{PERF_COUNT_HW_CACHE_OP_PREFETCH, "prefetch"},
|
||||||
}) {
|
}) {
|
||||||
|
int groupLeaderFd = -1;
|
||||||
for (auto [result, resultStr] :
|
for (auto [result, resultStr] :
|
||||||
std::initializer_list<std::pair<int, std::string>>{
|
std::initializer_list<std::pair<int, std::string>>{
|
||||||
{PERF_COUNT_HW_CACHE_RESULT_MISS, "miss"},
|
{PERF_COUNT_HW_CACHE_RESULT_MISS, "miss"},
|
||||||
@@ -276,14 +250,19 @@ int main(int argc, char **argv) {
|
|||||||
auto labels = "{id=\"" + idStr + "\", op=\"" + opStr +
|
auto labels = "{id=\"" + idStr + "\", op=\"" + opStr +
|
||||||
"\", result=\"" + resultStr + "\"}";
|
"\", result=\"" + resultStr + "\"}";
|
||||||
cacheCounters.emplace_back(PERF_TYPE_HW_CACHE,
|
cacheCounters.emplace_back(PERF_TYPE_HW_CACHE,
|
||||||
id | (op << 8) | (result << 16), labels);
|
id | (op << 8) | (result << 16), labels,
|
||||||
|
groupLeaderFd);
|
||||||
if (!cacheCounters.back().ok()) {
|
if (!cacheCounters.back().ok()) {
|
||||||
fprintf(stderr, "Could not open cache event: %s\n", labels.c_str());
|
|
||||||
cacheCounters.pop_back();
|
cacheCounters.pop_back();
|
||||||
|
} else {
|
||||||
|
if (groupLeaderFd == -1) {
|
||||||
|
groupLeaderFd = cacheCounters.back().getFd();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
auto w = std::thread{workload, &cs};
|
auto w = std::thread{workload, &cs};
|
||||||
|
|
||||||
@@ -312,6 +291,7 @@ int main(int argc, char **argv) {
|
|||||||
"transactions_total ";
|
"transactions_total ";
|
||||||
body += std::to_string(transactions.load(std::memory_order_relaxed));
|
body += std::to_string(transactions.load(std::memory_order_relaxed));
|
||||||
body += "\n";
|
body += "\n";
|
||||||
|
#ifdef __linux__
|
||||||
body += "# HELP instructions_total Total number of instructions\n"
|
body += "# HELP instructions_total Total number of instructions\n"
|
||||||
"# TYPE instructions_total counter\n"
|
"# TYPE instructions_total counter\n"
|
||||||
"instructions_total ";
|
"instructions_total ";
|
||||||
@@ -328,6 +308,7 @@ int main(int argc, char **argv) {
|
|||||||
body += "cache_event_total" + counter.getLabels() + " " +
|
body += "cache_event_total" + counter.getLabels() + " " +
|
||||||
std::to_string(counter.total()) + "\n";
|
std::to_string(counter.total()) + "\n";
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
for (int i = 0; i < metricsCount; ++i) {
|
for (int i = 0; i < metricsCount; ++i) {
|
||||||
body += "# HELP ";
|
body += "# HELP ";
|
||||||
|
|||||||
+126
-50
@@ -22,9 +22,11 @@
|
|||||||
|
|
||||||
#include "ConflictSet.h"
|
#include "ConflictSet.h"
|
||||||
#include "Internal.h"
|
#include "Internal.h"
|
||||||
|
#include "Metrics.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <span>
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) {
|
std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) {
|
||||||
auto result =
|
auto result =
|
||||||
@@ -115,15 +117,6 @@ bool operator==(const KeyInfo &lhs, const KeyInfo &rhs) {
|
|||||||
return !(lhs < rhs || rhs < lhs);
|
return !(lhs < rhs || rhs < lhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
void swapSort(std::vector<KeyInfo> &points, int a, int b) {
|
|
||||||
if (points[b] < points[a]) {
|
|
||||||
KeyInfo temp;
|
|
||||||
temp = points[a];
|
|
||||||
points[a] = points[b];
|
|
||||||
points[b] = temp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SortTask {
|
struct SortTask {
|
||||||
int begin;
|
int begin;
|
||||||
int size;
|
int size;
|
||||||
@@ -183,13 +176,6 @@ void sortPoints(std::vector<KeyInfo> &points) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static thread_local uint32_t g_seed = 0;
|
|
||||||
|
|
||||||
static inline int skfastrand() {
|
|
||||||
g_seed = g_seed * 1664525L + 1013904223L;
|
|
||||||
return g_seed;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int compare(const StringRef &a, const StringRef &b) {
|
static int compare(const StringRef &a, const StringRef &b) {
|
||||||
int c = memcmp(a.data(), b.data(), std::min(a.size(), b.size()));
|
int c = memcmp(a.data(), b.data(), std::min(a.size(), b.size()));
|
||||||
if (c < 0)
|
if (c < 0)
|
||||||
@@ -215,20 +201,24 @@ struct ReadConflictRange {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static constexpr int MaxLevels = 26;
|
||||||
|
|
||||||
|
struct RandomLevel {
|
||||||
|
explicit RandomLevel(uint32_t seed) : seed(seed) {}
|
||||||
|
|
||||||
|
int next() {
|
||||||
|
int result = __builtin_clz(seed | (uint32_t(-1) >> (MaxLevels - 1)));
|
||||||
|
seed = seed * 1664525L + 1013904223L;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t seed;
|
||||||
|
};
|
||||||
|
|
||||||
class SkipList {
|
class SkipList {
|
||||||
private:
|
private:
|
||||||
static constexpr int MaxLevels = 26;
|
RandomLevel randomLevel{0};
|
||||||
|
|
||||||
int randomLevel() const {
|
|
||||||
uint32_t i = uint32_t(skfastrand()) >> (32 - (MaxLevels - 1));
|
|
||||||
int level = 0;
|
|
||||||
while (i & 1) {
|
|
||||||
i >>= 1;
|
|
||||||
level++;
|
|
||||||
}
|
|
||||||
assert(level < MaxLevels);
|
|
||||||
return level;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Represent a node in the SkipList. The node has multiple (i.e., level)
|
// Represent a node in the SkipList. The node has multiple (i.e., level)
|
||||||
// pointers to other nodes, and keeps a record of the max versions for each
|
// pointers to other nodes, and keeps a record of the max versions for each
|
||||||
@@ -426,27 +416,33 @@ public:
|
|||||||
}
|
}
|
||||||
void swap(SkipList &other) { std::swap(header, other.header); }
|
void swap(SkipList &other) { std::swap(header, other.header); }
|
||||||
|
|
||||||
void addConflictRanges(const Finger *fingers, int rangeCount,
|
// Returns the change in the number of entries
|
||||||
Version version) {
|
int64_t addConflictRanges(const Finger *fingers, int rangeCount,
|
||||||
|
Version version) {
|
||||||
|
int64_t result = rangeCount;
|
||||||
for (int r = rangeCount - 1; r >= 0; r--) {
|
for (int r = rangeCount - 1; r >= 0; r--) {
|
||||||
const Finger &startF = fingers[r * 2];
|
const Finger &startF = fingers[r * 2];
|
||||||
const Finger &endF = fingers[r * 2 + 1];
|
const Finger &endF = fingers[r * 2 + 1];
|
||||||
|
|
||||||
if (endF.found() == nullptr)
|
if (endF.found() == nullptr) {
|
||||||
|
++result;
|
||||||
insert(endF, endF.finger[0]->getMaxVersion(0));
|
insert(endF, endF.finger[0]->getMaxVersion(0));
|
||||||
|
}
|
||||||
|
|
||||||
remove(startF, endF);
|
result -= remove(startF, endF);
|
||||||
insert(startF, version);
|
insert(startF, version);
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void detectConflicts(ReadConflictRange *ranges, int count,
|
// Return number of iterations of main loop
|
||||||
ConflictSet::Result *transactionConflictStatus) const {
|
int detectConflicts(ReadConflictRange *ranges, int count,
|
||||||
|
ConflictSet::Result *transactionConflictStatus) const {
|
||||||
const int M = 16;
|
const int M = 16;
|
||||||
int nextJob[M];
|
int nextJob[M];
|
||||||
CheckMax inProgress[M];
|
CheckMax inProgress[M];
|
||||||
if (!count)
|
if (!count)
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
int started = std::min(M, count);
|
int started = std::min(M, count);
|
||||||
for (int i = 0; i < started; i++) {
|
for (int i = 0; i < started; i++) {
|
||||||
@@ -457,8 +453,9 @@ public:
|
|||||||
|
|
||||||
int prevJob = started - 1;
|
int prevJob = started - 1;
|
||||||
int job = 0;
|
int job = 0;
|
||||||
|
int iters = 0;
|
||||||
// vtune: 340 parts
|
// vtune: 340 parts
|
||||||
while (true) {
|
for (;; ++iters) {
|
||||||
if (inProgress[job].advance()) {
|
if (inProgress[job].advance()) {
|
||||||
if (started == count) {
|
if (started == count) {
|
||||||
if (prevJob == job)
|
if (prevJob == job)
|
||||||
@@ -474,6 +471,7 @@ public:
|
|||||||
prevJob = job;
|
prevJob = job;
|
||||||
job = nextJob[job];
|
job = nextJob[job];
|
||||||
}
|
}
|
||||||
|
return iters;
|
||||||
}
|
}
|
||||||
|
|
||||||
void find(const StringRef *values, Finger *results, int *temp, int count) {
|
void find(const StringRef *values, Finger *results, int *temp, int count) {
|
||||||
@@ -567,9 +565,10 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void remove(const Finger &start, const Finger &end) {
|
// Returns the number of entries removed
|
||||||
|
int64_t remove(const Finger &start, const Finger &end) {
|
||||||
if (start.finger[0] == end.finger[0])
|
if (start.finger[0] == end.finger[0])
|
||||||
return;
|
return 0;
|
||||||
|
|
||||||
Node *x = start.finger[0]->getNext(0);
|
Node *x = start.finger[0]->getNext(0);
|
||||||
|
|
||||||
@@ -578,17 +577,20 @@ private:
|
|||||||
if (start.finger[i] != end.finger[i])
|
if (start.finger[i] != end.finger[i])
|
||||||
start.finger[i]->setNext(i, end.finger[i]->getNext(i));
|
start.finger[i]->setNext(i, end.finger[i]->getNext(i));
|
||||||
|
|
||||||
|
int64_t result = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
Node *next = x->getNext(0);
|
Node *next = x->getNext(0);
|
||||||
x->destroy();
|
x->destroy();
|
||||||
|
++result;
|
||||||
if (x == end.finger[0])
|
if (x == end.finger[0])
|
||||||
break;
|
break;
|
||||||
x = next;
|
x = next;
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void insert(const Finger &f, Version version) {
|
void insert(const Finger &f, Version version) {
|
||||||
int level = randomLevel();
|
int level = randomLevel.next();
|
||||||
// std::cout << std::string((const char*)value,length) << " level: " <<
|
// std::cout << std::string((const char*)value,length) << " level: " <<
|
||||||
// level << std::endl;
|
// level << std::endl;
|
||||||
Node *x = Node::create(f.value, level);
|
Node *x = Node::create(f.value, level);
|
||||||
@@ -704,17 +706,27 @@ private:
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SkipListConflictSet {};
|
struct ReadContext {
|
||||||
|
int64_t commits_accum = 0;
|
||||||
|
int64_t conflicts_accum = 0;
|
||||||
|
int64_t too_olds_accum = 0;
|
||||||
|
int64_t check_bytes_accum = 0;
|
||||||
|
};
|
||||||
|
|
||||||
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||||
Impl(int64_t oldestVersion)
|
Impl(int64_t oldestVersion)
|
||||||
: oldestVersion(oldestVersion), newestVersion(oldestVersion),
|
: oldestVersion(oldestVersion), newestVersion(oldestVersion),
|
||||||
skipList(oldestVersion) {}
|
skipList(oldestVersion) {
|
||||||
|
metrics = initMetrics(metricsList, metricsCount);
|
||||||
|
}
|
||||||
|
~Impl() { safe_free(metrics, metricsCount * sizeof(metrics[0])); }
|
||||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||||
int count) const {
|
int count) {
|
||||||
|
ReadContext tls;
|
||||||
Arena arena;
|
Arena arena;
|
||||||
auto *ranges = new (arena) ReadConflictRange[count];
|
auto *ranges = new (arena) ReadConflictRange[count];
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
|
tls.check_bytes_accum += reads[i].begin.len + reads[i].end.len;
|
||||||
ranges[i].begin = {reads[i].begin.p, size_t(reads[i].begin.len)};
|
ranges[i].begin = {reads[i].begin.p, size_t(reads[i].begin.len)};
|
||||||
ranges[i].end = reads[i].end.len > 0
|
ranges[i].end = reads[i].end.len > 0
|
||||||
? StringRef{reads[i].end.p, size_t(reads[i].end.len)}
|
? StringRef{reads[i].end.p, size_t(reads[i].end.len)}
|
||||||
@@ -722,13 +734,22 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
ranges[i].version = reads[i].readVersion;
|
ranges[i].version = reads[i].readVersion;
|
||||||
results[i] = ConflictSet::Commit;
|
results[i] = ConflictSet::Commit;
|
||||||
}
|
}
|
||||||
skipList.detectConflicts(ranges, count, results);
|
int iters = skipList.detectConflicts(ranges, count, results);
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
if (reads[i].readVersion < oldestVersion ||
|
if (reads[i].readVersion < oldestVersion ||
|
||||||
reads[i].readVersion < newestVersion - 2e9) {
|
reads[i].readVersion < newestVersion - 2e9) {
|
||||||
results[i] = TooOld;
|
results[i] = TooOld;
|
||||||
}
|
}
|
||||||
|
tls.commits_accum += results[i] == Commit;
|
||||||
|
tls.conflicts_accum += results[i] == Conflict;
|
||||||
|
tls.too_olds_accum += results[i] == TooOld;
|
||||||
}
|
}
|
||||||
|
range_read_iterations_total.add(iters);
|
||||||
|
range_read_total.add(count);
|
||||||
|
commits_total.add(tls.commits_accum);
|
||||||
|
conflicts_total.add(tls.conflicts_accum);
|
||||||
|
too_olds_total.add(tls.too_olds_accum);
|
||||||
|
check_bytes_total.add(tls.check_bytes_accum);
|
||||||
}
|
}
|
||||||
|
|
||||||
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||||
@@ -775,27 +796,33 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
StringRef values[stripeSize];
|
StringRef values[stripeSize];
|
||||||
int64_t writeVersions[stripeSize / 2];
|
int64_t writeVersions[stripeSize / 2];
|
||||||
int ss = stringCount - (stripes - 1) * stripeSize;
|
int ss = stringCount - (stripes - 1) * stripeSize;
|
||||||
|
int64_t entryDelta = 0;
|
||||||
for (int s = stripes - 1; s >= 0; s--) {
|
for (int s = stripes - 1; s >= 0; s--) {
|
||||||
for (int i = 0; i * 2 < ss; ++i) {
|
for (int i = 0; i * 2 < ss; ++i) {
|
||||||
const auto &w = combinedWriteConflictRanges[s * stripeSize / 2 + i];
|
const auto &w = combinedWriteConflictRanges[s * stripeSize / 2 + i];
|
||||||
values[i * 2] = w.first;
|
values[i * 2] = w.first;
|
||||||
values[i * 2 + 1] = w.second;
|
values[i * 2 + 1] = w.second;
|
||||||
keyUpdates += 3;
|
|
||||||
}
|
}
|
||||||
skipList.find(values, fingers, temp, ss);
|
skipList.find(values, fingers, temp, ss);
|
||||||
skipList.addConflictRanges(fingers, ss / 2, writeVersion);
|
entryDelta += skipList.addConflictRanges(fingers, ss / 2, writeVersion);
|
||||||
ss = stripeSize;
|
ss = stripeSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run gc at least 200% the rate we're inserting entries
|
||||||
|
keyUpdates += std::max<int64_t>(entryDelta, 0) * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setOldestVersion(int64_t oldestVersion) {
|
void setOldestVersion(int64_t oldestVersion) {
|
||||||
|
// This isn't 100% accurate. It overcounts if you hit the end
|
||||||
|
gc_iterations_total.add(keyUpdates);
|
||||||
|
|
||||||
assert(oldestVersion >= this->oldestVersion);
|
assert(oldestVersion >= this->oldestVersion);
|
||||||
this->oldestVersion = oldestVersion;
|
this->oldestVersion = oldestVersion;
|
||||||
SkipList::Finger finger;
|
SkipList::Finger finger;
|
||||||
int temp;
|
int temp;
|
||||||
std::span<const uint8_t> key = removalKey;
|
std::span<const uint8_t> key = removalKey;
|
||||||
skipList.find(&key, &finger, &temp, 1);
|
skipList.find(&key, &finger, &temp, 1);
|
||||||
skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 10));
|
skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 0));
|
||||||
removalArena = Arena();
|
removalArena = Arena();
|
||||||
removalKey = copyToArena(
|
removalKey = copyToArena(
|
||||||
removalArena, {finger.getValue().data(), finger.getValue().size()});
|
removalArena, {finger.getValue().data(), finger.getValue().size()});
|
||||||
@@ -803,8 +830,56 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
|
|
||||||
int64_t totalBytes = 0;
|
int64_t totalBytes = 0;
|
||||||
|
|
||||||
|
MetricsV1 *metrics;
|
||||||
|
int metricsCount = 0;
|
||||||
|
Metric *metricsList = nullptr;
|
||||||
|
|
||||||
|
#define GAUGE(name, help) \
|
||||||
|
Gauge name { metricsList, metricsCount, #name, help }
|
||||||
|
#define COUNTER(name, help) \
|
||||||
|
Counter name { metricsList, metricsCount, #name, help }
|
||||||
|
// ==================== METRICS DEFINITIONS ====================
|
||||||
|
COUNTER(range_read_total, "Total number of range reads checked");
|
||||||
|
COUNTER(range_read_iterations_total,
|
||||||
|
"Total number of iterations of the main loops for range read checks");
|
||||||
|
COUNTER(commits_total,
|
||||||
|
"Total number of checks where the result is \"commit\"");
|
||||||
|
COUNTER(conflicts_total,
|
||||||
|
"Total number of checks where the result is \"conflict\"");
|
||||||
|
COUNTER(too_olds_total,
|
||||||
|
"Total number of checks where the result is \"too old\"");
|
||||||
|
COUNTER(check_bytes_total, "Total number of key bytes checked");
|
||||||
|
GAUGE(memory_bytes, "Total number of bytes in use");
|
||||||
|
COUNTER(nodes_allocated_total,
|
||||||
|
"The total number of physical tree nodes allocated");
|
||||||
|
COUNTER(nodes_released_total,
|
||||||
|
"The total number of physical tree nodes released");
|
||||||
|
COUNTER(insert_iterations_total,
|
||||||
|
"The total number of iterations of the main loop for insertion. "
|
||||||
|
"Includes searches where the entry already existed, and so insertion "
|
||||||
|
"did not take place");
|
||||||
|
COUNTER(entries_inserted_total,
|
||||||
|
"The total number of entries inserted in the tree");
|
||||||
|
COUNTER(entries_erased_total,
|
||||||
|
"The total number of entries erased from the tree");
|
||||||
|
COUNTER(
|
||||||
|
gc_iterations_total,
|
||||||
|
"The total number of iterations of the main loop for garbage collection");
|
||||||
|
COUNTER(write_bytes_total, "Total number of key bytes in calls to addWrites");
|
||||||
|
GAUGE(oldest_version,
|
||||||
|
"The lowest version that doesn't result in \"TooOld\" for checks");
|
||||||
|
GAUGE(newest_version, "The version of the most recent call to addWrites");
|
||||||
|
// ==================== END METRICS DEFINITIONS ====================
|
||||||
|
#undef GAUGE
|
||||||
|
#undef COUNTER
|
||||||
|
|
||||||
|
void getMetricsV1(MetricsV1 **metrics, int *count) {
|
||||||
|
*metrics = this->metrics;
|
||||||
|
*count = metricsCount;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int64_t keyUpdates = 10;
|
int64_t keyUpdates = 0;
|
||||||
Arena removalArena;
|
Arena removalArena;
|
||||||
std::span<const uint8_t> removalKey;
|
std::span<const uint8_t> removalKey;
|
||||||
int64_t oldestVersion;
|
int64_t oldestVersion;
|
||||||
@@ -825,6 +900,7 @@ void internal_addWrites(ConflictSet::Impl *impl,
|
|||||||
mallocBytesDelta = 0;
|
mallocBytesDelta = 0;
|
||||||
impl->addWrites(writes, count, writeVersion);
|
impl->addWrites(writes, count, writeVersion);
|
||||||
impl->totalBytes += mallocBytesDelta;
|
impl->totalBytes += mallocBytesDelta;
|
||||||
|
impl->memory_bytes.set(impl->totalBytes);
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
if (impl->totalBytes != mallocBytes) {
|
if (impl->totalBytes != mallocBytes) {
|
||||||
abort();
|
abort();
|
||||||
@@ -836,6 +912,7 @@ void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
|
|||||||
mallocBytesDelta = 0;
|
mallocBytesDelta = 0;
|
||||||
impl->setOldestVersion(oldestVersion);
|
impl->setOldestVersion(oldestVersion);
|
||||||
impl->totalBytes += mallocBytesDelta;
|
impl->totalBytes += mallocBytesDelta;
|
||||||
|
impl->memory_bytes.set(impl->totalBytes);
|
||||||
#if SHOW_MEMORY
|
#if SHOW_MEMORY
|
||||||
if (impl->totalBytes != mallocBytes) {
|
if (impl->totalBytes != mallocBytes) {
|
||||||
abort();
|
abort();
|
||||||
@@ -859,12 +936,11 @@ int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
|
|||||||
|
|
||||||
void internal_getMetricsV1(ConflictSet::Impl *impl,
|
void internal_getMetricsV1(ConflictSet::Impl *impl,
|
||||||
ConflictSet::MetricsV1 **metrics, int *count) {
|
ConflictSet::MetricsV1 **metrics, int *count) {
|
||||||
*metrics = nullptr;
|
return impl->getMetricsV1(metrics, count);
|
||||||
*count = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double internal_getMetricValue(const ConflictSet::MetricsV1 *metric) {
|
double internal_getMetricValue(const ConflictSet::MetricsV1 *metric) {
|
||||||
return 0;
|
return ((Metric *)metric->p)->value.load(std::memory_order_relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConflictSet::check(const ReadRange *reads, Result *results,
|
void ConflictSet::check(const ReadRange *reads, Result *results,
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user