Compare commits
3 Commits
add0af11ad
...
89b3354a80
Author | SHA1 | Date | |
---|---|---|---|
89b3354a80 | |||
488c723726 | |||
76d0785b33 |
73
Bench.cpp
73
Bench.cpp
@@ -258,4 +258,75 @@ void benchConflictSet() {
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) { benchConflictSet(); }
|
||||
void benchWorstCaseForRadixRangeRead() {
|
||||
ankerl::nanobench::Bench bench;
|
||||
ConflictSet cs{0};
|
||||
|
||||
int64_t version = 0;
|
||||
|
||||
constexpr int kKeyLength = 50;
|
||||
|
||||
for (int i = 0; i < kKeyLength; ++i) {
|
||||
for (int j = 0; j < 256; ++j) {
|
||||
auto b = std::vector<uint8_t>(i, 0);
|
||||
b.push_back(j);
|
||||
auto e = std::vector<uint8_t>(i, 255);
|
||||
e.push_back(j);
|
||||
weaselab::ConflictSet::WriteRange w[] = {{
|
||||
{b.data(), int(b.size())},
|
||||
{nullptr, 0},
|
||||
},
|
||||
{
|
||||
{e.data(), int(e.size())},
|
||||
{nullptr, 0},
|
||||
}};
|
||||
cs.addWrites(w, sizeof(w) / sizeof(w[0]), version);
|
||||
}
|
||||
}
|
||||
|
||||
// Defeat short-circuiting on the left
|
||||
{
|
||||
auto k = std::vector<uint8_t>(kKeyLength, 0);
|
||||
weaselab::ConflictSet::WriteRange w[] = {
|
||||
{
|
||||
{k.data(), int(k.size())},
|
||||
{nullptr, 0},
|
||||
},
|
||||
};
|
||||
cs.addWrites(w, sizeof(w) / sizeof(w[0]), 2);
|
||||
}
|
||||
|
||||
// Defeat short-circuiting on the right
|
||||
{
|
||||
auto k = std::vector<uint8_t>(kKeyLength, 255);
|
||||
weaselab::ConflictSet::WriteRange w[] = {
|
||||
{
|
||||
{k.data(), int(k.size())},
|
||||
{nullptr, 0},
|
||||
},
|
||||
};
|
||||
cs.addWrites(w, sizeof(w) / sizeof(w[0]), 2);
|
||||
}
|
||||
|
||||
auto begin = std::vector<uint8_t>(kKeyLength - 1, 0);
|
||||
begin.push_back(1);
|
||||
auto end = std::vector<uint8_t>(kKeyLength - 1, 255);
|
||||
end.push_back(254);
|
||||
|
||||
weaselab::ConflictSet::Result result;
|
||||
weaselab::ConflictSet::ReadRange r{
|
||||
{begin.data(), int(begin.size())}, {end.data(), int(end.size())}, 1};
|
||||
|
||||
bench.run("worst case for radix tree", [&]() {
|
||||
result = weaselab::ConflictSet::TooOld;
|
||||
cs.check(&r, &result, 1);
|
||||
if (result != weaselab::ConflictSet::Commit) {
|
||||
abort();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
benchConflictSet();
|
||||
benchWorstCaseForRadixRangeRead();
|
||||
}
|
||||
|
@@ -1621,28 +1621,31 @@ downLeftSpine:
|
||||
}
|
||||
}
|
||||
|
||||
// Return the max version among all keys starting with the search path of n +
|
||||
// [child], where child in (begin, end). Does not account for the range version
|
||||
// of firstGt(searchpath(n) + [end - 1])
|
||||
int64_t maxBetweenExclusive(Node *n, int begin, int end) {
|
||||
// Return whether or not the max version among all keys starting with the search
|
||||
// path of n + [child], where child in (begin, end) is <= readVersion. Does not
|
||||
// account for the range version of firstGt(searchpath(n) + [end - 1])
|
||||
bool checkMaxBetweenExclusive(Node *n, int begin, int end,
|
||||
int64_t readVersion) {
|
||||
assume(-1 <= begin);
|
||||
assume(begin <= 256);
|
||||
assume(-1 <= end);
|
||||
assume(end <= 256);
|
||||
assume(begin < end);
|
||||
int64_t result = std::numeric_limits<int64_t>::lowest();
|
||||
{
|
||||
int c = getChildGeq(n, begin + 1);
|
||||
if (c >= 0 && c < end) {
|
||||
auto *child = getChildExists(n, c);
|
||||
if (child->entryPresent) {
|
||||
result = std::max(result, child->entry.rangeVersion);
|
||||
if (!(child->entry.rangeVersion <= readVersion)) {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
begin = c;
|
||||
} else {
|
||||
return result;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
switch (n->getType()) {
|
||||
case Type_Node0: // GCOVR_EXCL_LINE
|
||||
// We would have returned above, after not finding a child
|
||||
@@ -1651,7 +1654,9 @@ int64_t maxBetweenExclusive(Node *n, int begin, int end) {
|
||||
auto *self = static_cast<Node3 *>(n);
|
||||
for (int i = 0; i < self->numChildren && self->index[i] < end; ++i) {
|
||||
if (begin <= self->index[i]) {
|
||||
result = std::max(result, self->children[i].childMaxVersion);
|
||||
if (self->children[i].childMaxVersion > readVersion) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
@@ -1659,37 +1664,45 @@ int64_t maxBetweenExclusive(Node *n, int begin, int end) {
|
||||
auto *self = static_cast<Node16 *>(n);
|
||||
for (int i = 0; i < self->numChildren && self->index[i] < end; ++i) {
|
||||
if (begin <= self->index[i]) {
|
||||
result = std::max(result, self->children[i].childMaxVersion);
|
||||
if (self->children[i].childMaxVersion > readVersion) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case Type_Node48: {
|
||||
bool conflict[256] = {};
|
||||
auto *self = static_cast<Node48 *>(n);
|
||||
self->bitSet.forEachInRange(
|
||||
[&](int i) {
|
||||
result =
|
||||
std::max(result, self->children[self->index[i]].childMaxVersion);
|
||||
conflict[i] =
|
||||
self->children[self->index[i]].childMaxVersion > readVersion;
|
||||
},
|
||||
begin, end);
|
||||
break;
|
||||
bool result = true;
|
||||
for (auto c : conflict) {
|
||||
result &= !c;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case Type_Node256: {
|
||||
bool conflict[256] = {};
|
||||
auto *self = static_cast<Node256 *>(n);
|
||||
self->bitSet.forEachInRange(
|
||||
[&](int i) {
|
||||
result = std::max(result, self->children[i].childMaxVersion);
|
||||
conflict[i] = self->children[i].childMaxVersion > readVersion;
|
||||
},
|
||||
begin, end);
|
||||
break;
|
||||
bool result = true;
|
||||
for (auto c : conflict) {
|
||||
result &= !c;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
default: // GCOVR_EXCL_LINE
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
}
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
fprintf(stderr, "At `%s', max version in (%02x, %02x) is %" PRId64 "\n",
|
||||
getSearchPathPrintable(n).c_str(), begin, end, result);
|
||||
#endif
|
||||
return result;
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector<uint8_t> getSearchPath(Arena &arena, Node *n) {
|
||||
@@ -1722,7 +1735,7 @@ bool checkRangeStartsWith(Node *n, std::span<const uint8_t> key, int begin,
|
||||
#endif
|
||||
auto remaining = key;
|
||||
if (remaining.size() == 0) {
|
||||
return maxBetweenExclusive(n, begin, end) <= readVersion;
|
||||
return checkMaxBetweenExclusive(n, begin, end, readVersion);
|
||||
}
|
||||
|
||||
auto *child = getChild(n, remaining[0]);
|
||||
@@ -1821,7 +1834,7 @@ struct CheckRangeLeftSide {
|
||||
}
|
||||
|
||||
if (searchPathLen >= prefixLen) {
|
||||
if (maxBetweenExclusive(n, remaining[0], 256) > readVersion) {
|
||||
if (!checkMaxBetweenExclusive(n, remaining[0], 256, readVersion)) {
|
||||
ok = false;
|
||||
return true;
|
||||
}
|
||||
@@ -1963,7 +1976,7 @@ struct CheckRangeRightSide {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (maxBetweenExclusive(n, -1, remaining[0]) > readVersion) {
|
||||
if (!checkMaxBetweenExclusive(n, -1, remaining[0], readVersion)) {
|
||||
ok = false;
|
||||
return true;
|
||||
}
|
||||
|
38
README.md
38
README.md
@@ -58,27 +58,29 @@ Performance counters:
|
||||
|
||||
## Skip list
|
||||
|
||||
| ns/op | op/s | err% | total | benchmark |
|
||||
| -----: | -----------: | ---: | ----: | :---------------------------------- |
|
||||
| 246.99 | 4,048,700.59 | 0.2% | 0.01 | `point reads` |
|
||||
| 260.16 | 3,843,784.65 | 0.1% | 0.01 | `prefix reads` |
|
||||
| 493.35 | 2,026,953.19 | 0.1% | 0.01 | `range reads` |
|
||||
| 462.05 | 2,164,289.23 | 0.6% | 0.01 | `point writes` |
|
||||
| 448.19 | 2,231,205.25 | 0.9% | 0.01 | `prefix writes` |
|
||||
| 255.83 | 3,908,845.72 | 1.5% | 0.02 | `range writes` |
|
||||
| 582.63 | 1,716,349.02 | 1.3% | 0.01 | `monotonic increasing point writes` |
|
||||
| ns/op | op/s | err% | total | benchmark
|
||||
|--------------------:|--------------------:|--------:|----------:|:----------
|
||||
| 255.20 | 3,918,570.44 | 0.1% | 0.01 | `point reads`
|
||||
| 269.35 | 3,712,633.44 | 0.8% | 0.01 | `prefix reads`
|
||||
| 502.72 | 1,989,186.40 | 0.4% | 0.01 | `range reads`
|
||||
| 456.85 | 2,188,902.27 | 0.6% | 0.01 | `point writes`
|
||||
| 444.81 | 2,248,148.60 | 0.7% | 0.01 | `prefix writes`
|
||||
| 250.00 | 4,000,000.00 | 1.7% | 0.02 | `range writes`
|
||||
| 566.51 | 1,765,186.07 | 0.5% | 0.01 | `monotonic increasing point writes`
|
||||
| 226.41 | 4,416,703.74 | 0.5% | 0.01 | `worst case for radix tree`
|
||||
|
||||
## Radix tree (this implementation)
|
||||
|
||||
| ns/op | op/s | err% | total | benchmark |
|
||||
| -----: | ------------: | ---: | ----: | :---------------------------------- |
|
||||
| 19.42 | 51,483,206.67 | 0.3% | 0.01 | `point reads` |
|
||||
| 58.43 | 17,115,612.57 | 0.1% | 0.01 | `prefix reads` |
|
||||
| 216.09 | 4,627,766.60 | 0.2% | 0.01 | `range reads` |
|
||||
| 28.35 | 35,267,567.72 | 0.2% | 0.01 | `point writes` |
|
||||
| 43.43 | 23,026,226.17 | 0.2% | 0.01 | `prefix writes` |
|
||||
| 50.00 | 20,000,000.00 | 0.0% | 0.01 | `range writes` |
|
||||
| 92.38 | 10,824,863.69 | 4.1% | 0.01 | `monotonic increasing point writes` |
|
||||
| ns/op | op/s | err% | total | benchmark
|
||||
|--------------------:|--------------------:|--------:|----------:|:----------
|
||||
| 19.60 | 51,025,020.51 | 0.1% | 0.01 | `point reads`
|
||||
| 55.62 | 17,980,734.93 | 0.7% | 0.01 | `prefix reads`
|
||||
| 174.86 | 5,718,896.02 | 0.4% | 0.01 | `range reads`
|
||||
| 28.27 | 35,372,166.39 | 0.1% | 0.01 | `point writes`
|
||||
| 43.85 | 22,804,171.49 | 0.5% | 0.01 | `prefix writes`
|
||||
| 49.59 | 20,165,355.92 | 0.9% | 0.01 | `range writes`
|
||||
| 92.04 | 10,864,732.33 | 3.6% | 0.01 | `monotonic increasing point writes`
|
||||
| 6,937.00 | 144,154.53 | 0.2% | 0.01 | `worst case for radix tree`
|
||||
|
||||
# "Real data" test
|
||||
|
||||
|
@@ -1,3 +1,5 @@
|
||||
___stack_chk_fail
|
||||
___stack_chk_guard
|
||||
__tlv_bootstrap
|
||||
_abort
|
||||
_bzero
|
||||
|
Reference in New Issue
Block a user