31 Commits

Author SHA1 Message Date
84c6a2bfc2 Disable debug symbols and frame pointer for macos
All checks were successful
Tests / Clang total: 1420, passed: 1420
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 1418, passed: 1418
Tests / SIMD fallback total: 1420, passed: 1420
Tests / Release [gcc] total: 1420, passed: 1420
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1057, passed: 1057
Tests / Coverage total: 1067, passed: 1067
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 99.34% (1817/1829) * Branch Coverage: 67.49% (1503/2227) * Complexity Density: 0.00 * Lines of Code: 1829 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
This causes some versions of clang to crash
2024-08-08 11:51:13 -07:00
b5772a6aa0 Revert "Use homebrew clang for packaging for macos"
This reverts commit c20c08f112.
2024-08-08 11:50:13 -07:00
e6c39981b9 Bump version
All checks were successful
Tests / Clang total: 1420, passed: 1420
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 1418, passed: 1418
Tests / SIMD fallback total: 1420, passed: 1420
Tests / Release [gcc] total: 1420, passed: 1420
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1057, passed: 1057
Tests / Coverage total: 1067, passed: 1067
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 99.34% (1817/1829) * Branch Coverage: 67.49% (1503/2227) * Complexity Density: 0.00 * Lines of Code: 1829 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-08-08 11:26:49 -07:00
c20c08f112 Use homebrew clang for packaging for macos
All checks were successful
Tests / Clang total: 1420, passed: 1420
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 1418, passed: 1418
Tests / SIMD fallback total: 1420, passed: 1420
Tests / Release [gcc] total: 1420, passed: 1420
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1057, passed: 1057
Tests / Coverage total: 1067, passed: 1067
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 99.34% (1817/1829) * Branch Coverage: 67.49% (1503/2227) * Complexity Density: 0.00 * Lines of Code: 1829 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
The default segfaults now for some reason
2024-08-08 11:06:26 -07:00
ac98d4a443 Remove always_inline attribute - it wasn't affecting codegen
Some checks failed
Tests / Clang total: 1420, passed: 1420
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 1418, passed: 1418
Tests / SIMD fallback total: 1420, passed: 1420
Tests / Release [gcc] total: 1420, passed: 1420
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1057, passed: 1057
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-08-08 10:44:54 -07:00
1d9e8ab68b Add missing test coverage for fixupMaxVersion for Node256
All checks were successful
Tests / Clang total: 1420, passed: 1420
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 1418, passed: 1418
Tests / SIMD fallback total: 1420, passed: 1420
Tests / Release [gcc] total: 1420, passed: 1420
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1057, passed: 1057
Tests / Coverage total: 1067, passed: 1067
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 99.34% (1816/1828) * Branch Coverage: 65.33% (1528/2339) * Complexity Density: 0.00 * Lines of Code: 1828 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-08-07 17:02:19 -07:00
7d86beb14c Revert 29c05187fb
We're already doing this in checkRangeStartsWith
2024-08-07 16:51:23 -07:00
2fa954ed36 Fix compiler warning 2024-08-07 16:25:49 -07:00
ded6e7fc2c Require entry present for fixupMaxVersion 2024-08-06 17:59:38 -07:00
781ba15cae Enforce that root does not have a partial key 2024-08-06 17:55:33 -07:00
9b56a74b2f Update corpus 2024-08-06 17:43:48 -07:00
6da9cbdec9 Update benchmarks
Some checks failed
Tests / Clang total: 1479, passed: 1479
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 1477, passed: 1477
Tests / SIMD fallback total: 1479, passed: 1479
Tests / Release [gcc] total: 1479, passed: 1479
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1102, passed: 1102
Tests / Coverage total: 1111, passed: 1111
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 98.00% (1812/1849) * Branch Coverage: 64.20% (1521/2369) * Complexity Density: 0.00 * Lines of Code: 1849 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-08-06 16:07:16 -07:00
29c05187fb Early return if common prefix isn't a prefix of an entry
Some checks failed
Tests / Clang total: 1479, passed: 1479
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 1477, passed: 1477
Tests / SIMD fallback total: 1479, passed: 1479
Tests / Release [gcc] total: 1479, passed: 1479
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1102, passed: 1102
Tests / Coverage total: 1111, passed: 1111
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 98.00% (1812/1849) * Branch Coverage: 64.20% (1521/2369) * Complexity Density: 0.00 * Lines of Code: 1849 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head There was a failure building this commit
For range reads
2024-08-06 15:32:44 -07:00
d89028dd2f Inline SearchStepWise into checkRangeRead
This improves clang codegen
2024-08-06 14:45:52 -07:00
09cf807747 Avoid some branches on node type while inserting
Some checks failed
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-08-06 13:51:49 -07:00
051eb5919d Extract consumePartialKey to its own function 2024-08-06 13:20:44 -07:00
ed5589e4ed Specialize partial key split for newly created Node3 2024-08-06 13:04:19 -07:00
a7b3d8fe4c Clarify insert documentation 2024-08-06 11:29:33 -07:00
c3a047fdf8 Handle newly-created node partial key immediately 2024-08-06 09:22:21 -07:00
b4b469a175 Use maxOfMax in fixupMaxVersion
Some checks failed
Tests / Clang total: 1479, passed: 1479
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 1477, passed: 1477
Tests / SIMD fallback total: 1479, passed: 1479
Tests / Release [gcc] total: 1479, passed: 1479
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1102, passed: 1102
Tests / Coverage total: 1111, passed: 1111
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 98.83% (1776/1797) * Branch Coverage: 64.91% (1506/2320) * Complexity Density: 0.00 * Lines of Code: 1797 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head There was a failure building this commit
2024-08-05 21:41:49 -07:00
0201e27498 Remove redundant setMaxVersion calls 2024-08-05 19:29:38 -07:00
2010920a2c Correct comment 2024-08-05 19:28:24 -07:00
19af8da65c Fix endNode's max version after the fact
This sets us up to unconditionally update the max version along the
search path for inserts, and avoid dispatching on type twice per
iteration.
2024-08-05 17:50:26 -07:00
80785e3c3b Avoid switch on parent type for max version during search 2024-08-05 16:40:58 -07:00
4580ee44b4 Add range reads to ServerBench
All checks were successful
Tests / Clang total: 1479, passed: 1479
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 1477, passed: 1477
Tests / SIMD fallback total: 1479, passed: 1479
Tests / Release [gcc] total: 1479, passed: 1479
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1102, passed: 1102
Tests / Coverage total: 1111, passed: 1111
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 99.60% (1739/1746) * Branch Coverage: 64.95% (1492/2297) * Complexity Density: 0.00 * Lines of Code: 1746 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-08-05 15:44:13 -07:00
2d3985ca40 Add a simple point read/write workload to ServerBench 2024-08-05 14:37:00 -07:00
c8be68db40 Add ServerBench.cpp 2024-08-05 12:20:38 -07:00
f5d021d6b6 Add multi-version rezero16
All checks were successful
Tests / Clang total: 1479, passed: 1479
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 1477, passed: 1477
Tests / SIMD fallback total: 1479, passed: 1479
Tests / Release [gcc] total: 1479, passed: 1479
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1102, passed: 1102
Tests / Coverage total: 1111, passed: 1111
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 99.60% (1739/1746) * Branch Coverage: 64.95% (1492/2297) * Complexity Density: 0.00 * Lines of Code: 1746 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
2024-08-03 14:22:50 -07:00
1c41605b53 Use std::countr_zero instead of __builtin_ctz
Some checks reported errors
Tests / Clang total: 1479, passed: 1479
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 1477, passed: 1477
Tests / SIMD fallback total: 1479, passed: 1479
Tests / Release [gcc] total: 1479, passed: 1479
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1102, passed: 1102
Tests / Coverage total: 1111, passed: 1111
weaselab/conflict-set/pipeline/head Something is wrong with the build of this commit
Doesn't seem to affect codegen in these cases.
2024-08-03 10:01:12 -07:00
8f03a105bb Use target avx512f,avx512bw
All checks were successful
Tests / Clang total: 1479, passed: 1479
Clang |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Debug total: 1477, passed: 1477
Tests / SIMD fallback total: 1479, passed: 1479
Tests / Release [gcc] total: 1479, passed: 1479
GNU C Compiler (gcc) |Total|New|Outstanding|Fixed|Trend |:-:|:-:|:-:|:-:|:-: |0|0|0|0|:clap:
Tests / Release [gcc,aarch64] total: 1102, passed: 1102
Tests / Coverage total: 1111, passed: 1111
Code Coverage #### Project Overview No changes detected, that affect the code coverage. * Line Coverage: 99.60% (1737/1744) * Branch Coverage: 64.99% (1498/2305) * Complexity Density: 0.00 * Lines of Code: 1744 #### Quality Gates Summary Output truncated.
weaselab/conflict-set/pipeline/head This commit looks good
Appears to fix gcc build
2024-08-02 21:47:23 -07:00
0e574856be Make checkMaxBetweenExclusive a multi-version function
This introduces more branches but reduces code size
2024-08-02 21:09:55 -07:00
723 changed files with 722 additions and 254 deletions

View File

@@ -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.10 VERSION 0.0.11
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"
@@ -31,14 +31,12 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
"MinSizeRel" "RelWithDebInfo") "MinSizeRel" "RelWithDebInfo")
endif() endif()
add_compile_options( add_compile_options(-fdata-sections -ffunction-sections -Wswitch-enum
-fdata-sections -Werror=switch-enum -fPIC)
-ffunction-sections if(NOT APPLE)
-Wswitch-enum # This causes some versions of clang to crash on macos
-Werror=switch-enum add_compile_options(-g -fno-omit-frame-pointer)
-fPIC endif()
-g
-fno-omit-frame-pointer)
set(full_relro_flags "-pie;LINKER:-z,relro,-z,now,-z,noexecstack") set(full_relro_flags "-pie;LINKER:-z,relro,-z,now,-z,noexecstack")
cmake_push_check_state() cmake_push_check_state()
@@ -342,6 +340,11 @@ if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
add_executable(driver_perf TestDriver.cpp) add_executable(driver_perf TestDriver.cpp)
target_compile_definitions(driver_perf PRIVATE PERF_TEST=1) target_compile_definitions(driver_perf PRIVATE PERF_TEST=1)
target_link_libraries(driver_perf PRIVATE ${PROJECT_NAME}) target_link_libraries(driver_perf PRIVATE ${PROJECT_NAME})
# server bench
add_executable(server_bench ServerBench.cpp)
target_link_libraries(server_bench PRIVATE ${PROJECT_NAME})
set_target_properties(server_bench PROPERTIES SKIP_BUILD_RPATH ON)
endif() endif()
# packaging # packaging

View File

@@ -902,6 +902,7 @@ Node *&getChildExists(Node *self, uint8_t index) {
} }
InternalVersionT maxVersion(Node *n, ConflictSet::Impl *); InternalVersionT maxVersion(Node *n, ConflictSet::Impl *);
InternalVersionT exchangeMaxVersion(Node *n, InternalVersionT newMax);
void setMaxVersion(Node *n, ConflictSet::Impl *, InternalVersionT maxVersion); void setMaxVersion(Node *n, ConflictSet::Impl *, InternalVersionT maxVersion);
@@ -939,6 +940,54 @@ Node *getChild(Node *self, uint8_t index) {
} }
} }
struct ChildAndMaxVersion {
Node *child;
InternalVersionT maxVersion;
};
ChildAndMaxVersion getChildAndMaxVersion(Node0 *, uint8_t) { return {}; }
ChildAndMaxVersion getChildAndMaxVersion(Node3 *self, uint8_t index) {
int i = getNodeIndex(self, index);
if (i < 0) {
return {};
}
return {self->children[i], self->childMaxVersion[i]};
}
ChildAndMaxVersion getChildAndMaxVersion(Node16 *self, uint8_t index) {
int i = getNodeIndex(self, index);
if (i < 0) {
return {};
}
return {self->children[i], self->childMaxVersion[i]};
}
ChildAndMaxVersion getChildAndMaxVersion(Node48 *self, uint8_t index) {
int i = self->index[index];
if (i < 0) {
return {};
}
return {self->children[i], self->childMaxVersion[i]};
}
ChildAndMaxVersion getChildAndMaxVersion(Node256 *self, uint8_t index) {
return {self->children[index], self->childMaxVersion[index]};
}
ChildAndMaxVersion getChildAndMaxVersion(Node *self, uint8_t index) {
switch (self->getType()) {
case Type_Node0:
return getChildAndMaxVersion(static_cast<Node0 *>(self), index);
case Type_Node3:
return getChildAndMaxVersion(static_cast<Node3 *>(self), index);
case Type_Node16:
return getChildAndMaxVersion(static_cast<Node16 *>(self), index);
case Type_Node48:
return getChildAndMaxVersion(static_cast<Node48 *>(self), index);
case Type_Node256:
return getChildAndMaxVersion(static_cast<Node256 *>(self), index);
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
template <class NodeT> Node *getChildGeqSimd(NodeT *self, int child) { template <class NodeT> Node *getChildGeqSimd(NodeT *self, int child) {
static_assert(std::is_same_v<NodeT, Node3> || std::is_same_v<NodeT, Node16>); static_assert(std::is_same_v<NodeT, Node3> || std::is_same_v<NodeT, Node16>);
@@ -1075,8 +1124,10 @@ Node *getFirstChildExists(Node *self) {
} }
// Caller is responsible for assigning a non-null pointer to the returned // Caller is responsible for assigning a non-null pointer to the returned
// reference if null // reference if null. Updates child's max version to `newMaxVersion` if child
Node *&getOrCreateChild(Node *&self, uint8_t index, WriteContext *tls) { // exists but does not have a partial key.
Node *&getOrCreateChild(Node *&self, uint8_t index,
InternalVersionT newMaxVersion, WriteContext *tls) {
// Fast path for if it exists already // Fast path for if it exists already
switch (self->getType()) { switch (self->getType()) {
@@ -1086,6 +1137,9 @@ Node *&getOrCreateChild(Node *&self, uint8_t index, WriteContext *tls) {
auto *self3 = static_cast<Node3 *>(self); auto *self3 = static_cast<Node3 *>(self);
int i = getNodeIndex(self3, index); int i = getNodeIndex(self3, index);
if (i >= 0) { if (i >= 0) {
if (self3->children[i]->partialKeyLen == 0) {
self3->childMaxVersion[i] = newMaxVersion;
}
return self3->children[i]; return self3->children[i];
} }
} break; } break;
@@ -1093,6 +1147,9 @@ Node *&getOrCreateChild(Node *&self, uint8_t index, WriteContext *tls) {
auto *self16 = static_cast<Node16 *>(self); auto *self16 = static_cast<Node16 *>(self);
int i = getNodeIndex(self16, index); int i = getNodeIndex(self16, index);
if (i >= 0) { if (i >= 0) {
if (self16->children[i]->partialKeyLen == 0) {
self16->childMaxVersion[i] = newMaxVersion;
}
return self16->children[i]; return self16->children[i];
} }
} break; } break;
@@ -1100,12 +1157,23 @@ Node *&getOrCreateChild(Node *&self, uint8_t index, WriteContext *tls) {
auto *self48 = static_cast<Node48 *>(self); auto *self48 = static_cast<Node48 *>(self);
int secondIndex = self48->index[index]; int secondIndex = self48->index[index];
if (secondIndex >= 0) { if (secondIndex >= 0) {
if (self48->children[secondIndex]->partialKeyLen == 0) {
self48->childMaxVersion[secondIndex] = newMaxVersion;
self48->maxOfMax[secondIndex >> Node48::kMaxOfMaxShift] =
std::max(self48->maxOfMax[secondIndex >> Node48::kMaxOfMaxShift],
newMaxVersion);
}
return self48->children[secondIndex]; return self48->children[secondIndex];
} }
} break; } break;
case Type_Node256: { case Type_Node256: {
auto *self256 = static_cast<Node256 *>(self); auto *self256 = static_cast<Node256 *>(self);
if (auto &result = self256->children[index]; result != nullptr) { if (auto &result = self256->children[index]; result != nullptr) {
if (self256->children[index]->partialKeyLen == 0) {
self256->childMaxVersion[index] = newMaxVersion;
self256->maxOfMax[index >> Node256::kMaxOfMaxShift] = std::max(
self256->maxOfMax[index >> Node256::kMaxOfMaxShift], newMaxVersion);
}
return result; return result;
} }
} break; } break;
@@ -1332,6 +1400,29 @@ void maybeDecreaseCapacity(Node *&self, WriteContext *tls,
freeAndMakeCapacityAtLeast(self, maxCapacity, tls, impl, false); freeAndMakeCapacityAtLeast(self, maxCapacity, tls, impl, false);
} }
#if defined(HAS_AVX) && !defined(__SANITIZE_THREAD__)
// This gets covered in local development
// GCOVR_EXCL_START
__attribute__((target("avx512f"))) void rezero16(InternalVersionT *vs,
InternalVersionT zero) {
uint32_t z;
memcpy(&z, &zero, sizeof(z));
const auto zvec = _mm512_set1_epi32(z);
auto m = _mm512_cmplt_epi32_mask(
_mm512_sub_epi32(_mm512_loadu_epi32(vs), zvec), _mm512_setzero_epi32());
_mm512_mask_storeu_epi32(vs, m, zvec);
}
// GCOVR_EXCL_STOP
__attribute__((target("default")))
#endif
void rezero16(InternalVersionT *vs, InternalVersionT zero) {
for (int i = 0; i < 16; ++i) {
vs[i] = std::max(vs[i], zero);
}
}
void rezero(Node *n, InternalVersionT z) { void rezero(Node *n, InternalVersionT z) {
#if DEBUG_VERBOSE && !defined(NDEBUG) #if DEBUG_VERBOSE && !defined(NDEBUG)
fprintf(stderr, "rezero to %" PRId64 ": %s\n", z.toInt64(), fprintf(stderr, "rezero to %" PRId64 ": %s\n", z.toInt64(),
@@ -1352,14 +1443,12 @@ void rezero(Node *n, InternalVersionT z) {
} break; } break;
case Type_Node16: { case Type_Node16: {
auto *self = static_cast<Node16 *>(n); auto *self = static_cast<Node16 *>(n);
for (int i = 0; i < 16; ++i) { rezero16(self->childMaxVersion, z);
self->childMaxVersion[i] = std::max(self->childMaxVersion[i], z);
}
} break; } break;
case Type_Node48: { case Type_Node48: {
auto *self = static_cast<Node48 *>(n); auto *self = static_cast<Node48 *>(n);
for (int i = 0; i < 48; ++i) { for (int i = 0; i < 48; i += 16) {
self->childMaxVersion[i] = std::max(self->childMaxVersion[i], z); rezero16(self->childMaxVersion + i, z);
} }
for (auto &m : self->maxOfMax) { for (auto &m : self->maxOfMax) {
m = std::max(m, z); m = std::max(m, z);
@@ -1367,12 +1456,10 @@ void rezero(Node *n, InternalVersionT z) {
} break; } break;
case Type_Node256: { case Type_Node256: {
auto *self = static_cast<Node256 *>(n); auto *self = static_cast<Node256 *>(n);
for (int i = 0; i < 256; ++i) { for (int i = 0; i < 256; i += 16) {
self->childMaxVersion[i] = std::max(self->childMaxVersion[i], z); rezero16(self->childMaxVersion + i, z);
}
for (auto &m : self->maxOfMax) {
m = std::max(m, z);
} }
rezero16(self->maxOfMax, z);
} break; } break;
default: // GCOVR_EXCL_LINE default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE __builtin_unreachable(); // GCOVR_EXCL_LINE
@@ -1676,7 +1763,7 @@ int firstNeqStride(const uint8_t *ap, const uint8_t *bp) {
// This gets covered in local development // This gets covered in local development
// GCOVR_EXCL_START // GCOVR_EXCL_START
#if defined(HAS_AVX) && !defined(__SANITIZE_THREAD__) #if defined(HAS_AVX) && !defined(__SANITIZE_THREAD__)
__attribute__((target("avx512bw"))) int __attribute__((target("avx512f,avx512bw"))) int
longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) { longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) {
int i = 0; int i = 0;
int end = cl & ~63; int end = cl & ~63;
@@ -1761,41 +1848,6 @@ int longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) {
return i; return i;
} }
// Performs a physical search for remaining
struct SearchStepWise {
Node *n;
std::span<const uint8_t> remaining;
SearchStepWise() {}
SearchStepWise(Node *n, std::span<const uint8_t> remaining)
: n(n), remaining(remaining) {
assert(n->partialKeyLen == 0);
}
bool step() {
if (remaining.size() == 0) {
return true;
}
auto *child = getChild(n, remaining[0]);
if (child == nullptr) {
return true;
}
if (child->partialKeyLen > 0) {
int cl = std::min<int>(child->partialKeyLen, remaining.size() - 1);
int i =
longestCommonPrefix(child->partialKey(), remaining.data() + 1, cl);
if (i != child->partialKeyLen) {
return true;
}
}
n = child;
remaining =
remaining.subspan(1 + child->partialKeyLen,
remaining.size() - (1 + child->partialKeyLen));
return false;
}
};
// Logically this is the same as performing firstGeq and then checking against // Logically this is the same as performing firstGeq and then checking against
// point or range version according to cmp, but this version short circuits as // point or range version according to cmp, but this version short circuits as
// soon as it can prove that there's no conflict. // soon as it can prove that there's no conflict.
@@ -1806,12 +1858,7 @@ bool checkPointRead(Node *n, const std::span<const uint8_t> key,
fprintf(stderr, "Check point read: %s\n", printable(key).c_str()); fprintf(stderr, "Check point read: %s\n", printable(key).c_str());
#endif #endif
auto remaining = key; auto remaining = key;
auto *impl = tls->impl;
for (;; ++tls->point_read_iterations_accum) { for (;; ++tls->point_read_iterations_accum) {
if (maxVersion(n, impl) <= readVersion) {
++tls->point_read_short_circuit_accum;
return true;
}
if (remaining.size() == 0) { if (remaining.size() == 0) {
if (n->entryPresent) { if (n->entryPresent) {
return n->entry.pointVersion <= readVersion; return n->entry.pointVersion <= readVersion;
@@ -1820,7 +1867,7 @@ bool checkPointRead(Node *n, const std::span<const uint8_t> key,
goto downLeftSpine; goto downLeftSpine;
} }
auto *child = getChild(n, remaining[0]); auto [child, maxV] = getChildAndMaxVersion(n, remaining[0]);
if (child == nullptr) { if (child == nullptr) {
auto c = getChildGeq(n, remaining[0]); auto c = getChildGeq(n, remaining[0]);
if (c != nullptr) { if (c != nullptr) {
@@ -1862,6 +1909,11 @@ bool checkPointRead(Node *n, const std::span<const uint8_t> key,
goto downLeftSpine; goto downLeftSpine;
} }
} }
if (maxV <= readVersion) {
++tls->point_read_short_circuit_accum;
return true;
}
} }
downLeftSpine: downLeftSpine:
for (; !n->entryPresent; n = getFirstChildExists(n)) { for (; !n->entryPresent; n = getFirstChildExists(n)) {
@@ -1881,17 +1933,11 @@ bool checkPrefixRead(Node *n, const std::span<const uint8_t> key,
auto remaining = key; auto remaining = key;
auto *impl = tls->impl; auto *impl = tls->impl;
for (;; ++tls->prefix_read_iterations_accum) { for (;; ++tls->prefix_read_iterations_accum) {
auto m = maxVersion(n, impl);
if (remaining.size() == 0) { if (remaining.size() == 0) {
return m <= readVersion; return maxVersion(n, impl) <= readVersion;
} }
if (m <= readVersion) { auto [child, maxV] = getChildAndMaxVersion(n, remaining[0]);
++tls->prefix_read_short_circuit_accum;
return true;
}
auto *child = getChild(n, remaining[0]);
if (child == nullptr) { if (child == nullptr) {
auto c = getChildGeq(n, remaining[0]); auto c = getChildGeq(n, remaining[0]);
if (c != nullptr) { if (c != nullptr) {
@@ -1937,6 +1983,11 @@ bool checkPrefixRead(Node *n, const std::span<const uint8_t> key,
goto downLeftSpine; goto downLeftSpine;
} }
} }
if (maxV <= readVersion) {
++tls->prefix_read_short_circuit_accum;
return true;
}
} }
downLeftSpine: downLeftSpine:
for (; !n->entryPresent; n = getFirstChildExists(n)) { for (; !n->entryPresent; n = getFirstChildExists(n)) {
@@ -2053,13 +2104,9 @@ bool scan16(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
} }
// Returns true if v[i] <= readVersion for all i such that begin <= i < end // Returns true if v[i] <= readVersion for all i such that begin <= i < end
//
// always_inline So that we can optimize when begin or end is a constant.
// gcovr exclude annotation necessary because of always_inline?
template <bool kAVX512> template <bool kAVX512>
inline __attribute__((always_inline)) bool bool scan16(const InternalVersionT *vs, int begin, int end,
scan16(const InternalVersionT *vs, int begin, int end, // GCOVR_EXCL_LINE InternalVersionT readVersion) {
InternalVersionT readVersion) { // GCOVR_EXCL_LINE
assert(0 <= begin && begin < 16); assert(0 <= begin && begin < 16);
assert(0 <= end && end <= 16); assert(0 <= end && end <= 16);
assert(begin <= end); assert(begin <= end);
@@ -2114,8 +2161,9 @@ scan16(const InternalVersionT *vs, int begin, int end, // GCOVR_EXCL_LINE
// path of n + [child], where child in (begin, end) is <= readVersion. Does not // path of n + [child], where child in (begin, end) is <= readVersion. Does not
// account for the range version of firstGt(searchpath(n) + [end - 1]) // account for the range version of firstGt(searchpath(n) + [end - 1])
template <bool kAVX512> template <bool kAVX512>
bool checkMaxBetweenExclusive(Node *n, int begin, int end, bool checkMaxBetweenExclusiveImpl(Node *n, int begin, int end,
InternalVersionT readVersion, ReadContext *tls) { InternalVersionT readVersion,
ReadContext *tls) {
++tls->range_read_node_scan_accum; ++tls->range_read_node_scan_accum;
assume(-1 <= begin); assume(-1 <= begin);
assume(begin <= 256); assume(begin <= 256);
@@ -2147,7 +2195,7 @@ bool checkMaxBetweenExclusive(Node *n, int begin, int end,
if (!mask) { if (!mask) {
return true; return true;
} }
auto *child = self->children[__builtin_ctz(mask)]; auto *child = self->children[std::countr_zero(mask)];
const bool firstRangeOk = const bool firstRangeOk =
!child->entryPresent || child->entry.rangeVersion <= readVersion; !child->entryPresent || child->entry.rangeVersion <= readVersion;
uint32_t compared = 0; uint32_t compared = 0;
@@ -2182,7 +2230,7 @@ bool checkMaxBetweenExclusive(Node *n, int begin, int end,
if (!mask) { if (!mask) {
return true; return true;
} }
auto *child = self->children[__builtin_ctzll(mask) >> 2]; auto *child = self->children[std::countr_zero(mask) >> 2];
const bool firstRangeOk = const bool firstRangeOk =
!child->entryPresent || child->entry.rangeVersion <= readVersion; !child->entryPresent || child->entry.rangeVersion <= readVersion;
@@ -2222,7 +2270,7 @@ bool checkMaxBetweenExclusive(Node *n, int begin, int end,
if (!mask) { if (!mask) {
return true; return true;
} }
auto *child = self->children[__builtin_ctz(mask)]; auto *child = self->children[std::countr_zero(mask)];
const bool firstRangeOk = const bool firstRangeOk =
!child->entryPresent || child->entry.rangeVersion <= readVersion; !child->entryPresent || child->entry.rangeVersion <= readVersion;
@@ -2253,7 +2301,7 @@ bool checkMaxBetweenExclusive(Node *n, int begin, int end,
if (!mask) { if (!mask) {
return true; return true;
} }
auto *child = self->children[__builtin_ctz(mask)]; auto *child = self->children[std::countr_zero(mask)];
const bool firstRangeOk = const bool firstRangeOk =
!child->entryPresent || child->entry.rangeVersion <= readVersion; !child->entryPresent || child->entry.rangeVersion <= readVersion;
uint32_t compared = 0; uint32_t compared = 0;
@@ -2363,6 +2411,22 @@ bool checkMaxBetweenExclusive(Node *n, int begin, int end,
} }
} }
#if defined(HAS_AVX) && !defined(__SANITIZE_THREAD__)
// This gets covered in local development
// GCOVR_EXCL_START
__attribute__((target("avx512f"))) bool
checkMaxBetweenExclusive(Node *n, int begin, int end,
InternalVersionT readVersion, ReadContext *tls) {
return checkMaxBetweenExclusiveImpl<true>(n, begin, end, readVersion, tls);
}
// GCOVR_EXCL_STOP
__attribute__((target("default")))
#endif
bool checkMaxBetweenExclusive(Node *n, int begin, int end,
InternalVersionT readVersion, ReadContext *tls) {
return checkMaxBetweenExclusiveImpl<false>(n, begin, end, readVersion, tls);
}
Vector<uint8_t> getSearchPath(Arena &arena, Node *n) { Vector<uint8_t> getSearchPath(Arena &arena, Node *n) {
assert(n != nullptr); assert(n != nullptr);
auto result = vector<uint8_t>(arena); auto result = vector<uint8_t>(arena);
@@ -2385,7 +2449,6 @@ Vector<uint8_t> getSearchPath(Arena &arena, Node *n) {
// //
// Precondition: transitively, no child of n has a search path that's a longer // Precondition: transitively, no child of n has a search path that's a longer
// prefix of key than n // prefix of key than n
template <bool kAVX512>
bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin, bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
int end, InternalVersionT readVersion, int end, InternalVersionT readVersion,
ReadContext *tls) { ReadContext *tls) {
@@ -2395,7 +2458,7 @@ bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
auto remaining = key; auto remaining = key;
auto *impl = tls->impl; auto *impl = tls->impl;
if (remaining.size() == 0) { if (remaining.size() == 0) {
return checkMaxBetweenExclusive<kAVX512>(n, begin, end, readVersion, tls); return checkMaxBetweenExclusive(n, begin, end, readVersion, tls);
} }
auto *child = getChild(n, remaining[0]); auto *child = getChild(n, remaining[0]);
@@ -2454,7 +2517,7 @@ downLeftSpine:
namespace { namespace {
// Return true if the max version among all keys that start with key[:prefixLen] // Return true if the max version among all keys that start with key[:prefixLen]
// that are >= key is <= readVersion // that are >= key is <= readVersion
template <bool kAVX512> struct CheckRangeLeftSide { struct CheckRangeLeftSide {
CheckRangeLeftSide(Node *n, std::span<const uint8_t> key, int prefixLen, CheckRangeLeftSide(Node *n, std::span<const uint8_t> key, int prefixLen,
InternalVersionT readVersion, ReadContext *tls) InternalVersionT readVersion, ReadContext *tls)
: n(n), remaining(key), prefixLen(prefixLen), readVersion(readVersion), : n(n), remaining(key), prefixLen(prefixLen), readVersion(readVersion),
@@ -2476,10 +2539,6 @@ template <bool kAVX512> struct CheckRangeLeftSide {
bool ok; bool ok;
bool step() { bool step() {
if (maxVersion(n, impl) <= readVersion) {
ok = true;
return true;
}
if (remaining.size() == 0) { if (remaining.size() == 0) {
assert(searchPathLen >= prefixLen); assert(searchPathLen >= prefixLen);
ok = maxVersion(n, impl) <= readVersion; ok = maxVersion(n, impl) <= readVersion;
@@ -2487,14 +2546,13 @@ template <bool kAVX512> struct CheckRangeLeftSide {
} }
if (searchPathLen >= prefixLen) { if (searchPathLen >= prefixLen) {
if (!checkMaxBetweenExclusive<kAVX512>(n, remaining[0], 256, readVersion, if (!checkMaxBetweenExclusive(n, remaining[0], 256, readVersion, tls)) {
tls)) {
ok = false; ok = false;
return true; return true;
} }
} }
auto *child = getChild(n, remaining[0]); auto [child, maxV] = getChildAndMaxVersion(n, remaining[0]);
if (child == nullptr) { if (child == nullptr) {
auto c = getChildGeq(n, remaining[0]); auto c = getChildGeq(n, remaining[0]);
if (c != nullptr) { if (c != nullptr) {
@@ -2557,6 +2615,10 @@ template <bool kAVX512> struct CheckRangeLeftSide {
return true; return true;
} }
} }
if (maxV <= readVersion) {
ok = true;
return true;
}
return false; return false;
} }
@@ -2570,7 +2632,7 @@ template <bool kAVX512> struct CheckRangeLeftSide {
// Return true if the max version among all keys that start with key[:prefixLen] // Return true if the max version among all keys that start with key[:prefixLen]
// that are < key is <= readVersion // that are < key is <= readVersion
template <bool kAVX512> struct CheckRangeRightSide { struct CheckRangeRightSide {
CheckRangeRightSide(Node *n, std::span<const uint8_t> key, int prefixLen, CheckRangeRightSide(Node *n, std::span<const uint8_t> key, int prefixLen,
InternalVersionT readVersion, ReadContext *tls) InternalVersionT readVersion, ReadContext *tls)
: n(n), key(key), remaining(key), prefixLen(prefixLen), : n(n), key(key), remaining(key), prefixLen(prefixLen),
@@ -2613,8 +2675,7 @@ template <bool kAVX512> struct CheckRangeRightSide {
return true; return true;
} }
if (!checkMaxBetweenExclusive<kAVX512>(n, -1, remaining[0], readVersion, if (!checkMaxBetweenExclusive(n, -1, remaining[0], readVersion, tls)) {
tls)) {
ok = false; ok = false;
return true; return true;
} }
@@ -2701,10 +2762,9 @@ template <bool kAVX512> struct CheckRangeRightSide {
}; };
} // namespace } // namespace
template <bool kAVX512> bool checkRangeRead(Node *n, std::span<const uint8_t> begin,
bool checkRangeReadImpl(Node *n, std::span<const uint8_t> begin, std::span<const uint8_t> end, InternalVersionT readVersion,
std::span<const uint8_t> end, ReadContext *tls) {
InternalVersionT readVersion, ReadContext *tls) {
int lcp = longestCommonPrefix(begin.data(), end.data(), int lcp = longestCommonPrefix(begin.data(), end.data(),
std::min(begin.size(), end.size())); std::min(begin.size(), end.size()));
if (lcp == int(begin.size()) && end.size() == begin.size() + 1 && if (lcp == int(begin.size()) && end.size() == begin.size() + 1 &&
@@ -2718,50 +2778,63 @@ bool checkRangeReadImpl(Node *n, std::span<const uint8_t> begin,
++tls->range_read_accum; ++tls->range_read_accum;
SearchStepWise search{n, begin.subspan(0, lcp)}; auto remaining = begin.subspan(0, lcp);
Arena arena; Arena arena;
auto *impl = tls->impl; // If the common prefix isn't a prefix of any physical entry in the tree, we
// can go to "downLeftSpine"
for (;; ++tls->range_read_iterations_accum) { for (;; ++tls->range_read_iterations_accum) {
assert(getSearchPath(arena, search.n) <=> assert(getSearchPath(arena, n) <=>
begin.subspan(0, lcp - search.remaining.size()) == begin.subspan(0, lcp - remaining.size()) ==
0); 0);
if (maxVersion(search.n, impl) <= readVersion) { if (remaining.size() == 0) {
break;
}
auto [child, v] = getChildAndMaxVersion(n, remaining[0]);
if (child == nullptr) {
break;
}
if (child->partialKeyLen > 0) {
int cl = std::min<int>(child->partialKeyLen, remaining.size() - 1);
int i =
longestCommonPrefix(child->partialKey(), remaining.data() + 1, cl);
if (i != child->partialKeyLen) {
break;
}
}
if (v <= readVersion) {
++tls->range_read_short_circuit_accum; ++tls->range_read_short_circuit_accum;
return true; return true;
} }
if (search.step()) { n = child;
break; remaining =
} remaining.subspan(1 + child->partialKeyLen,
remaining.size() - (1 + child->partialKeyLen));
} }
assert(getSearchPath(arena, search.n) <=> assert(getSearchPath(arena, n) <=> begin.subspan(0, lcp - remaining.size()) ==
begin.subspan(0, lcp - search.remaining.size()) ==
0); 0);
const int consumed = lcp - search.remaining.size(); const int consumed = lcp - remaining.size();
assume(consumed >= 0); assume(consumed >= 0);
begin = begin.subspan(consumed, int(begin.size()) - consumed); begin = begin.subspan(consumed, int(begin.size()) - consumed);
end = end.subspan(consumed, int(end.size()) - consumed); end = end.subspan(consumed, int(end.size()) - consumed);
n = search.n;
lcp -= consumed; lcp -= consumed;
if (lcp == int(begin.size())) { if (lcp == int(begin.size())) {
CheckRangeRightSide<kAVX512> checkRangeRightSide{n, end, lcp, readVersion, CheckRangeRightSide checkRangeRightSide{n, end, lcp, readVersion, tls};
tls};
while (!checkRangeRightSide.step()) while (!checkRangeRightSide.step())
; ;
return checkRangeRightSide.ok; return checkRangeRightSide.ok;
} }
if (!checkRangeStartsWith<kAVX512>(n, begin.subspan(0, lcp), begin[lcp], if (!checkRangeStartsWith(n, begin.subspan(0, lcp), begin[lcp], end[lcp],
end[lcp], readVersion, tls)) { readVersion, tls)) {
return false; return false;
} }
CheckRangeLeftSide<kAVX512> checkRangeLeftSide{n, begin, lcp + 1, readVersion, CheckRangeLeftSide checkRangeLeftSide{n, begin, lcp + 1, readVersion, tls};
tls}; CheckRangeRightSide checkRangeRightSide{n, end, lcp + 1, readVersion, tls};
CheckRangeRightSide<kAVX512> checkRangeRightSide{n, end, lcp + 1, readVersion,
tls};
for (;;) { for (;;) {
bool leftDone = checkRangeLeftSide.step(); bool leftDone = checkRangeLeftSide.step();
@@ -2796,138 +2869,101 @@ bool checkRangeReadImpl(Node *n, std::span<const uint8_t> begin,
template __attribute__((target("avx512f"))) bool template __attribute__((target("avx512f"))) bool
scan16<true>(const InternalVersionT *vs, const uint8_t *is, int begin, int end, scan16<true>(const InternalVersionT *vs, const uint8_t *is, int begin, int end,
InternalVersionT readVersion); InternalVersionT readVersion);
template __attribute__((always_inline, target("avx512f"))) bool template __attribute__((target("avx512f"))) bool
scan16<true>(const InternalVersionT *vs, int begin, int end, scan16<true>(const InternalVersionT *vs, int begin, int end,
InternalVersionT readVersion); InternalVersionT readVersion);
template __attribute__((target("avx512f"))) bool template __attribute__((target("avx512f"))) bool
checkMaxBetweenExclusive<true>(Node *n, int begin, int end, checkMaxBetweenExclusiveImpl<true>(Node *n, int begin, int end,
InternalVersionT readVersion, ReadContext *); InternalVersionT readVersion, ReadContext *);
template __attribute__((target("avx512f"))) bool
checkRangeStartsWith<true>(Node *n, std::span<const uint8_t> key, int begin,
int end, InternalVersionT readVersion,
ReadContext *);
template __attribute__((target("avx512f"))) bool
CheckRangeLeftSide<true>::step();
template __attribute__((target("avx512f"))) bool
CheckRangeRightSide<true>::step();
template __attribute__((target("avx512f"))) bool
checkRangeReadImpl<true>(Node *n, std::span<const uint8_t> begin,
std::span<const uint8_t> end,
InternalVersionT readVersion, ReadContext *);
#endif #endif
#if defined(__SANITIZE_THREAD__) || !defined(__x86_64__) // Consume the partial key of `self` (which must exist), and update `self` and
bool checkRangeRead(Node *n, std::span<const uint8_t> begin, // `key` such that `self` is along the search path of `key`
std::span<const uint8_t> end, InternalVersionT readVersion, void consumePartialKey(Node *&self, std::span<const uint8_t> &key,
ReadContext *tls) { InternalVersionT writeVersion, WriteContext *tls) {
return checkRangeReadImpl<false>(n, begin, end, readVersion, tls); assert(self->partialKeyLen > 0);
// Handle an existing partial key
int commonLen = std::min<int>(self->partialKeyLen, key.size());
int partialKeyIndex =
longestCommonPrefix(self->partialKey(), key.data(), commonLen);
if (partialKeyIndex < self->partialKeyLen) {
auto *old = self;
// Since root cannot have a partial key
assert(old->parent != nullptr);
InternalVersionT oldMaxVersion = exchangeMaxVersion(old, writeVersion);
// *self will have one child (old)
auto *newSelf = tls->allocate<Node3>(partialKeyIndex);
newSelf->parent = old->parent;
newSelf->parentsIndex = old->parentsIndex;
newSelf->partialKeyLen = partialKeyIndex;
newSelf->entryPresent = false;
newSelf->numChildren = 1;
memcpy(newSelf->partialKey(), old->partialKey(), newSelf->partialKeyLen);
uint8_t oldDistinguishingByte = old->partialKey()[partialKeyIndex];
old->parent = newSelf;
old->parentsIndex = oldDistinguishingByte;
newSelf->index[0] = oldDistinguishingByte;
newSelf->children[0] = old;
newSelf->childMaxVersion[0] = oldMaxVersion;
self = newSelf;
memmove(old->partialKey(), old->partialKey() + partialKeyIndex + 1,
old->partialKeyLen - (partialKeyIndex + 1));
old->partialKeyLen -= partialKeyIndex + 1;
// We would consider decreasing capacity here, but we can't invalidate
// old since it's not on the search path. setOldestVersion will clean it
// up.
}
key = key.subspan(partialKeyIndex, key.size() - partialKeyIndex);
} }
#else
// Only one of these is ever exercised. I'm not worried about somehow not
// calling one of these though.
// GCOVR_EXCL_START
__attribute__((target("default"))) bool
checkRangeRead(Node *n, std::span<const uint8_t> begin,
std::span<const uint8_t> end, InternalVersionT readVersion,
ReadContext *tls) {
return checkRangeReadImpl<false>(n, begin, end, readVersion, tls);
}
__attribute__((target("avx512f"))) bool
checkRangeRead(Node *n, std::span<const uint8_t> begin,
std::span<const uint8_t> end, InternalVersionT readVersion,
ReadContext *tls) {
return checkRangeReadImpl<true>(n, begin, end, readVersion, tls);
}
// GCOVR_EXCL_STOP
#endif
// Returns a pointer to the newly inserted node. Caller must set // Returns a pointer to the newly inserted node. Caller must set
// `entryPresent`, `entry` fields and `maxVersion` on the result. The search // `entryPresent`, and `entry` fields. All nodes along the search path of the
// path of the result's parent will have `maxVersion` at least `writeVersion` as // result will have `maxVersion` set to `writeVersion` as a postcondition. Nodes
// a postcondition. Nodes along the search path to `key` may be invalidated. // along the search path may be invalidated.
template <bool kBegin> [[nodiscard]]
[[nodiscard]] Node *insert(Node **self, std::span<const uint8_t> key, Node *insert(Node **self, std::span<const uint8_t> key,
InternalVersionT writeVersion, WriteContext *tls, InternalVersionT writeVersion, WriteContext *tls,
ConflictSet::Impl *impl) { ConflictSet::Impl *impl) {
if ((*self)->partialKeyLen > 0) {
consumePartialKey(*self, key, writeVersion, tls);
}
assert(maxVersion(*self, impl) <= writeVersion);
setMaxVersion(*self, impl, writeVersion);
for (;; ++tls->accum.insert_iterations) { for (;; ++tls->accum.insert_iterations) {
if ((*self)->partialKeyLen > 0) {
// Handle an existing partial key
int commonLen = std::min<int>((*self)->partialKeyLen, key.size());
int partialKeyIndex =
longestCommonPrefix((*self)->partialKey(), key.data(), commonLen);
if (partialKeyIndex < (*self)->partialKeyLen) {
auto *old = *self;
InternalVersionT oldMaxVersion = maxVersion(old, impl);
// *self will have one child
*self = tls->allocate<Node3>(partialKeyIndex);
memcpy((char *)*self + kNodeCopyBegin, (char *)old + kNodeCopyBegin,
kNodeCopySize);
(*self)->partialKeyLen = partialKeyIndex;
// Not necessary to call removeKey here, since this node is "synthetic"
(*self)->entryPresent = false;
(*self)->numChildren = 0;
memcpy((*self)->partialKey(), old->partialKey(),
(*self)->partialKeyLen);
getOrCreateChild(*self, old->partialKey()[partialKeyIndex], tls) = old;
old->parent = *self;
old->parentsIndex = old->partialKey()[partialKeyIndex];
setMaxVersion(old, impl, oldMaxVersion);
memmove(old->partialKey(), old->partialKey() + partialKeyIndex + 1,
old->partialKeyLen - (partialKeyIndex + 1));
old->partialKeyLen -= partialKeyIndex + 1;
// We would consider decreasing capacity here, but we can't invalidate
// old since it's not on the search path. setOldestVersion will clean it
// up.
}
key = key.subspan(partialKeyIndex, key.size() - partialKeyIndex);
} else {
// Consider adding a partial key
if ((*self)->numChildren == 0 && !(*self)->entryPresent) {
assert((*self)->getCapacity() >= int(key.size()));
(*self)->partialKeyLen = key.size();
memcpy((*self)->partialKey(), key.data(), (*self)->partialKeyLen);
key = key.subspan((*self)->partialKeyLen,
key.size() - (*self)->partialKeyLen);
}
}
if constexpr (kBegin) {
assert(maxVersion(*self, impl) <= writeVersion);
setMaxVersion(*self, impl, writeVersion);
}
if (key.size() == 0) { if (key.size() == 0) {
return *self; return *self;
} }
if constexpr (!kBegin) { auto &child = getOrCreateChild(*self, key.front(), writeVersion, tls);
assert(maxVersion(*self, impl) <= writeVersion);
setMaxVersion(*self, impl, writeVersion);
}
auto &child = getOrCreateChild(*self, key.front(), tls);
if (!child) { if (!child) {
child = tls->allocate<Node0>(key.size() - 1); child = tls->allocate<Node0>(key.size() - 1);
child->numChildren = 0; child->numChildren = 0;
child->entryPresent = false; child->entryPresent = false;
child->partialKeyLen = 0; child->partialKeyLen = key.size() - 1;
child->parent = *self; child->parent = *self;
child->parentsIndex = key.front(); child->parentsIndex = key.front();
setMaxVersion(child, impl, kBegin ? writeVersion : tls->zero); setMaxVersion(child, impl, writeVersion);
memcpy(child->partialKey(), key.data() + 1, child->partialKeyLen);
return child;
} }
self = &child; self = &child;
key = key.subspan(1, key.size() - 1); key = key.subspan(1, key.size() - 1);
if ((*self)->partialKeyLen > 0) {
consumePartialKey(*self, key, writeVersion, tls);
assert(maxVersion(*self, impl) <= writeVersion);
setMaxVersion(*self, impl, writeVersion);
}
} }
} }
@@ -2960,7 +2996,7 @@ void addPointWrite(Node *&root, std::span<const uint8_t> key,
InternalVersionT writeVersion, WriteContext *tls, InternalVersionT writeVersion, WriteContext *tls,
ConflictSet::Impl *impl) { ConflictSet::Impl *impl) {
++tls->accum.point_writes; ++tls->accum.point_writes;
auto *n = insert<true>(&root, key, writeVersion, tls, impl); auto *n = insert(&root, key, writeVersion, tls, impl);
if (!n->entryPresent) { if (!n->entryPresent) {
++tls->accum.entries_inserted; ++tls->accum.entries_inserted;
auto *p = nextLogical(n); auto *p = nextLogical(n);
@@ -2969,7 +3005,6 @@ void addPointWrite(Node *&root, std::span<const uint8_t> key,
n->entryPresent = true; n->entryPresent = true;
n->entry.pointVersion = writeVersion; n->entry.pointVersion = writeVersion;
setMaxVersion(n, impl, writeVersion);
n->entry.rangeVersion = n->entry.rangeVersion =
p == nullptr ? tls->zero : std::max(p->entry.rangeVersion, tls->zero); p == nullptr ? tls->zero : std::max(p->entry.rangeVersion, tls->zero);
} else { } else {
@@ -2978,6 +3013,44 @@ void addPointWrite(Node *&root, std::span<const uint8_t> key,
} }
} }
// Precondition: `node->entryPresent`
void fixupMaxVersion(Node *node, ConflictSet::Impl *impl, WriteContext *tls) {
InternalVersionT max;
assert(node->entryPresent);
max = std::max(node->entry.pointVersion, tls->zero);
switch (node->getType()) {
case Type_Node0:
break;
case Type_Node3: {
auto *self3 = static_cast<Node3 *>(node);
for (int i = 0; i < self3->numChildren; ++i) {
max = std::max(self3->childMaxVersion[i], max);
}
} break;
case Type_Node16: {
auto *self16 = static_cast<Node16 *>(node);
for (int i = 0; i < self16->numChildren; ++i) {
max = std::max(self16->childMaxVersion[i], max);
}
} break;
case Type_Node48: {
auto *self48 = static_cast<Node48 *>(node);
for (auto v : self48->maxOfMax) {
max = std::max(v, max);
}
} break;
case Type_Node256: {
auto *self256 = static_cast<Node256 *>(node);
for (auto v : self256->maxOfMax) {
max = std::max(v, max);
}
} break;
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
setMaxVersion(node, impl, max);
}
void addWriteRange(Node *&root, std::span<const uint8_t> begin, void addWriteRange(Node *&root, std::span<const uint8_t> begin,
std::span<const uint8_t> end, InternalVersionT writeVersion, std::span<const uint8_t> end, InternalVersionT writeVersion,
WriteContext *tls, ConflictSet::Impl *impl) { WriteContext *tls, ConflictSet::Impl *impl) {
@@ -3024,7 +3097,7 @@ void addWriteRange(Node *&root, std::span<const uint8_t> begin,
begin = begin.subspan(consumed, begin.size() - consumed); begin = begin.subspan(consumed, begin.size() - consumed);
end = end.subspan(consumed, end.size() - consumed); end = end.subspan(consumed, end.size() - consumed);
auto *beginNode = insert<true>(useAsRoot, begin, writeVersion, tls, impl); auto *beginNode = insert(useAsRoot, begin, writeVersion, tls, impl);
const bool insertedBegin = !beginNode->entryPresent; const bool insertedBegin = !beginNode->entryPresent;
@@ -3037,14 +3110,11 @@ void addWriteRange(Node *&root, std::span<const uint8_t> begin,
beginNode->entry.rangeVersion = beginNode->entry.rangeVersion =
p == nullptr ? tls->zero : std::max(p->entry.rangeVersion, tls->zero); p == nullptr ? tls->zero : std::max(p->entry.rangeVersion, tls->zero);
beginNode->entry.pointVersion = writeVersion; beginNode->entry.pointVersion = writeVersion;
assert(maxVersion(beginNode, impl) <= writeVersion);
setMaxVersion(beginNode, impl, writeVersion);
} }
setMaxVersion(beginNode, impl, writeVersion);
assert(writeVersion >= beginNode->entry.pointVersion); assert(writeVersion >= beginNode->entry.pointVersion);
beginNode->entry.pointVersion = writeVersion; beginNode->entry.pointVersion = writeVersion;
auto *endNode = insert<false>(useAsRoot, end, writeVersion, tls, impl); auto *endNode = insert(useAsRoot, end, writeVersion, tls, impl);
const bool insertedEnd = !endNode->entryPresent; const bool insertedEnd = !endNode->entryPresent;
@@ -3056,22 +3126,22 @@ void addWriteRange(Node *&root, std::span<const uint8_t> begin,
auto *p = nextLogical(endNode); auto *p = nextLogical(endNode);
endNode->entry.pointVersion = endNode->entry.pointVersion =
p == nullptr ? tls->zero : std::max(p->entry.rangeVersion, tls->zero); p == nullptr ? tls->zero : std::max(p->entry.rangeVersion, tls->zero);
auto m = maxVersion(endNode, impl);
setMaxVersion(endNode, impl,
std::max<InternalVersionT>(m, endNode->entry.pointVersion));
} }
endNode->entry.rangeVersion = writeVersion; endNode->entry.rangeVersion = writeVersion;
if (beginIsPrefix && insertedEnd) { if (beginIsPrefix && insertedEnd) {
// beginNode may have been invalidated when inserting end. TODO can we do // beginNode may have been invalidated when inserting end. TODO can we do
// better? // better?
beginNode = insert<true>(useAsRoot, begin, writeVersion, tls, impl); beginNode = insert(useAsRoot, begin, writeVersion, tls, impl);
assert(beginNode->entryPresent); assert(beginNode->entryPresent);
} }
for (beginNode = nextLogical(beginNode); beginNode != endNode; for (beginNode = nextLogical(beginNode); beginNode != endNode;
beginNode = erase(beginNode, tls, impl, /*logical*/ true, endNode)) { beginNode = erase(beginNode, tls, impl, /*logical*/ true, endNode)) {
} }
// Inserting end trashed endNode's maxVersion. Fix that
fixupMaxVersion(endNode, impl, tls);
} }
Node *firstGeqPhysical(Node *n, const std::span<const uint8_t> key) { Node *firstGeqPhysical(Node *n, const std::span<const uint8_t> key) {
@@ -3488,6 +3558,39 @@ InternalVersionT maxVersion(Node *n, ConflictSet::Impl *impl) {
} }
} }
// Precondition `n` is not the root
InternalVersionT exchangeMaxVersion(Node *n, InternalVersionT newMax) {
int index = n->parentsIndex;
n = n->parent;
assert(n != nullptr);
switch (n->getType()) {
case Type_Node0: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
case Type_Node3: {
auto *n3 = static_cast<Node3 *>(n);
int i = getNodeIndex(n3, index);
return std::exchange(n3->childMaxVersion[i], newMax);
}
case Type_Node16: {
auto *n16 = static_cast<Node16 *>(n);
int i = getNodeIndex(n16, index);
return std::exchange(n16->childMaxVersion[i], newMax);
}
case Type_Node48: {
auto *n48 = static_cast<Node48 *>(n);
assert(n48->bitSet.test(index));
return std::exchange(n48->childMaxVersion[n48->index[index]], newMax);
}
case Type_Node256: {
auto *n256 = static_cast<Node256 *>(n);
assert(n256->bitSet.test(index));
return std::exchange(n256->childMaxVersion[index], newMax);
}
default: // GCOVR_EXCL_LINE
__builtin_unreachable(); // GCOVR_EXCL_LINE
}
}
void setMaxVersion(Node *n, ConflictSet::Impl *impl, InternalVersionT newMax) { void setMaxVersion(Node *n, ConflictSet::Impl *impl, InternalVersionT newMax) {
int index = n->parentsIndex; int index = n->parentsIndex;
n = n->parent; n = n->parent;
@@ -3998,6 +4101,10 @@ checkMaxVersion(Node *root, Node *node, InternalVersionT oldestVersion,
ConflictSet::Impl *impl) { ConflictSet::Impl *impl) {
bool success = true; bool success = true;
if (node->partialKeyLen > 0) {
fprintf(stderr, "Root cannot have a partial key");
success = false;
}
checkParentPointers(node, success); checkParentPointers(node, success);
checkMaxVersion(node, node, oldestVersion, success, impl); checkMaxVersion(node, node, oldestVersion, success, impl);
checkEntriesExist(node, success); checkEntriesExist(node, success);

View File

@@ -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
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:---------- |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
| 17.03 | 58,732,967.93 | 0.6% | 276.28 | 87.96 | 3.141 | 52.15 | 0.4% | 0.01 | `point reads` | 12.42 | 80,500,398.66 | 0.8% | 180.38 | 61.57 | 2.930 | 41.51 | 0.4% | 0.01 | `point reads`
| 19.52 | 51,239,158.04 | 0.3% | 367.16 | 101.50 | 3.617 | 61.92 | 0.3% | 0.01 | `prefix reads` | 15.17 | 65,917,580.99 | 0.2% | 279.47 | 74.95 | 3.729 | 55.54 | 0.3% | 0.01 | `prefix reads`
| 47.74 | 20,947,676.63 | 0.5% | 998.16 | 247.43 | 4.034 | 161.64 | 0.2% | 0.01 | `range reads` | 38.16 | 26,202,393.91 | 0.1% | 803.07 | 189.13 | 4.246 | 141.68 | 0.2% | 0.01 | `range reads`
| 23.14 | 43,207,824.89 | 0.4% | 408.18 | 121.64 | 3.356 | 70.20 | 0.3% | 0.01 | `point writes` | 20.20 | 49,504,615.44 | 0.4% | 363.00 | 100.35 | 3.617 | 49.81 | 0.3% | 0.01 | `point writes`
| 38.02 | 26,302,115.66 | 0.1% | 709.72 | 199.70 | 3.554 | 134.26 | 0.3% | 0.01 | `prefix writes` | 41.99 | 23,816,559.99 | 0.3% | 799.27 | 209.63 | 3.813 | 154.32 | 0.1% | 0.01 | `prefix writes`
| 44.28 | 22,583,559.17 | 0.9% | 825.19 | 233.10 | 3.540 | 141.48 | 0.2% | 0.01 | `range writes` | 46.28 | 21,607,605.88 | 1.5% | 953.79 | 231.47 | 4.121 | 168.34 | 0.0% | 0.01 | `range writes`
| 85.50 | 11,695,990.63 | 0.5% | 1,488.16 | 455.68 | 3.266 | 289.22 | 0.1% | 0.01 | `monotonic increasing point writes` | 80.99 | 12,347,449.98 | 0.9% | 1,501.97 | 406.50 | 3.695 | 281.89 | 0.1% | 0.01 | `monotonic increasing point writes`
| 338,388.50 | 2,955.18 | 3.3% | 4,097,087.00 | 1,809,996.00 | 2.264 | 759,645.00 | 0.0% | 0.01 | `worst case for radix tree` | 318,010.00 | 3,144.56 | 1.0% | 3,994,511.50 | 1,657,831.50 | 2.409 | 805,969.50 | 0.0% | 0.01 | `worst case for radix tree`
| 84.84 | 11,787,313.59 | 1.4% | 1,716.02 | 440.50 | 3.896 | 271.00 | 0.0% | 0.01 | `create and destroy` | 75.85 | 13,183,612.56 | 0.5% | 1,590.01 | 385.64 | 4.123 | 258.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: 1.05813 seconds, 1540.97 MB/s, Add: 1.32071 seconds, 424.508 MB/s, Gc ratio: 42.2067% Check: 0.963721 seconds, 1691.93 MB/s, Add: 1.3288 seconds, 421.924 MB/s, Gc ratio: 42.8819%
``` ```
## hash table ## hash table

233
ServerBench.cpp Normal file
View File

@@ -0,0 +1,233 @@
#include <atomic>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <string_view>
#include <sys/resource.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <thread>
#include <unistd.h>
#include "ConflictSet.h"
#include "third_party/nadeau.h"
std::atomic<int64_t> transactions;
constexpr int kBaseSearchDepth = 32;
constexpr int kWindowSize = 10000000;
std::basic_string<uint8_t> numToKey(int64_t num) {
std::basic_string<uint8_t> result;
result.resize(kBaseSearchDepth + sizeof(int64_t));
memset(result.data(), 0, kBaseSearchDepth);
int64_t be = __builtin_bswap64(num);
memcpy(result.data() + kBaseSearchDepth, &be, sizeof(int64_t));
return result;
}
void workload(weaselab::ConflictSet *cs) {
int64_t version = kWindowSize;
cs->addWrites(nullptr, 0, version);
for (;; transactions.fetch_add(1, std::memory_order_relaxed)) {
// Reads
{
auto beginK = numToKey(version - kWindowSize);
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
{
weaselab::ConflictSet::WriteRange w;
auto k = numToKey(version);
w.begin.p = k.data();
w.end.len = 0;
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);
}
}
// GC
cs->setOldestVersion(version - kWindowSize);
++version;
}
}
// Adapted from getaddrinfo man page
int getListenFd(const char *node, const char *service) {
struct addrinfo hints;
struct addrinfo *result, *rp;
int sfd, s;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
hints.ai_socktype = SOCK_STREAM; /* stream socket */
hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */
hints.ai_protocol = 0; /* Any protocol */
hints.ai_canonname = nullptr;
hints.ai_addr = nullptr;
hints.ai_next = nullptr;
s = getaddrinfo(node, service, &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
abort();
}
/* getaddrinfo() returns a list of address structures.
Try each address until we successfully bind(2).
If socket(2) (or bind(2)) fails, we (close the socket
and) try the next address. */
for (rp = result; rp != nullptr; rp = rp->ai_next) {
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
if (sfd == -1) {
continue;
}
int val = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) {
break; /* Success */
}
close(sfd);
}
freeaddrinfo(result); /* No longer needed */
if (rp == nullptr) { /* No address succeeded */
fprintf(stderr, "Could not bind\n");
abort();
}
int rv = listen(sfd, SOMAXCONN);
if (rv) {
perror("listen()");
abort();
}
return sfd;
}
// HTTP response
//
std::string_view part1 =
"HTTP/1.1 200 OK \r\nContent-type: text/plain; version=0.0.4; "
"charset=utf-8; escaping=values\r\nContent-Length: ";
// Decimal content length
std::string_view part2 = "\r\n\r\n";
// Body
double toSeconds(timeval t) {
return double(t.tv_sec) + double(t.tv_usec) * 1e-6;
}
int main(int argc, char **argv) {
if (argc != 3) {
goto fail;
}
{
int listenFd = getListenFd(argv[1], argv[2]);
weaselab::ConflictSet cs{0};
weaselab::ConflictSet::MetricsV1 *metrics;
int metricsCount;
cs.getMetricsV1(&metrics, &metricsCount);
auto w = std::thread{workload, &cs};
for (;;) {
struct sockaddr_storage peer_addr = {};
socklen_t peer_addr_len = sizeof(peer_addr);
const int connfd =
accept(listenFd, (struct sockaddr *)&peer_addr, &peer_addr_len);
std::string body;
rusage r;
getrusage(RUSAGE_SELF, &r);
body += "# HELP process_cpu_seconds_total Total user and system CPU time "
"spent in seconds.\n# TYPE process_cpu_seconds_total counter\n"
"process_cpu_seconds_total ";
body += std::to_string(toSeconds(r.ru_utime) + toSeconds(r.ru_stime));
body += "\n";
body += "# HELP process_resident_memory_bytes Resident memory size in "
"bytes.\n# TYPE process_resident_memory_bytes gauge\n"
"process_resident_memory_bytes ";
body += std::to_string(getCurrentRSS());
body += "\n";
body += "# HELP transactions_total Total number of transactions\n"
"# TYPE transactions_total counter\n"
"transactions_total ";
body += std::to_string(transactions.load(std::memory_order_relaxed));
body += "\n";
for (int i = 0; i < metricsCount; ++i) {
body += "# HELP ";
body += metrics[i].name;
body += " ";
body += metrics[i].help;
body += "\n";
body += "# TYPE ";
body += metrics[i].name;
body += " ";
body += metrics[i].type == metrics[i].Counter ? "counter" : "gauge";
body += "\n";
body += metrics[i].name;
body += " ";
body += std::to_string(metrics[i].getValue());
body += "\n";
}
auto len = std::to_string(body.size());
iovec iov[] = {
{(void *)part1.data(), part1.size()},
{(void *)len.data(), len.size()},
{(void *)part2.data(), part2.size()},
{(void *)body.data(), body.size()},
};
int written;
do {
written = writev(connfd, iov, sizeof(iov) / sizeof(iov[0]));
} while (written < 0 && errno == EINTR);
close(connfd);
}
}
fail:
fprintf(stderr, "Expected ./%s <host> <port>\n", argv[0]);
return 1;
}

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.

View File

@@ -1,5 +0,0 @@
<EFBFBD><EFBFBD>
2

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.

View File

@@ -1 +0,0 @@
<EFBFBD>

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