Compare commits
6 Commits
55e23bafba
...
cf-integri
Author | SHA1 | Date | |
---|---|---|---|
c46f633dbf | |||
400350946c | |||
607a4ef6e2 | |||
b0750772ec | |||
86abc02188 | |||
a90e353fcd |
@@ -57,7 +57,6 @@ ConflictSet::ReadRange prefixRange(Arena &arena, TrivialSpan key) {
|
|||||||
|
|
||||||
void benchConflictSet() {
|
void benchConflictSet() {
|
||||||
ankerl::nanobench::Bench bench;
|
ankerl::nanobench::Bench bench;
|
||||||
bench.minEpochIterations(10000);
|
|
||||||
ConflictSet cs{0};
|
ConflictSet cs{0};
|
||||||
|
|
||||||
bench.batch(kOpsPerTx);
|
bench.batch(kOpsPerTx);
|
||||||
|
@@ -32,8 +32,7 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_compile_options(
|
add_compile_options(
|
||||||
# -Werror=switch-enum
|
-Werror=switch-enum -Wswitch-enum -fPIC -fdata-sections -ffunction-sections
|
||||||
-Wswitch-enum -Wunused-variable -fPIC -fdata-sections -ffunction-sections
|
|
||||||
-fno-jump-tables # https://github.com/llvm/llvm-project/issues/54247
|
-fno-jump-tables # https://github.com/llvm/llvm-project/issues/54247
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -65,8 +64,7 @@ if(HAS_FULL_RELRO)
|
|||||||
endif()
|
endif()
|
||||||
cmake_pop_check_state()
|
cmake_pop_check_state()
|
||||||
|
|
||||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64 OR CMAKE_SYSTEM_PROCESSOR STREQUAL
|
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||||
arm64)
|
|
||||||
add_compile_options(-mbranch-protection=standard)
|
add_compile_options(-mbranch-protection=standard)
|
||||||
else()
|
else()
|
||||||
add_compile_options(-fcf-protection)
|
add_compile_options(-fcf-protection)
|
||||||
@@ -98,17 +96,27 @@ option(DISABLE_TSAN "Disable TSAN" OFF)
|
|||||||
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
||||||
include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/valgrind)
|
include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/valgrind)
|
||||||
|
|
||||||
|
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
add_link_options(-Wl,-dead_strip)
|
add_link_options(-Wl,-dead_strip)
|
||||||
else()
|
else()
|
||||||
add_link_options(-Wl,--gc-sections)
|
add_link_options(-Wl,--gc-sections)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_SIMD_FALLBACK)
|
if(NOT USE_SIMD_FALLBACK)
|
||||||
add_compile_definitions(USE_SIMD_FALLBACK)
|
cmake_push_check_state()
|
||||||
else()
|
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
||||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64)
|
check_include_file_cxx("immintrin.h" HAS_AVX)
|
||||||
|
if(HAS_AVX)
|
||||||
add_compile_options(-mavx)
|
add_compile_options(-mavx)
|
||||||
|
add_compile_definitions(HAS_AVX)
|
||||||
|
endif()
|
||||||
|
cmake_pop_check_state()
|
||||||
|
|
||||||
|
check_include_file_cxx("arm_neon.h" HAS_ARM_NEON)
|
||||||
|
if(HAS_ARM_NEON)
|
||||||
|
add_compile_definitions(HAS_ARM_NEON)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -338,7 +346,8 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
|||||||
# c++98
|
# c++98
|
||||||
add_executable(conflict_set_cxx_api_test conflict_set_cxx_api_test.cpp)
|
add_executable(conflict_set_cxx_api_test conflict_set_cxx_api_test.cpp)
|
||||||
target_compile_options(conflict_set_cxx_api_test PRIVATE ${TEST_FLAGS})
|
target_compile_options(conflict_set_cxx_api_test PRIVATE ${TEST_FLAGS})
|
||||||
target_link_libraries(conflict_set_cxx_api_test PRIVATE ${PROJECT_NAME})
|
target_link_libraries(conflict_set_cxx_api_test
|
||||||
|
PRIVATE ${PROJECT_NAME}-static)
|
||||||
set_target_properties(conflict_set_cxx_api_test PROPERTIES CXX_STANDARD 98)
|
set_target_properties(conflict_set_cxx_api_test PROPERTIES CXX_STANDARD 98)
|
||||||
set_target_properties(conflict_set_cxx_api_test
|
set_target_properties(conflict_set_cxx_api_test
|
||||||
PROPERTIES CXX_STANDARD_REQUIRED ON)
|
PROPERTIES CXX_STANDARD_REQUIRED ON)
|
||||||
|
2348
ConflictSet.cpp
2348
ConflictSet.cpp
File diff suppressed because it is too large
Load Diff
18
Internal.h
18
Internal.h
@@ -18,6 +18,7 @@ using namespace weaselab;
|
|||||||
#include <span>
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <callgrind.h>
|
#include <callgrind.h>
|
||||||
@@ -367,6 +368,23 @@ template <class T, class C = std::less<T>> auto set(Arena &arena) {
|
|||||||
return Set<T, C>(ArenaAlloc<T>(&arena));
|
return Set<T, C>(ArenaAlloc<T>(&arena));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class T> struct MyHash;
|
||||||
|
|
||||||
|
template <class T> struct MyHash<T *> {
|
||||||
|
size_t operator()(const T *t) const noexcept {
|
||||||
|
size_t result;
|
||||||
|
memcpy(&result, &t, sizeof(result));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
using HashSet =
|
||||||
|
std::unordered_set<T, MyHash<T>, std::equal_to<T>, ArenaAlloc<T>>;
|
||||||
|
template <class T> auto hashSet(Arena &arena) {
|
||||||
|
return HashSet<T>(ArenaAlloc<T>(&arena));
|
||||||
|
}
|
||||||
|
|
||||||
template <class T, class U>
|
template <class T, class U>
|
||||||
bool operator==(const ArenaAlloc<T> &lhs, const ArenaAlloc<U> &rhs) {
|
bool operator==(const ArenaAlloc<T> &lhs, const ArenaAlloc<U> &rhs) {
|
||||||
return lhs.arena == rhs.arena;
|
return lhs.arena == rhs.arena;
|
||||||
|
4
Jenkinsfile
vendored
4
Jenkinsfile
vendored
@@ -91,7 +91,7 @@ pipeline {
|
|||||||
minio bucket: 'jenkins', credentialsId: 'jenkins-minio', excludes: '', host: 'minio.weaselab.dev', includes: 'build/*.deb,build/*.rpm,paper/*.pdf', targetFolder: '${JOB_NAME}/${BUILD_NUMBER}/${STAGE_NAME}/'
|
minio bucket: 'jenkins', credentialsId: 'jenkins-minio', excludes: '', host: 'minio.weaselab.dev', includes: 'build/*.deb,build/*.rpm,paper/*.pdf', targetFolder: '${JOB_NAME}/${BUILD_NUMBER}/${STAGE_NAME}/'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('gcc') {
|
stage('Release [gcc]') {
|
||||||
agent {
|
agent {
|
||||||
dockerfile {
|
dockerfile {
|
||||||
args '-v /home/jenkins/ccache:/ccache'
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
@@ -99,7 +99,7 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++")
|
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
||||||
recordIssues(tools: [gcc()])
|
recordIssues(tools: [gcc()])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
55
README.md
55
README.md
@@ -4,14 +4,7 @@ Intended as an alternative to FoundationDB's skip list.
|
|||||||
|
|
||||||
Hardware for all benchmarks is an AMD Ryzen 9 7900 with (2x32GB) 5600MT/s CL28-34-34-89 1.35V RAM.
|
Hardware for all benchmarks is an AMD Ryzen 9 7900 with (2x32GB) 5600MT/s CL28-34-34-89 1.35V RAM.
|
||||||
|
|
||||||
```
|
Compiler is `Ubuntu clang version 20.0.0 (++20241029082144+7544d3af0e28-1~exp1~20241029082307.506)`.
|
||||||
$ clang++ --version
|
|
||||||
|
|
||||||
Ubuntu clang version 20.0.0 (++20241120082228+86734c857724-1~exp1~20241120202359.554)
|
|
||||||
Target: x86_64-pc-linux-gnu
|
|
||||||
Thread model: posix
|
|
||||||
InstalledDir: /usr/lib/llvm-20/bin
|
|
||||||
```
|
|
||||||
|
|
||||||
# Microbenchmark
|
# Microbenchmark
|
||||||
|
|
||||||
@@ -19,45 +12,44 @@ InstalledDir: /usr/lib/llvm-20/bin
|
|||||||
|
|
||||||
| 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
|
||||||
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
||||||
| 161.29 | 6,200,056.17 | 0.1% | 3,014.03 | 831.04 | 3.627 | 504.59 | 0.0% | 1.93 | `point reads`
|
| 159.65 | 6,263,576.52 | 1.6% | 2,972.36 | 820.37 | 3.623 | 504.59 | 0.0% | 0.01 | `point reads`
|
||||||
| 158.32 | 6,316,160.64 | 0.1% | 2,954.16 | 815.80 | 3.621 | 490.17 | 0.0% | 1.89 | `prefix reads`
|
| 156.32 | 6,397,320.65 | 0.7% | 2,913.62 | 806.87 | 3.611 | 490.19 | 0.0% | 0.01 | `prefix reads`
|
||||||
| 237.39 | 4,212,409.50 | 0.2% | 3,592.41 | 1,233.96 | 2.911 | 629.31 | 0.0% | 2.84 | `range reads`
|
| 229.18 | 4,363,293.65 | 1.2% | 3,541.05 | 1,219.75 | 2.903 | 629.33 | 0.0% | 0.01 | `range reads`
|
||||||
| 442.11 | 2,261,878.94 | 0.0% | 4,450.57 | 2,314.25 | 1.923 | 707.92 | 2.1% | 5.28 | `point writes`
|
| 363.37 | 2,752,026.30 | 0.3% | 5,273.63 | 1,951.54 | 2.702 | 851.66 | 1.7% | 0.01 | `point writes`
|
||||||
| 439.89 | 2,273,308.53 | 0.1% | 4,410.22 | 2,302.29 | 1.916 | 694.74 | 2.1% | 5.25 | `prefix writes`
|
| 364.99 | 2,739,787.02 | 0.3% | 5,250.92 | 1,958.54 | 2.681 | 839.24 | 1.7% | 0.01 | `prefix writes`
|
||||||
| 290.96 | 3,436,936.78 | 0.0% | 2,315.38 | 1,528.68 | 1.515 | 396.69 | 3.3% | 3.49 | `range writes`
|
| 242.26 | 4,127,796.58 | 2.9% | 3,117.33 | 1,304.41 | 2.390 | 541.07 | 2.8% | 0.02 | `range writes`
|
||||||
| 476.93 | 2,096,762.02 | 0.6% | 6,999.33 | 2,484.94 | 2.817 | 1,251.73 | 1.3% | 0.06 | `monotonic increasing point writes`
|
| 562.48 | 1,777,855.27 | 0.8% | 7,305.21 | 3,034.34 | 2.408 | 1,329.30 | 1.3% | 0.01 | `monotonic increasing point writes`
|
||||||
| 131,736.57 | 7,590.91 | 1.1% | 807,444.50 | 704,941.71 | 1.145 | 144,584.60 | 0.9% | 0.01 | `worst case for radix tree`
|
| 122,688.57 | 8,150.72 | 0.7% | 798,766.00 | 666,842.00 | 1.198 | 144,584.50 | 0.1% | 0.01 | `worst case for radix tree`
|
||||||
| 45.50 | 21,978,369.95 | 1.1% | 902.00 | 232.36 | 3.882 | 132.00 | 0.0% | 0.01 | `create and destroy`
|
| 41.71 | 23,976,459.34 | 1.7% | 885.00 | 219.17 | 4.038 | 132.00 | 0.0% | 0.01 | `create and destroy`
|
||||||
|
|
||||||
## Radix tree (this implementation)
|
## Radix tree (this implementation)
|
||||||
|
|
||||||
|
|
||||||
| 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
|
||||||
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
||||||
| 12.36 | 80,885,626.43 | 0.2% | 243.56 | 63.62 | 3.828 | 31.07 | 0.6% | 0.15 | `point reads`
|
| 12.63 | 79,186,868.18 | 1.4% | 241.61 | 64.76 | 3.731 | 31.64 | 0.8% | 0.01 | `point reads`
|
||||||
| 14.18 | 70,502,196.81 | 0.1% | 297.72 | 73.13 | 4.071 | 40.31 | 0.5% | 0.17 | `prefix reads`
|
| 14.48 | 69,078,073.40 | 0.3% | 292.42 | 74.69 | 3.915 | 41.49 | 0.5% | 0.01 | `prefix reads`
|
||||||
| 33.44 | 29,901,623.04 | 0.1% | 767.90 | 172.42 | 4.454 | 101.32 | 0.2% | 0.40 | `range reads`
|
| 34.37 | 29,094,694.11 | 0.2% | 759.53 | 179.77 | 4.225 | 100.38 | 0.2% | 0.01 | `range reads`
|
||||||
| 19.48 | 51,342,564.70 | 0.3% | 374.45 | 100.43 | 3.728 | 48.92 | 0.5% | 0.23 | `point writes`
|
| 19.34 | 51,713,896.36 | 0.7% | 369.70 | 101.81 | 3.631 | 47.88 | 0.6% | 0.01 | `point writes`
|
||||||
| 37.46 | 26,694,471.44 | 0.1% | 672.00 | 193.14 | 3.479 | 101.28 | 0.3% | 0.45 | `prefix writes`
|
| 39.16 | 25,538,968.61 | 0.2% | 653.16 | 206.77 | 3.159 | 89.62 | 0.8% | 0.01 | `prefix writes`
|
||||||
| 38.78 | 25,784,784.34 | 0.0% | 738.26 | 199.93 | 3.693 | 111.59 | 0.1% | 0.47 | `range writes`
|
| 40.58 | 24,642,681.12 | 4.7% | 718.44 | 216.44 | 3.319 | 99.28 | 0.6% | 0.01 | `range writes`
|
||||||
| 76.05 | 13,148,995.74 | 0.7% | 1,450.77 | 397.16 | 3.653 | 275.72 | 0.0% | 0.01 | `monotonic increasing point writes`
|
| 78.77 | 12,694,520.69 | 3.8% | 1,395.55 | 421.73 | 3.309 | 249.81 | 0.1% | 0.01 | `monotonic increasing point writes`
|
||||||
| 286,920.33 | 3,485.29 | 0.4% | 4,117,948.00 | 1,521,352.00 | 2.707 | 714,833.00 | 0.1% | 0.01 | `worst case for radix tree`
|
| 287,760.50 | 3,475.11 | 0.5% | 3,929,266.50 | 1,550,225.50 | 2.535 | 639,064.00 | 0.0% | 0.01 | `worst case for radix tree`
|
||||||
| 95.66 | 10,453,798.72 | 0.5% | 1,986.00 | 495.04 | 4.012 | 315.00 | 0.0% | 0.01 | `create and destroy`
|
| 104.76 | 9,545,250.65 | 3.1% | 2,000.00 | 552.82 | 3.618 | 342.00 | 0.0% | 0.01 | `create and destroy`
|
||||||
|
|
||||||
# "Real data" test
|
# "Real data" test
|
||||||
|
|
||||||
Point queries only. Gc ratio is the ratio of time spent doing garbage collection to time spent adding writes or doing garbage collection. Lower is better.
|
Point queries only, best of three runs. Gc ratio is the ratio of time spent doing garbage collection to time spent adding writes or doing garbage collection. Lower is better.
|
||||||
|
|
||||||
## skip list
|
## skip list
|
||||||
|
|
||||||
```
|
```
|
||||||
Check: 4.53508 seconds, 371.81 MB/s, Add: 3.81222 seconds, 150.919 MB/s, Gc ratio: 33.66%, Peak idle memory: 5.61007e+06
|
Check: 4.39702 seconds, 370.83 MB/s, Add: 4.50025 seconds, 124.583 MB/s, Gc ratio: 29.1333%, Peak idle memory: 5.51852e+06
|
||||||
```
|
```
|
||||||
|
|
||||||
## radix tree
|
## radix tree
|
||||||
|
|
||||||
```
|
```
|
||||||
Check: 0.957735 seconds, 1760.6 MB/s, Add: 1.19942 seconds, 479.678 MB/s, Gc ratio: 38.6069%, Peak idle memory: 2.05667e+06
|
Check: 0.987757 seconds, 1650.76 MB/s, Add: 1.24815 seconds, 449.186 MB/s, Gc ratio: 41.4675%, Peak idle memory: 2.02872e+06
|
||||||
```
|
```
|
||||||
|
|
||||||
## hash table
|
## hash table
|
||||||
@@ -65,6 +57,5 @@ Check: 0.957735 seconds, 1760.6 MB/s, Add: 1.19942 seconds, 479.678 MB/s, Gc rat
|
|||||||
(The hash table implementation doesn't work on range queries, and its purpose is to provide an idea of how fast point queries can be)
|
(The hash table implementation doesn't work on range queries, and its purpose is to provide an idea of how fast point queries can be)
|
||||||
|
|
||||||
```
|
```
|
||||||
Check: 0.804598 seconds, 2095.69 MB/s, Add: 0.671221 seconds, 857.147 MB/s, Gc ratio: 35.0034%, Peak idle memory: 0
|
Check: 0.84256 seconds, 1935.23 MB/s, Add: 0.697204 seconds, 804.146 MB/s, Gc ratio: 35.4091%
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <span>
|
#include <string_view>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@@ -64,7 +64,7 @@ int main(int argc, const char **argv) {
|
|||||||
auto *const mapOriginal = begin;
|
auto *const mapOriginal = begin;
|
||||||
const auto sizeOriginal = size;
|
const auto sizeOriginal = size;
|
||||||
|
|
||||||
using StringView = std::span<const uint8_t>;
|
using StringView = std::basic_string_view<uint8_t>;
|
||||||
|
|
||||||
StringView write;
|
StringView write;
|
||||||
std::vector<StringView> reads;
|
std::vector<StringView> reads;
|
||||||
@@ -78,9 +78,9 @@ int main(int argc, const char **argv) {
|
|||||||
end = (uint8_t *)memchr(begin, '\n', size);
|
end = (uint8_t *)memchr(begin, '\n', size);
|
||||||
|
|
||||||
if (line.size() > 0 && line[0] == 'P') {
|
if (line.size() > 0 && line[0] == 'P') {
|
||||||
write = line.subspan(2, line.size());
|
write = line.substr(2, line.size());
|
||||||
} else if (line.size() > 0 && line[0] == 'L') {
|
} else if (line.size() > 0 && line[0] == 'L') {
|
||||||
reads.push_back(line.subspan(2, line.size()));
|
reads.push_back(line.substr(2, line.size()));
|
||||||
} else if (line.empty()) {
|
} else if (line.empty()) {
|
||||||
{
|
{
|
||||||
readRanges.resize(reads.size());
|
readRanges.resize(reads.size());
|
||||||
@@ -133,10 +133,10 @@ int main(int argc, const char **argv) {
|
|||||||
int metricsCount;
|
int metricsCount;
|
||||||
cs.getMetricsV1(&metrics, &metricsCount);
|
cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
for (int i = 0; i < metricsCount; ++i) {
|
for (int i = 0; i < metricsCount; ++i) {
|
||||||
fprintf(stderr, "# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||||
fprintf(stderr, "# TYPE %s %s\n", metrics[i].name,
|
printf("# TYPE %s %s\n", metrics[i].name,
|
||||||
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||||
fprintf(stderr, "%s %g\n", metrics[i].name, metrics[i].getValue());
|
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
|
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
|
||||||
|
193
ServerBench.cpp
193
ServerBench.cpp
@@ -23,97 +23,6 @@
|
|||||||
#include "Internal.h"
|
#include "Internal.h"
|
||||||
#include "third_party/nadeau.h"
|
#include "third_party/nadeau.h"
|
||||||
|
|
||||||
constexpr int kCacheLine = 64; // TODO mac m1 is 128
|
|
||||||
|
|
||||||
template <class T> struct TxQueue {
|
|
||||||
|
|
||||||
explicit TxQueue(int lgSlotCount)
|
|
||||||
: slotCount(1 << lgSlotCount), slotCountMask(slotCount - 1),
|
|
||||||
slots(new T[slotCount]) {
|
|
||||||
// Otherwise we can't tell the difference between full and empty.
|
|
||||||
assert(!(slotCountMask & 0x80000000));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call from producer thread, after ensuring consumer is no longer accessing
|
|
||||||
/// it somehow
|
|
||||||
~TxQueue() { delete[] slots; }
|
|
||||||
|
|
||||||
/// Must be called from the producer thread
|
|
||||||
void push(T t) {
|
|
||||||
if (wouldBlock()) {
|
|
||||||
// Wait for pops to change and try again
|
|
||||||
consumer.pops.wait(producer.lastPopRead, std::memory_order_relaxed);
|
|
||||||
producer.lastPopRead = consumer.pops.load(std::memory_order_acquire);
|
|
||||||
}
|
|
||||||
slots[producer.pushesNonAtomic++ & slotCountMask] = std::move(t);
|
|
||||||
// seq_cst so that the notify can't be ordered before the store
|
|
||||||
producer.pushes.store(producer.pushesNonAtomic, std::memory_order_seq_cst);
|
|
||||||
// We have to notify every time, since we don't know if this is the last
|
|
||||||
// push ever
|
|
||||||
producer.pushes.notify_one();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Must be called from the producer thread
|
|
||||||
uint32_t outstanding() {
|
|
||||||
return producer.pushesNonAtomic -
|
|
||||||
consumer.pops.load(std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if a call to push might block. Must be called from the
|
|
||||||
/// producer thread.
|
|
||||||
bool wouldBlock() {
|
|
||||||
// See if we can determine that overflow won't happen entirely from state
|
|
||||||
// local to the producer
|
|
||||||
if (producer.pushesNonAtomic - producer.lastPopRead == slotCount - 1) {
|
|
||||||
// Re-read pops with memory order
|
|
||||||
producer.lastPopRead = consumer.pops.load(std::memory_order_acquire);
|
|
||||||
return producer.pushesNonAtomic - producer.lastPopRead == slotCount - 1;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Valid until the next pop, or until this queue is destroyed.
|
|
||||||
T *pop() {
|
|
||||||
// See if we can determine that there's an entry we can pop entirely from
|
|
||||||
// state local to the consumer
|
|
||||||
if (consumer.lastPushRead - consumer.popsNonAtomic == 0) {
|
|
||||||
// Re-read pushes with memory order and try again
|
|
||||||
consumer.lastPushRead = producer.pushes.load(std::memory_order_acquire);
|
|
||||||
if (consumer.lastPushRead - consumer.popsNonAtomic == 0) {
|
|
||||||
// Wait for pushes to change and try again
|
|
||||||
producer.pushes.wait(consumer.lastPushRead, std::memory_order_relaxed);
|
|
||||||
consumer.lastPushRead = producer.pushes.load(std::memory_order_acquire);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto result = &slots[consumer.popsNonAtomic++ & slotCountMask];
|
|
||||||
// We only have to write pops with memory order if we've run out of items.
|
|
||||||
// We know that we'll eventually run out.
|
|
||||||
if (consumer.lastPushRead - consumer.popsNonAtomic == 0) {
|
|
||||||
// seq_cst so that the notify can't be ordered before the store
|
|
||||||
consumer.pops.store(consumer.popsNonAtomic, std::memory_order_seq_cst);
|
|
||||||
consumer.pops.notify_one();
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
const uint32_t slotCount;
|
|
||||||
const uint32_t slotCountMask;
|
|
||||||
T *slots;
|
|
||||||
struct alignas(kCacheLine) ProducerState {
|
|
||||||
std::atomic<uint32_t> pushes{0};
|
|
||||||
uint32_t pushesNonAtomic{0};
|
|
||||||
uint32_t lastPopRead{0};
|
|
||||||
};
|
|
||||||
struct alignas(kCacheLine) ConsumerState {
|
|
||||||
std::atomic<uint32_t> pops{0};
|
|
||||||
uint32_t popsNonAtomic{0};
|
|
||||||
uint32_t lastPushRead{0};
|
|
||||||
};
|
|
||||||
ProducerState producer;
|
|
||||||
ConsumerState consumer;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::atomic<int64_t> transactions;
|
std::atomic<int64_t> transactions;
|
||||||
|
|
||||||
int64_t safeUnaryMinus(int64_t x) {
|
int64_t safeUnaryMinus(int64_t x) {
|
||||||
@@ -138,17 +47,13 @@ void tupleAppend(std::string &output, int64_t value) {
|
|||||||
|
|
||||||
void tupleAppend(std::string &output, std::string_view value) {
|
void tupleAppend(std::string &output, std::string_view value) {
|
||||||
output.push_back('\x02');
|
output.push_back('\x02');
|
||||||
if (memchr(value.data(), '\x00', value.size()) != nullptr) {
|
for (auto c : value) {
|
||||||
for (auto c : value) {
|
if (c == '\x00') {
|
||||||
if (c == '\x00') {
|
output.push_back('\x00');
|
||||||
output.push_back('\x00');
|
output.push_back('\xff');
|
||||||
output.push_back('\xff');
|
} else {
|
||||||
} else {
|
output.push_back(c);
|
||||||
output.push_back(c);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
output.insert(output.end(), value.begin(), value.end());
|
|
||||||
}
|
}
|
||||||
output.push_back('\x00');
|
output.push_back('\x00');
|
||||||
}
|
}
|
||||||
@@ -161,69 +66,49 @@ template <class... Ts> std::string tupleKey(const Ts &...ts) {
|
|||||||
|
|
||||||
constexpr int kTotalKeyRange = 1'000'000'000;
|
constexpr int kTotalKeyRange = 1'000'000'000;
|
||||||
constexpr int kWindowSize = 1'000'000;
|
constexpr int kWindowSize = 1'000'000;
|
||||||
constexpr int kNumReadKeysPerTx = 5;
|
constexpr int kNumKeys = 10;
|
||||||
constexpr int kNumWriteKeysPerTx = 10;
|
|
||||||
|
|
||||||
struct Transaction {
|
void workload(weaselab::ConflictSet *cs) {
|
||||||
std::vector<std::string> keys;
|
int64_t version = kWindowSize;
|
||||||
std::vector<weaselab::ConflictSet::ReadRange> reads;
|
for (;; transactions.fetch_add(1, std::memory_order_relaxed)) {
|
||||||
std::vector<weaselab::ConflictSet::WriteRange> writes;
|
|
||||||
int64_t version;
|
|
||||||
int64_t oldestVersion;
|
|
||||||
Transaction() = default;
|
|
||||||
explicit Transaction(int64_t version)
|
|
||||||
: version(version), oldestVersion(version - kWindowSize) {
|
|
||||||
std::vector<int64_t> keyIndices;
|
std::vector<int64_t> keyIndices;
|
||||||
for (int i = 0; i < std::max(kNumReadKeysPerTx, kNumWriteKeysPerTx); ++i) {
|
for (int i = 0; i < kNumKeys; ++i) {
|
||||||
keyIndices.push_back(rand() % kTotalKeyRange);
|
keyIndices.push_back(rand() % kTotalKeyRange);
|
||||||
}
|
}
|
||||||
std::sort(keyIndices.begin(), keyIndices.end());
|
std::sort(keyIndices.begin(), keyIndices.end());
|
||||||
|
std::vector<std::string> keys;
|
||||||
constexpr std::string_view fullString =
|
constexpr std::string_view fullString =
|
||||||
"this is a string, where a prefix of it is used as an element of the "
|
"this is a string, where a prefix of it is used as an element of the "
|
||||||
"tuple forming the key";
|
"tuple forming the key";
|
||||||
for (int i = 0; i < int(keyIndices.size()); ++i) {
|
for (int i = 0; i < kNumKeys; ++i) {
|
||||||
keys.push_back(
|
keys.push_back(
|
||||||
tupleKey(0x100, keyIndices[i] / fullString.size(),
|
tupleKey(0x100, keyIndices[i] / fullString.size(),
|
||||||
fullString.substr(0, keyIndices[i] % fullString.size())));
|
fullString.substr(0, keyIndices[i] % fullString.size())));
|
||||||
// printf("%s\n", printable(keys.back()).c_str());
|
// printf("%s\n", printable(keys.back()).c_str());
|
||||||
}
|
}
|
||||||
for (int i = 0; i < kNumWriteKeysPerTx; ++i) {
|
|
||||||
|
std::vector<weaselab::ConflictSet::ReadRange> reads;
|
||||||
|
std::vector<weaselab::ConflictSet::WriteRange> writes;
|
||||||
|
std::vector<weaselab::ConflictSet::Result> results;
|
||||||
|
for (int i = 0; i < kNumKeys; ++i) {
|
||||||
writes.push_back({{(const uint8_t *)keys[i].data(), int(keys[i].size())},
|
writes.push_back({{(const uint8_t *)keys[i].data(), int(keys[i].size())},
|
||||||
{nullptr, 0}});
|
{nullptr, 0}});
|
||||||
}
|
|
||||||
reads.push_back({{(const uint8_t *)keys[0].data(), int(keys[0].size())},
|
|
||||||
{(const uint8_t *)keys[1].data(), int(keys[1].size())},
|
|
||||||
version - std::min(10, kWindowSize)});
|
|
||||||
static_assert(kNumReadKeysPerTx >= 3);
|
|
||||||
for (int i = 2; i < kNumReadKeysPerTx; ++i) {
|
|
||||||
reads.push_back({{(const uint8_t *)keys[i].data(), int(keys[i].size())},
|
reads.push_back({{(const uint8_t *)keys[i].data(), int(keys[i].size())},
|
||||||
{nullptr, 0},
|
{nullptr, 0},
|
||||||
version - kWindowSize});
|
version - kWindowSize});
|
||||||
}
|
}
|
||||||
|
results.resize(reads.size());
|
||||||
|
|
||||||
|
cs->check(reads.data(), results.data(), reads.size());
|
||||||
|
bool ok = true;
|
||||||
|
for (auto result : results) {
|
||||||
|
ok &= result == weaselab::ConflictSet::Commit;
|
||||||
|
}
|
||||||
|
cs->addWrites(writes.data(), ok ? writes.size() : 0, version);
|
||||||
|
cs->setOldestVersion(version - kWindowSize);
|
||||||
|
++version;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Transaction(Transaction &&) = default;
|
|
||||||
Transaction &operator=(Transaction &&) = default;
|
|
||||||
Transaction(Transaction const &) = delete;
|
|
||||||
Transaction const &operator=(Transaction const &) = delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Resolver {
|
|
||||||
|
|
||||||
void resolve(const weaselab::ConflictSet::ReadRange *reads, int readCount,
|
|
||||||
const weaselab::ConflictSet::WriteRange *writes, int writeCount,
|
|
||||||
int64_t newVersion, int64_t newOldestVersion) {
|
|
||||||
results.resize(readCount);
|
|
||||||
cs.check(reads, results.data(), readCount);
|
|
||||||
cs.addWrites(writes, writeCount, newVersion);
|
|
||||||
cs.setOldestVersion(newOldestVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConflictSet cs{0};
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<weaselab::ConflictSet::Result> results;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Adapted from getaddrinfo man page
|
// Adapted from getaddrinfo man page
|
||||||
int getListenFd(const char *node, const char *service) {
|
int getListenFd(const char *node, const char *service) {
|
||||||
@@ -366,8 +251,7 @@ int main(int argc, char **argv) {
|
|||||||
{
|
{
|
||||||
int listenFd = getListenFd(argv[1], argv[2]);
|
int listenFd = getListenFd(argv[1], argv[2]);
|
||||||
|
|
||||||
Resolver resolver;
|
weaselab::ConflictSet cs{0};
|
||||||
auto &cs = resolver.cs;
|
|
||||||
weaselab::ConflictSet::MetricsV1 *metrics;
|
weaselab::ConflictSet::MetricsV1 *metrics;
|
||||||
int metricsCount;
|
int metricsCount;
|
||||||
cs.getMetricsV1(&metrics, &metricsCount);
|
cs.getMetricsV1(&metrics, &metricsCount);
|
||||||
@@ -416,22 +300,7 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
TxQueue<Transaction> queue{10};
|
auto w = std::thread{workload, &cs};
|
||||||
|
|
||||||
auto workloadThread = std::thread{[&]() {
|
|
||||||
for (int64_t version = kWindowSize;;
|
|
||||||
++version, transactions.fetch_add(1, std::memory_order_relaxed)) {
|
|
||||||
queue.push(Transaction(version));
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
|
|
||||||
auto resolverThread = std::thread{[&]() {
|
|
||||||
for (;;) {
|
|
||||||
auto tx = queue.pop();
|
|
||||||
resolver.resolve(tx->reads.data(), tx->reads.size(), tx->writes.data(),
|
|
||||||
tx->writes.size(), tx->version, tx->oldestVersion);
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
struct sockaddr_storage peer_addr = {};
|
struct sockaddr_storage peer_addr = {};
|
||||||
|
@@ -796,6 +796,7 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
|||||||
int temp[stripeSize];
|
int temp[stripeSize];
|
||||||
int stripes = (stringCount + stripeSize - 1) / stripeSize;
|
int stripes = (stringCount + stripeSize - 1) / stripeSize;
|
||||||
StringRef values[stripeSize];
|
StringRef values[stripeSize];
|
||||||
|
int64_t writeVersions[stripeSize / 2];
|
||||||
int ss = stringCount - (stripes - 1) * stripeSize;
|
int ss = stringCount - (stripes - 1) * stripeSize;
|
||||||
int64_t entryDelta = 0;
|
int64_t entryDelta = 0;
|
||||||
for (int s = stripes - 1; s >= 0; s--) {
|
for (int s = stripes - 1; s >= 0; s--) {
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
___chkstk_darwin
|
|
||||||
___stack_chk_fail
|
___stack_chk_fail
|
||||||
___stack_chk_guard
|
___stack_chk_guard
|
||||||
__tlv_bootstrap
|
__tlv_bootstrap
|
||||||
@@ -6,7 +5,6 @@ _abort
|
|||||||
_bzero
|
_bzero
|
||||||
_free
|
_free
|
||||||
_malloc
|
_malloc
|
||||||
_memcmp
|
|
||||||
_memcpy
|
_memcpy
|
||||||
_memmove
|
_memmove
|
||||||
dyld_stub_binder
|
dyld_stub_binder
|
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.
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