16 Commits

Author SHA1 Message Date
71c39f9955 Opt-in to rpm/deb default package filenames
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1096, passed: 1096
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-29 15:56:58 -07:00
8cc17158fd Fix preprocessing instructions for linux
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1096, passed: 1096
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-28 14:58:29 -07:00
ab211c646a Apply compiler-appeasing syntax changes from Taoxi 2024-03-28 14:57:31 -07:00
7af961f141 Fix jenkins build
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1096, passed: 1096
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-28 11:44:01 -07:00
a91df62608 Add USE_SIMD_FALLBACK build in jenkins
Some checks failed
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / SIMD fallback total: 1096, passed: 1096
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-03-28 11:33:53 -07:00
0a1843a161 Add USE_SIMD_FALLBACK
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-28 11:12:50 -07:00
4edf0315d9 Find insertion point for Node16 with simd
Closes #13
2024-03-28 10:47:20 -07:00
14515e186a Update readme benchmarks again
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
I think the last skiplisttest benchmark looked bad because I had vscode
+ firefox + ?? open
2024-03-27 16:47:36 -07:00
b0085df5ad Update symbols.txt
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-27 16:29:30 -07:00
76a7e17b29 Update readme benchmarks
Some checks failed
Tests / Clang total: 1096, failed: 2, passed: 1094
Tests / Release [gcc] total: 1096, failed: 2, passed: 1094
Tests / Release [gcc,aarch64] total: 824, failed: 2, passed: 822
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head There was a failure building this commit
I was incorrectly linking to an old build of conflict set, before the
conflict-set name change. I don't know what the regression is from, but
update the README for transparency now.
2024-03-27 16:12:45 -07:00
5cf43d1bfa Add weaselab namespace 2024-03-27 16:07:05 -07:00
25cc427ec5 Assert safe_free size is correct in debug builds
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
Closes #16
2024-03-27 15:34:24 -07:00
c15c2e7b44 Remove redundant static assert
Closes #14
2024-03-27 12:41:45 -07:00
a4d1f91670 Update README benchmark after adding getBytes
All checks were successful
Tests / Clang total: 1096, passed: 1096
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc] total: 1096, passed: 1096
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 824, passed: 824
Tests / Coverage total: 823, passed: 823
weaselab/conflict-set/pipeline/head This commit looks good
2024-03-20 16:26:05 -07:00
b7cdecaf71 Document thread-safety in terms of constness 2024-03-20 16:16:15 -07:00
cda28643a6 Fix ConflictSet_getBytes 2024-03-20 16:15:43 -07:00
12 changed files with 221 additions and 188 deletions

View File

@@ -34,7 +34,7 @@ ConflictSet::ReadRange singleton(Arena &arena, std::span<const uint8_t> key) {
std::span<uint8_t>(new (arena) uint8_t[key.size() + 1], key.size() + 1); std::span<uint8_t>(new (arena) uint8_t[key.size() + 1], key.size() + 1);
memcpy(r.data(), key.data(), key.size()); memcpy(r.data(), key.data(), key.size());
r[key.size()] = 0; r[key.size()] = 0;
return {key.data(), int(key.size()), r.data(), int(r.size())}; return {{key.data(), int(key.size())}, {r.data(), int(r.size())}, 0};
} }
ConflictSet::ReadRange prefixRange(Arena &arena, std::span<const uint8_t> key) { ConflictSet::ReadRange prefixRange(Arena &arena, std::span<const uint8_t> key) {
@@ -52,7 +52,7 @@ ConflictSet::ReadRange prefixRange(Arena &arena, std::span<const uint8_t> key) {
auto r = std::span<uint8_t>(new (arena) uint8_t[index + 1], index + 1); auto r = std::span<uint8_t>(new (arena) uint8_t[index + 1], index + 1);
memcpy(r.data(), key.data(), index + 1); memcpy(r.data(), key.data(), index + 1);
r[r.size() - 1]++; r[r.size() - 1]++;
return {key.data(), int(key.size()), r.data(), int(r.size())}; return {{key.data(), int(key.size())}, {r.data(), int(r.size())}, 0};
} }
void benchConflictSet() { void benchConflictSet() {
@@ -258,4 +258,4 @@ void benchConflictSet() {
} }
} }
int main(void) { benchConflictSet(); } int main(void) { benchConflictSet(); }

View File

@@ -25,6 +25,9 @@ endif()
add_compile_options(-fdata-sections -ffunction-sections -Wswitch-enum add_compile_options(-fdata-sections -ffunction-sections -Wswitch-enum
-Werror=switch-enum) -Werror=switch-enum)
option(USE_SIMD_FALLBACK
"Use fallback implementations of functions that use SIMD" OFF)
# This is encouraged according to # This is encouraged according to
# 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_SOURCE_DIR}/third_party/valgrind) include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/third_party/valgrind)
@@ -43,18 +46,20 @@ endif()
include(CheckIncludeFileCXX) include(CheckIncludeFileCXX)
include(CMakePushCheckState) include(CMakePushCheckState)
cmake_push_check_state() if(NOT USE_SIMD_FALLBACK)
list(APPEND CMAKE_REQUIRED_FLAGS -mavx) cmake_push_check_state()
check_include_file_cxx("immintrin.h" HAS_AVX) list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
if(HAS_AVX) check_include_file_cxx("immintrin.h" HAS_AVX)
add_compile_options(-mavx) if(HAS_AVX)
add_compile_definitions(HAS_AVX) add_compile_options(-mavx)
endif() add_compile_definitions(HAS_AVX)
cmake_pop_check_state() endif()
cmake_pop_check_state()
check_include_file_cxx("arm_neon.h" HAS_ARM_NEON) check_include_file_cxx("arm_neon.h" HAS_ARM_NEON)
if(HAS_ARM_NEON) if(HAS_ARM_NEON)
add_compile_definitions(HAS_ARM_NEON) add_compile_definitions(HAS_ARM_NEON)
endif()
endif() endif()
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "") set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
@@ -267,6 +272,10 @@ set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
set(CPACK_RPM_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR}) set(CPACK_RPM_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
set(CPACK_RPM_SPEC_INSTALL_POST "/bin/true") # avoid stripping set(CPACK_RPM_SPEC_INSTALL_POST "/bin/true") # avoid stripping
set(CPACK_RPM_PACKAGE_LICENSE "Apache 2.0") set(CPACK_RPM_PACKAGE_LICENSE "Apache 2.0")
set(CPACK_RPM_FILE_NAME RPM-DEFAULT)
# deb
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
include(CPack) include(CPack)

View File

@@ -29,6 +29,7 @@ limitations under the License.
#include <span> #include <span>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <type_traits>
#include <utility> #include <utility>
#ifdef HAS_AVX #ifdef HAS_AVX
@@ -39,6 +40,8 @@ limitations under the License.
#include <memcheck.h> #include <memcheck.h>
using namespace weaselab;
// Use assert for checking potentially complex properties during tests. // Use assert for checking potentially complex properties during tests.
// Use assume to hint simple properties to the optimizer. // Use assume to hint simple properties to the optimizer.
@@ -558,7 +561,6 @@ template <class T> struct BoundedFreeListAllocator {
} }
void release(T *p) { void release(T *p) {
static_assert(std::is_trivially_destructible_v<T>);
if (freeListBytes >= kFreeListMaxMemory) { if (freeListBytes >= kFreeListMaxMemory) {
removeNode(p); removeNode(p);
return safe_free(p, sizeof(T) + p->partialKeyCapacity); return safe_free(p, sizeof(T) + p->partialKeyCapacity);
@@ -953,6 +955,42 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
assert(self->getType() == Type_Node16); assert(self->getType() == Type_Node16);
++self->numChildren; ++self->numChildren;
#ifdef HAS_AVX
__m128i key_vec = _mm_set1_epi8(index);
__m128i indices;
memcpy(&indices, self16->index, sizeof(self16->index));
__m128i results = _mm_cmpeq_epi8(key_vec, _mm_min_epu8(key_vec, indices));
int mask = (1 << (self->numChildren - 1)) - 1;
uint32_t bitfield = _mm_movemask_epi8(results) & mask;
bitfield |= uint32_t(1) << (self->numChildren - 1);
int i = std::countr_zero(bitfield);
if (i < self->numChildren - 1) {
memmove(self16->index + i + 1, self16->index + i,
self->numChildren - (i + 1));
memmove(self16->children + i + 1, self16->children + i,
(self->numChildren - (i + 1)) * sizeof(Child));
}
#elif defined(HAS_ARM_NEON)
uint8x16_t indices;
memcpy(&indices, self16->index, sizeof(self16->index));
// 0xff for each leq
auto results = vcleq_u8(vdupq_n_u8(index), indices);
uint64_t mask = (uint64_t(1) << ((self->numChildren - 1) * 4)) - 1;
// 0xf for each 0xff (within mask)
uint64_t bitfield =
vget_lane_u64(
vreinterpret_u64_u8(vshrn_n_u16(vreinterpretq_u16_u8(results), 4)),
0) &
mask;
bitfield |= uint64_t(0xf) << ((self->numChildren - 1) * 4);
int i = std::countr_zero(bitfield) / 4;
if (i < self->numChildren - 1) {
memmove(self16->index + i + 1, self16->index + i,
self->numChildren - (i + 1));
memmove(self16->children + i + 1, self16->children + i,
(self->numChildren - (i + 1)) * sizeof(Child));
}
#else
int i = 0; int i = 0;
for (; i < int(self->numChildren) - 1; ++i) { for (; i < int(self->numChildren) - 1; ++i) {
if (int(self16->index[i]) > int(index)) { if (int(self16->index[i]) > int(index)) {
@@ -963,6 +1001,7 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
break; break;
} }
} }
#endif
self16->index[i] = index; self16->index[i] = index;
auto &result = self16->children[i].child; auto &result = self16->children[i].child;
result = nullptr; result = nullptr;
@@ -978,8 +1017,8 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
self = newSelf; self = newSelf;
goto insert256; goto insert256;
} }
insert48:
insert48:
auto *self48 = static_cast<Node48 *>(self); auto *self48 = static_cast<Node48 *>(self);
self48->bitSet.set(index); self48->bitSet.set(index);
++self->numChildren; ++self->numChildren;
@@ -991,6 +1030,7 @@ Node *&getOrCreateChild(Node *&self, uint8_t index,
return result; return result;
} }
case Type_Node256: { case Type_Node256: {
insert256: insert256:
auto *self256 = static_cast<Node256 *>(self); auto *self256 = static_cast<Node256 *>(self);
++self->numChildren; ++self->numChildren;
@@ -2607,16 +2647,25 @@ ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
__attribute__((__visibility__("default"))) void __attribute__((__visibility__("default"))) void
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count, ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
int64_t writeVersion) { int64_t writeVersion) {
((ConflictSet::Impl *)cs)->addWrites(writes, count, writeVersion); auto *impl = ((ConflictSet::Impl *)cs);
mallocBytesDelta = 0;
impl->addWrites(writes, count, writeVersion);
impl->totalBytes += mallocBytesDelta;
} }
__attribute__((__visibility__("default"))) void __attribute__((__visibility__("default"))) void
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) { ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion); auto *impl = ((ConflictSet::Impl *)cs);
mallocBytesDelta = 0;
impl->setOldestVersion(oldestVersion);
impl->totalBytes += mallocBytesDelta;
} }
__attribute__((__visibility__("default"))) void * __attribute__((__visibility__("default"))) void *
ConflictSet_create(int64_t oldestVersion) { ConflictSet_create(int64_t oldestVersion) {
return new (safe_malloc(sizeof(ConflictSet::Impl))) mallocBytesDelta = 0;
auto *result = new (safe_malloc(sizeof(ConflictSet::Impl)))
ConflictSet::Impl{oldestVersion}; ConflictSet::Impl{oldestVersion};
result->totalBytes += mallocBytesDelta;
return result;
} }
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) { __attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
using Impl = ConflictSet::Impl; using Impl = ConflictSet::Impl;
@@ -2630,6 +2679,12 @@ ConflictSet_getBytes(void *cs) {
} }
} }
// Make sure abi is well-defined
static_assert(std::is_standard_layout_v<ConflictSet::Result>);
static_assert(std::is_standard_layout_v<ConflictSet::Key>);
static_assert(std::is_standard_layout_v<ConflictSet::ReadRange>);
static_assert(std::is_standard_layout_v<ConflictSet::WriteRange>);
namespace { namespace {
std::string getSearchPathPrintable(Node *n) { std::string getSearchPathPrintable(Node *n) {
@@ -2842,8 +2897,8 @@ Iterator firstGeq(Node *n, std::string_view key) {
} }
} }
bool checkCorrectness(Node *node, int64_t oldestVersion, [[maybe_unused]] bool checkCorrectness(Node *node, int64_t oldestVersion,
ConflictSet::Impl *impl) { ConflictSet::Impl *impl) {
bool success = true; bool success = true;
checkParentPointers(node, success); checkParentPointers(node, success);

View File

@@ -2,6 +2,8 @@
#include "ConflictSet.h" #include "ConflictSet.h"
using namespace weaselab;
#include <bit> #include <bit>
#include <cassert> #include <cassert>
#include <compare> #include <compare>
@@ -60,6 +62,10 @@ inline int64_t peakMallocBytes = 0;
inline thread_local int64_t mallocBytesDelta = 0; inline thread_local int64_t mallocBytesDelta = 0;
#ifndef NDEBUG
constexpr auto kMallocHeaderSize = 16;
#endif
// malloc that aborts on OOM and thus always returns a non-null pointer. Must be // malloc that aborts on OOM and thus always returns a non-null pointer. Must be
// paired with `safe_free`. // paired with `safe_free`.
__attribute__((always_inline)) inline void *safe_malloc(size_t s) { __attribute__((always_inline)) inline void *safe_malloc(size_t s) {
@@ -69,18 +75,20 @@ __attribute__((always_inline)) inline void *safe_malloc(size_t s) {
if (mallocBytes > peakMallocBytes) { if (mallocBytes > peakMallocBytes) {
peakMallocBytes = mallocBytes; peakMallocBytes = mallocBytes;
} }
void *p = malloc(s);
if (p == nullptr) {
abort();
}
return p;
#else
void *p = malloc(s);
if (p == nullptr) {
abort();
}
return p;
#endif #endif
void *p = malloc(s
#ifndef NDEBUG
+ kMallocHeaderSize
#endif
);
if (p == nullptr) {
abort();
}
#ifndef NDEBUG
memcpy(p, &s, sizeof(s));
(char *&)p += kMallocHeaderSize;
#endif
return p;
} }
// Must be paired with `safe_malloc`. // Must be paired with `safe_malloc`.
@@ -93,6 +101,12 @@ __attribute__((always_inline)) inline void safe_free(void *p, size_t s) {
mallocBytes -= s; mallocBytes -= s;
free(p); free(p);
#else #else
#ifndef NDEBUG
(char *&)p -= kMallocHeaderSize;
size_t expected;
memcpy(&expected, p, sizeof(expected));
assert(s == expected);
#endif
free(p); free(p);
#endif #endif
} }

11
Jenkinsfile vendored
View File

@@ -48,6 +48,17 @@ pipeline {
recordIssues(tools: [clang()]) recordIssues(tools: [clang()])
} }
} }
stage('SIMD fallback') {
agent {
dockerfile {
args '-v /home/jenkins/ccache:/ccache'
reuseNode true
}
}
steps {
CleanBuildAndTest("-DUSE_SIMD_FALLBACK=ON")
}
}
stage('Release [gcc]') { stage('Release [gcc]') {
agent { agent {
dockerfile { dockerfile {

104
README.md
View File

@@ -9,49 +9,49 @@ Hardware for all benchmarks is a mac m1 2020.
## Skip list ## Skip list
``` ```
New conflict set: 1.964 sec New conflict set: 1.957 sec
0.637 Mtransactions/sec 0.639 Mtransactions/sec
2.546 Mkeys/sec 2.555 Mkeys/sec
Detect only: 1.859 sec Detect only: 1.845 sec
0.672 Mtransactions/sec 0.678 Mtransactions/sec
2.690 Mkeys/sec 2.710 Mkeys/sec
Skiplist only: 1.275 sec Skiplist only: 1.263 sec
0.980 Mtransactions/sec 0.990 Mtransactions/sec
3.921 Mkeys/sec 3.960 Mkeys/sec
Performance counters: Performance counters:
Build: 0.0496 Build: 0.0546
Add: 0.0539 Add: 0.0563
Detect: 1.86 Detect: 1.84
D.Sort: 0.412 D.Sort: 0.412
D.Combine: 0.0139 D.Combine: 0.0141
D.CheckRead: 0.682 D.CheckRead: 0.671
D.CheckIntraBatch: 0.00673 D.CheckIntraBatch: 0.0068
D.MergeWrite: 0.593 D.MergeWrite: 0.592
D.RemoveBefore: 0.148 D.RemoveBefore: 0.146
``` ```
## Radix tree (this implementation) ## Radix tree (this implementation)
``` ```
New conflict set: 1.289 sec New conflict set: 1.366 sec
0.970 Mtransactions/sec 0.915 Mtransactions/sec
3.879 Mkeys/sec 3.660 Mkeys/sec
Detect only: 1.199 sec Detect only: 1.248 sec
1.043 Mtransactions/sec 1.002 Mtransactions/sec
4.170 Mkeys/sec 4.007 Mkeys/sec
Skiplist only: 0.542 sec Skiplist only: 0.573 sec
2.305 Mtransactions/sec 2.182 Mtransactions/sec
9.220 Mkeys/sec 8.730 Mkeys/sec
Performance counters: Performance counters:
Build: 0.0395 Build: 0.0594
Add: 0.0492 Add: 0.0572
Detect: 1.2 Detect: 1.25
D.Sort: 0.411 D.Sort: 0.418
D.Combine: 0.0135 D.Combine: 0.0149
D.CheckRead: 0.22 D.CheckRead: 0.232
D.CheckIntraBatch: 0.00652 D.CheckIntraBatch: 0.0067
D.MergeWrite: 0.322 D.MergeWrite: 0.341
D.RemoveBefore: 0.223 D.RemoveBefore: 0.232
``` ```
# Our benchmark # Our benchmark
@@ -60,25 +60,25 @@ Performance counters:
| ns/op | op/s | err% | total | benchmark | ns/op | op/s | err% | total | benchmark
|--------------------:|--------------------:|--------:|----------:|:---------- |--------------------:|--------------------:|--------:|----------:|:----------
| 249.47 | 4,008,533.72 | 0.5% | 0.01 | `point reads` | 246.99 | 4,048,700.59 | 0.2% | 0.01 | `point reads`
| 236.78 | 4,223,252.12 | 0.4% | 0.01 | `prefix reads` | 260.16 | 3,843,784.65 | 0.1% | 0.01 | `prefix reads`
| 432.76 | 2,310,737.74 | 0.1% | 0.01 | `range reads` | 493.35 | 2,026,953.19 | 0.1% | 0.01 | `range reads`
| 449.42 | 2,225,105.96 | 0.5% | 0.01 | `point writes` | 462.05 | 2,164,289.23 | 0.6% | 0.01 | `point writes`
| 438.47 | 2,280,674.39 | 0.3% | 0.01 | `prefix writes` | 448.19 | 2,231,205.25 | 0.9% | 0.01 | `prefix writes`
| 242.92 | 4,116,581.59 | 3.7% | 0.02 | `range writes` | 255.83 | 3,908,845.72 | 1.5% | 0.02 | `range writes`
| 553.31 | 1,807,319.60 | 0.7% | 0.01 | `monotonic increasing point writes` | 582.63 | 1,716,349.02 | 1.3% | 0.01 | `monotonic increasing point writes`
## Radix tree (this implementation) ## Radix tree (this implementation)
| ns/op | op/s | err% | total | benchmark | ns/op | op/s | err% | total | benchmark
|--------------------:|--------------------:|--------:|----------:|:---------- |--------------------:|--------------------:|--------:|----------:|:----------
| 19.35 | 51,669,510.11 | 0.2% | 0.01 | `point reads` | 19.42 | 51,483,206.67 | 0.3% | 0.01 | `point reads`
| 56.64 | 17,655,057.00 | 0.2% | 0.01 | `prefix reads` | 58.43 | 17,115,612.57 | 0.1% | 0.01 | `prefix reads`
| 217.04 | 4,607,450.05 | 0.2% | 0.01 | `range reads` | 216.09 | 4,627,766.60 | 0.2% | 0.01 | `range reads`
| 26.31 | 38,012,015.29 | 0.0% | 0.01 | `point writes` | 28.35 | 35,267,567.72 | 0.2% | 0.01 | `point writes`
| 41.45 | 24,124,003.19 | 0.2% | 0.01 | `prefix writes` | 43.43 | 23,026,226.17 | 0.2% | 0.01 | `prefix writes`
| 48.33 | 20,691,082.14 | 0.0% | 0.01 | `range writes` | 50.00 | 20,000,000.00 | 0.0% | 0.01 | `range writes`
| 84.92 | 11,775,856.81 | 4.0% | 0.01 | `monotonic increasing point writes` | 92.38 | 10,824,863.69 | 4.1% | 0.01 | `monotonic increasing point writes`
# "Real data" test # "Real data" test
@@ -87,13 +87,13 @@ Point queries only, best of three runs. Gc ratio is the ratio of time spent doin
## skip list ## skip list
``` ```
Check: 11.267 seconds, 331.812 MB/s, Add: 5.33323 seconds, 131.635 MB/s, Gc ratio: 45.4671% Check: 11.3385 seconds, 329.718 MB/s, Add: 5.35612 seconds, 131.072 MB/s, Gc ratio: 45.7173%
``` ```
## radix tree ## radix tree
``` ```
Check: 2.48508 seconds, 1504.38 MB/s, Add: 2.07295 seconds, 338.666 MB/s, Gc ratio: 42.2825% Check: 2.48583 seconds, 1503.93 MB/s, Add: 2.12768 seconds, 329.954 MB/s, Gc ratio: 41.7943%
``` ```
## hash table ## hash table
@@ -101,5 +101,5 @@ Check: 2.48508 seconds, 1504.38 MB/s, Add: 2.07295 seconds, 338.666 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: 1.83931 seconds, 2032.56 MB/s, Add: 0.607861 seconds, 1154.93 MB/s, Gc ratio: 48.7142% Check: 1.83386 seconds, 2038.6 MB/s, Add: 0.601411 seconds, 1167.32 MB/s, Gc ratio: 48.9776%
``` ```

View File

@@ -8,6 +8,8 @@
#include <unistd.h> #include <unistd.h>
#include <vector> #include <vector>
using namespace weaselab;
double now() { double now() {
return std::chrono::duration_cast<std::chrono::nanoseconds>( return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now().time_since_epoch()) std::chrono::steady_clock::now().time_since_epoch())
@@ -28,7 +30,7 @@ constexpr inline size_t rightAlign(size_t offset, size_t alignment) {
int main(int argc, const char **argv) { int main(int argc, const char **argv) {
// Use with this dataset https://snap.stanford.edu/data/memetracker9.html // Use with this dataset https://snap.stanford.edu/data/memetracker9.html
// Preprocess the files with `sed -i '' '/^Q/d'` // Preprocess the files with `sed -i'' '/^Q/d'`
double checkTime = 0; double checkTime = 0;
double addTime = 0; double addTime = 0;

View File

@@ -7,6 +7,7 @@ int main(void) {
ConflictSet_WriteRange w; ConflictSet_WriteRange w;
ConflictSet_Result result; ConflictSet_Result result;
ConflictSet_ReadRange r; ConflictSet_ReadRange r;
int64_t bytes;
w.begin.p = (const uint8_t *)"0000"; w.begin.p = (const uint8_t *)"0000";
w.begin.len = 4; w.begin.len = 4;
w.end.len = 0; w.end.len = 0;
@@ -17,6 +18,8 @@ int main(void) {
r.readVersion = 0; r.readVersion = 0;
ConflictSet_check(cs, &r, &result, 1); ConflictSet_check(cs, &r, &result, 1);
assert(result == ConflictSet_Conflict); assert(result == ConflictSet_Conflict);
bytes = ConflictSet_getBytes(cs);
assert(bytes > 0);
ConflictSet_destroy(cs); ConflictSet_destroy(cs);
return 0; return 0;
} }

View File

@@ -2,6 +2,8 @@
#include <cassert> #include <cassert>
using namespace weaselab;
int main(void) { int main(void) {
ConflictSet cs(0); ConflictSet cs(0);
ConflictSet::WriteRange w; ConflictSet::WriteRange w;
@@ -17,4 +19,6 @@ int main(void) {
r.readVersion = 0; r.readVersion = 0;
cs.check(&r, &result, 1); cs.check(&r, &result, 1);
assert(result == ConflictSet::Conflict); assert(result == ConflictSet::Conflict);
int64_t bytes = cs.getBytes();
assert(bytes > 0);
} }

View File

@@ -1,32 +1,19 @@
giff --git a/fdbserver/CMakeLists.txt b/fdbserver/CMakeLists.txt diff --git a/fdbserver/CMakeLists.txt b/fdbserver/CMakeLists.txt
index 3f353c2ef..cd0834761 100644 index 3f353c2ef..074a18628 100644
--- a/fdbserver/CMakeLists.txt --- a/fdbserver/CMakeLists.txt
+++ b/fdbserver/CMakeLists.txt +++ b/fdbserver/CMakeLists.txt
@@ -22,6 +22,9 @@ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/workloads) @@ -22,6 +22,9 @@ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/workloads)
add_flow_target(EXECUTABLE NAME fdbserver SRCS ${FDBSERVER_SRCS}) add_flow_target(EXECUTABLE NAME fdbserver SRCS ${FDBSERVER_SRCS})
+find_package(ConflictSet) +find_package(conflict-set)
+target_link_libraries(fdbserver PRIVATE conflict_set_static) +target_link_libraries(fdbserver PRIVATE conflict-set-static)
+ +
if (WITH_SWIFT) if (WITH_SWIFT)
# Setup the Swift sources in FDBServer. # Setup the Swift sources in FDBServer.
include(FindSwiftLibs) include(FindSwiftLibs)
diff --git a/fdbserver/Resolver.actor.cpp b/fdbserver/Resolver.actor.cpp
index bf4118f5f..d3b4eaad8 100644
--- a/fdbserver/Resolver.actor.cpp
+++ b/fdbserver/Resolver.actor.cpp
@@ -132,7 +132,7 @@ struct Resolver : ReferenceCounted<Resolver> {
AsyncVar<int64_t> totalStateBytes;
AsyncTrigger checkNeededVersion;
std::map<NetworkAddress, ProxyRequestsInfo> proxyInfoMap;
- ConflictSet* conflictSet;
+ ConflictSet2* conflictSet;
TransientStorageMetricSample iopsSample;
// Use LogSystem as backend for txnStateStore. However, the real commit
diff --git a/fdbserver/SkipList.cpp b/fdbserver/SkipList.cpp diff --git a/fdbserver/SkipList.cpp b/fdbserver/SkipList.cpp
index b48d32c6b..da106b5d2 100644 index b48d32c6b..da99e03aa 100644
--- a/fdbserver/SkipList.cpp --- a/fdbserver/SkipList.cpp
+++ b/fdbserver/SkipList.cpp +++ b/fdbserver/SkipList.cpp
@@ -25,6 +25,7 @@ @@ -25,6 +25,7 @@
@@ -46,50 +33,35 @@ index b48d32c6b..da106b5d2 100644
static std::vector<PerfDoubleCounter*> skc; static std::vector<PerfDoubleCounter*> skc;
static thread_local uint32_t g_seed = 0; static thread_local uint32_t g_seed = 0;
@@ -782,26 +785,34 @@ private: @@ -783,10 +786,14 @@ private:
}
}; };
-struct ConflictSet { struct ConflictSet {
- ConflictSet() : removalKey(makeString(0)), oldestVersion(0) {} - ConflictSet() : removalKey(makeString(0)), oldestVersion(0) {}
- ~ConflictSet() {} + ConflictSet() : versionHistory(0), removalKey(makeString(0)), oldestVersion(0) {}
+struct ConflictSet2 { ~ConflictSet() {}
+ ConflictSet2() : versionHistory(0), removalKey(makeString(0)), oldestVersion(0) {}
+ ~ConflictSet2() {}
+#if USE_RADIX_TREE +#if USE_RADIX_TREE
+ ConflictSet versionHistory; + weaselab::ConflictSet versionHistory;
+#else +#else
SkipList versionHistory; SkipList versionHistory;
+#endif +#endif
Key removalKey; Key removalKey;
Version oldestVersion; Version oldestVersion;
}; };
@@ -795,7 +802,11 @@ ConflictSet* newConflictSet() {
-ConflictSet* newConflictSet() { return new ConflictSet;
- return new ConflictSet;
+ConflictSet2* newConflictSet() {
+ return new ConflictSet2;
} }
-void clearConflictSet(ConflictSet* cs, Version v) { void clearConflictSet(ConflictSet* cs, Version v) {
- SkipList(v).swap(cs->versionHistory); - SkipList(v).swap(cs->versionHistory);
+void clearConflictSet(ConflictSet2* cs, Version v) {
+#if USE_RADIX_TREE +#if USE_RADIX_TREE
+ cs->versionHistory = ConflictSet{ 0 }; + cs->versionHistory = weaselab::ConflictSet{ 0 };
+#else +#else
+ SkipList().swap(cs->versionHistory); + SkipList().swap(cs->versionHistory);
+#endif +#endif
} }
-void destroyConflictSet(ConflictSet* cs) { void destroyConflictSet(ConflictSet* cs) {
+void destroyConflictSet(ConflictSet2* cs) {
delete cs; delete cs;
}
-ConflictBatch::ConflictBatch(ConflictSet* cs,
+ConflictBatch::ConflictBatch(ConflictSet2* cs,
std::map<int, VectorRef<int>>* conflictingKeyRangeMap,
Arena* resolveBatchReplyArena)
: cs(cs), transactionCount(0), conflictingKeyRangeMap(conflictingKeyRangeMap),
@@ -971,11 +982,15 @@ void ConflictBatch::detectConflicts(Version now, @@ -971,11 +982,15 @@ void ConflictBatch::detectConflicts(Version now,
t = timer(); t = timer();
if (newOldestVersion > cs->oldestVersion) { if (newOldestVersion > cs->oldestVersion) {
@@ -112,7 +84,7 @@ index b48d32c6b..da106b5d2 100644
+#if USE_RADIX_TREE +#if USE_RADIX_TREE
+ Arena arena; + Arena arena;
+ auto* reads = new (arena) ConflictSet::ReadRange[combinedReadConflictRanges.size()]; + auto* reads = new (arena) weaselab::ConflictSet::ReadRange[combinedReadConflictRanges.size()];
+ +
+ for (int i = 0; i < combinedReadConflictRanges.size(); ++i) { + for (int i = 0; i < combinedReadConflictRanges.size(); ++i) {
+ auto& read = reads[i]; + auto& read = reads[i];
@@ -122,11 +94,11 @@ index b48d32c6b..da106b5d2 100644
+ read.end.p = combinedReadConflictRanges[i].end.begin(); + read.end.p = combinedReadConflictRanges[i].end.begin();
+ read.end.len = combinedReadConflictRanges[i].end.size(); + read.end.len = combinedReadConflictRanges[i].end.size();
+ } + }
+ auto* results = new (arena) ConflictSet::Result[combinedReadConflictRanges.size()]; + auto* results = new (arena) weaselab::ConflictSet::Result[combinedReadConflictRanges.size()];
+ cs->versionHistory.check(reads, results, combinedReadConflictRanges.size()); + cs->versionHistory.check(reads, results, combinedReadConflictRanges.size());
+ +
+ for (int i = 0; i < combinedReadConflictRanges.size(); ++i) { + for (int i = 0; i < combinedReadConflictRanges.size(); ++i) {
+ if (results[i] == ConflictSet::Conflict) { + if (results[i] == weaselab::ConflictSet::Conflict) {
+ transactionConflictStatus[combinedReadConflictRanges[i].transaction] = true; + transactionConflictStatus[combinedReadConflictRanges[i].transaction] = true;
+ if (combinedReadConflictRanges[i].conflictingKeyRange != nullptr) { + if (combinedReadConflictRanges[i].conflictingKeyRange != nullptr) {
+ combinedReadConflictRanges[i].conflictingKeyRange->push_back(*combinedReadConflictRanges[i].cKRArena, + combinedReadConflictRanges[i].conflictingKeyRange->push_back(*combinedReadConflictRanges[i].cKRArena,
@@ -147,7 +119,7 @@ index b48d32c6b..da106b5d2 100644
+#if USE_RADIX_TREE +#if USE_RADIX_TREE
+ Arena arena; + Arena arena;
+ auto* writes = new (arena) ConflictSet::WriteRange[combinedWriteConflictRanges.size()]; + auto* writes = new (arena) weaselab::ConflictSet::WriteRange[combinedWriteConflictRanges.size()];
+ +
+ for (int i = 0; i < combinedWriteConflictRanges.size(); ++i) { + for (int i = 0; i < combinedWriteConflictRanges.size(); ++i) {
+ auto& write = writes[i]; + auto& write = writes[i];
@@ -164,15 +136,6 @@ index b48d32c6b..da106b5d2 100644
} }
void ConflictBatch::combineWriteConflictRanges() { void ConflictBatch::combineWriteConflictRanges() {
@@ -1115,7 +1171,7 @@ void skipListTest() {
double start;
- ConflictSet* cs = newConflictSet();
+ ConflictSet2* cs = newConflictSet();
Arena testDataArena;
VectorRef<VectorRef<KeyRangeRef>> testData;
@@ -1197,6 +1253,4 @@ void skipListTest() { @@ -1197,6 +1253,4 @@ void skipListTest() {
for (const auto& counter : skc) { for (const auto& counter : skc) {
printf("%20s: %s\n", counter->getMetric().name().c_str(), counter->getMetric().formatted().c_str()); printf("%20s: %s\n", counter->getMetric().name().c_str(), counter->getMetric().formatted().c_str());
@@ -180,35 +143,3 @@ index b48d32c6b..da106b5d2 100644
- -
- printf("%d entries in version history\n", cs->versionHistory.count()); - printf("%d entries in version history\n", cs->versionHistory.count());
} }
diff --git a/fdbserver/include/fdbserver/ConflictSet.h b/fdbserver/include/fdbserver/ConflictSet.h
index 90ed2c406..b7e31217c 100644
--- a/fdbserver/include/fdbserver/ConflictSet.h
+++ b/fdbserver/include/fdbserver/ConflictSet.h
@@ -28,13 +28,13 @@
#include "fdbclient/CommitTransaction.h"
#include "fdbserver/ResolverBug.h"
-struct ConflictSet;
-ConflictSet* newConflictSet();
-void clearConflictSet(ConflictSet*, Version);
-void destroyConflictSet(ConflictSet*);
+struct ConflictSet2;
+ConflictSet2* newConflictSet();
+void clearConflictSet(ConflictSet2*, Version);
+void destroyConflictSet(ConflictSet2*);
struct ConflictBatch {
- explicit ConflictBatch(ConflictSet*,
+ explicit ConflictBatch(ConflictSet2*,
std::map<int, VectorRef<int>>* conflictingKeyRangeMap = nullptr,
Arena* resolveBatchReplyArena = nullptr);
~ConflictBatch();
@@ -54,7 +54,7 @@ struct ConflictBatch {
void GetTooOldTransactions(std::vector<int>& tooOldTransactions);
private:
- ConflictSet* cs;
+ ConflictSet2* cs;
Standalone<VectorRef<struct TransactionInfo*>> transactionInfo;
std::vector<struct KeyInfo> points;
int transactionCount;

View File

@@ -19,6 +19,7 @@ limitations under the License.
#include <stdint.h> #include <stdint.h>
#ifdef __cplusplus #ifdef __cplusplus
namespace weaselab {
/** A data structure for optimistic concurrency control on ranges of /** A data structure for optimistic concurrency control on ranges of
* bitwise-lexicographically-ordered keys. * bitwise-lexicographically-ordered keys.
* *
@@ -26,7 +27,7 @@ limitations under the License.
* - It's safe to operate on two different ConflictSets in two different * - It's safe to operate on two different ConflictSets in two different
* threads concurrently * threads concurrently
* - It's safe to have multiple threads operating on the same ConflictSet * - It's safe to have multiple threads operating on the same ConflictSet
* concurrently if and only if all threads only call `check`. * concurrently if and only if all threads only call const methods
*/ */
struct __attribute__((__visibility__("default"))) ConflictSet { struct __attribute__((__visibility__("default"))) ConflictSet {
enum Result { enum Result {
@@ -100,6 +101,7 @@ struct __attribute__((__visibility__("default"))) ConflictSet {
private: private:
Impl *impl; Impl *impl;
}; };
} /* namespace weaselab */
#else #else
@@ -110,7 +112,8 @@ private:
* - It's safe to operate on two different ConflictSets in two different * - It's safe to operate on two different ConflictSets in two different
* threads concurrently * threads concurrently
* - It's safe to have multiple threads operating on the same ConflictSet * - It's safe to have multiple threads operating on the same ConflictSet
* concurrently if and only if all threads only call `check`. * concurrently if and only if all threads only call functions that accept a
* const ConflictSet pointer
*/ */
typedef struct ConflictSet ConflictSet; typedef struct ConflictSet ConflictSet;
@@ -150,7 +153,8 @@ typedef struct {
} ConflictSet_WriteRange; } ConflictSet_WriteRange;
/** The result of checking reads[i] is written in results[i] */ /** The result of checking reads[i] is written in results[i] */
void ConflictSet_check(ConflictSet *cs, const ConflictSet_ReadRange *reads, void ConflictSet_check(const ConflictSet *cs,
const ConflictSet_ReadRange *reads,
ConflictSet_Result *results, int count); ConflictSet_Result *results, int count);
/** `writes` must be sorted ascending, and must not have adjacent or /** `writes` must be sorted ascending, and must not have adjacent or
@@ -173,6 +177,6 @@ ConflictSet *ConflictSet_create(int64_t oldestVersion);
void ConflictSet_destroy(ConflictSet *cs); void ConflictSet_destroy(ConflictSet *cs);
/** Returns the total bytes in use by this ConflictSet */ /** Returns the total bytes in use by this ConflictSet */
int64_t ConflictSet_getBytes(ConflictSet *cs); int64_t ConflictSet_getBytes(const ConflictSet *cs);
#endif #endif

View File

@@ -4,14 +4,14 @@ ConflictSet_create
ConflictSet_destroy ConflictSet_destroy
ConflictSet_getBytes ConflictSet_getBytes
ConflictSet_setOldestVersion ConflictSet_setOldestVersion
_ZN11ConflictSet16setOldestVersionEl _ZN8weaselab11ConflictSet16setOldestVersionEl
_ZN11ConflictSet9addWritesEPKNS_10WriteRangeEil _ZN8weaselab11ConflictSet9addWritesEPKNS0_10WriteRangeEil
_ZN11ConflictSetaSEOS_ _ZN8weaselab11ConflictSetaSEOS0_
_ZN11ConflictSetC1El _ZN8weaselab11ConflictSetC1El
_ZN11ConflictSetC1EOS_ _ZN8weaselab11ConflictSetC1EOS0_
_ZN11ConflictSetC2El _ZN8weaselab11ConflictSetC2El
_ZN11ConflictSetC2EOS_ _ZN8weaselab11ConflictSetC2EOS0_
_ZN11ConflictSetD1Ev _ZN8weaselab11ConflictSetD1Ev
_ZN11ConflictSetD2Ev _ZN8weaselab11ConflictSetD2Ev
_ZNK11ConflictSet5checkEPKNS_9ReadRangeEPNS_6ResultEi _ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
_ZNK11ConflictSet8getBytesEv _ZNK8weaselab11ConflictSet8getBytesEv