Compare commits
495 Commits
v0.0.3
...
f403c78410
Author | SHA1 | Date | |
---|---|---|---|
f403c78410 | |||
08958d4109 | |||
dcc5275ec9 | |||
c5ef843f9e | |||
b78e817e24 | |||
9c82f17e20 | |||
665a9313a4 | |||
6e66202d5e | |||
a92271a205 | |||
0dbfb4deae | |||
6e229b6b36 | |||
2200de11c8 | |||
b37feb58dd | |||
94a4802824 | |||
707dbdb391 | |||
bdd343bb57 | |||
7b31bd5efe | |||
e255e1a926 | |||
f85b92f8db | |||
3c44614311 | |||
9c1ac3702e | |||
224d21648a | |||
33f9c89328 | |||
12c2d5eb95 | |||
db357e747d | |||
4494359ca2 | |||
f079d84bda | |||
724ec09248 | |||
4eaad39294 | |||
891100e649 | |||
22e55309be | |||
d6269c5b7c | |||
faacdff2d9 | |||
821179b8de | |||
681a961289 | |||
c73a3da14c | |||
5153d25cce | |||
d2ec4e7fae | |||
c7e2358746 | |||
ec1c1cf43f | |||
eaad0c69a7 | |||
309e6ab816 | |||
12b82c1be5 | |||
0cce9df8a8 | |||
0df09743da | |||
c4b0aa1085 | |||
051bfb05fe | |||
7e1bcbf9be | |||
4e685bbc3b | |||
b6bfc6f48d | |||
3b858551f3 | |||
2c1c26bc88 | |||
958ee15cfc | |||
9015b555de | |||
7aac73ee80 | |||
c06afeb81e | |||
b015711b7c | |||
f27ca6d6af | |||
c0bb175b7e | |||
6a6fe5738a | |||
dc16eccf06 | |||
3f15db7e82 | |||
e8a8b5aef1 | |||
b8fefff3ba | |||
2706b2f65e | |||
f1292efe41 | |||
a2d3d269ec | |||
8ff7a112b7 | |||
cf25b8626c | |||
e025f934d8 | |||
e5452c0f7d | |||
66fc526a55 | |||
21f08b9f88 | |||
10c2f06199 | |||
5cf04e9718 | |||
707b220fbc | |||
fd39065498 | |||
b963d481c9 | |||
e7ed47e288 | |||
04f138109b | |||
a0d07dd40c | |||
7fb408b466 | |||
6d265acfc7 | |||
67a61513b8 | |||
583f2e7612 | |||
66e5b033c0 | |||
1d705cd4b7 | |||
769cf8de9a | |||
84942a5bf8 | |||
7ad6872ee8 | |||
9db5eb960d | |||
5df25a138a | |||
381fbce0c0 | |||
87aeb349a3 | |||
28fb0d7faa | |||
5013c689a0 | |||
316bbf679f | |||
58aabe83f5 | |||
0c8a051913 | |||
11e8717da8 | |||
824037bf32 | |||
bbe964110e | |||
100449c76c | |||
51b5f638a4 | |||
767dacc742 | |||
978a7585b6 | |||
71b3c7fb7f | |||
420f50c40f | |||
69a131df38 | |||
8a4032e850 | |||
9c365435ea | |||
8eb5e76336 | |||
e8982074f2 | |||
f60833a57f | |||
47fd811efc | |||
73f93edf49 | |||
8bac1f66fc | |||
352c07cbc9 | |||
2e7e357355 | |||
147f5af16b | |||
323b239411 | |||
54c7ccb96b | |||
6a12210866 | |||
416504158e | |||
b0bc68a14e | |||
0de85ecda0 | |||
44afb8be00 | |||
ecdbaaf2c1 | |||
2c253c29b5 | |||
fe9678787d | |||
0ac259c782 | |||
8b1a0afc58 | |||
2018fa277c | |||
1faeb220d5 | |||
0dc657bfeb | |||
b51ef97c71 | |||
31ad3e8e1c | |||
e213237698 | |||
a1c61962a1 | |||
a28283748c | |||
cafa540fc8 | |||
b9c642d81d | |||
7abb129f03 | |||
3739ccaaf2 | |||
c3190c11ac | |||
52b4bf5a0e | |||
5516477956 | |||
f639db18a5 | |||
f8a1643714 | |||
a0a961ae58 | |||
f41a62471b | |||
d8f85dedc4 | |||
656939560b | |||
5580f9b71d | |||
628d16b7e6 | |||
d9e4a7d1b6 | |||
52201fa4c7 | |||
0814822d82 | |||
41df2398e8 | |||
84c4d0fcba | |||
6241533dfb | |||
0abf6a1ecf | |||
867136ff1b | |||
4b8f7320d3 | |||
6628092384 | |||
a0a4f1afea | |||
ca479c03ce | |||
0a2e133ab9 | |||
b0b31419b0 | |||
5c0cc1edf5 | |||
de47aa53b0 | |||
56893f9702 | |||
e2234be10f | |||
ce853680f2 | |||
5c39c1d64f | |||
55b73c8ddb | |||
b9503f8258 | |||
c4c4531bd3 | |||
2037d37c66 | |||
6fe6a244af | |||
8a4b370e2a | |||
394f09f9fb | |||
5e06a30357 | |||
cb6e4292f2 | |||
154a48ded0 | |||
c11b4714b5 | |||
bc13094406 | |||
c9d742b696 | |||
795ae7cb01 | |||
849e2d3e5c | |||
1560037680 | |||
764c31bbc8 | |||
ee3361952a | |||
8a04e57353 | |||
7f86fdee66 | |||
442755d0a6 | |||
e15b3bb137 | |||
311794c37e | |||
dfa178ba19 | |||
a16d18edfe | |||
2b60287448 | |||
0a9ac59676 | |||
e3a77ed773 | |||
cdf9a8a7b0 | |||
305dfdd52f | |||
7261c91492 | |||
f11720f5ae | |||
e2b7298af5 | |||
8e1e344f4b | |||
3634b6a59b | |||
a3cc14c807 | |||
55b3275434 | |||
3a5b86ed9e | |||
159f2eef74 | |||
2952abe811 | |||
ce54746a4a | |||
b15959d62c | |||
b009de1c2b | |||
55a230c75e | |||
0711ec3831 | |||
0280bd77e5 | |||
359f6f0042 | |||
aa8504ddba | |||
fb7cf18f9b | |||
b808b97940 | |||
e480f66846 | |||
d5bc9221a0 | |||
9d23b81d6f | |||
0740dcad43 | |||
176df61321 | |||
0a850f22e9 | |||
479b39d055 | |||
482408d725 | |||
45995e3307 | |||
359b0b29ff | |||
54e47ebd40 | |||
1c9dda68a6 | |||
142455dd28 | |||
567d385fbd | |||
8a44055533 | |||
62516825d1 | |||
3d592bd6a9 | |||
f5f5fb620b | |||
e3d1b2e842 | |||
9f8800af16 | |||
182c065c8e | |||
2dba0d5be3 | |||
a1dfdf355c | |||
15919cb1c4 | |||
5ed9003a83 | |||
84c6a2bfc2 | |||
b5772a6aa0 | |||
e6c39981b9 | |||
c20c08f112 | |||
ac98d4a443 | |||
1d9e8ab68b | |||
7d86beb14c | |||
2fa954ed36 | |||
ded6e7fc2c | |||
781ba15cae | |||
9b56a74b2f | |||
6da9cbdec9 | |||
29c05187fb | |||
d89028dd2f | |||
09cf807747 | |||
051eb5919d | |||
ed5589e4ed | |||
a7b3d8fe4c | |||
c3a047fdf8 | |||
b4b469a175 | |||
0201e27498 | |||
2010920a2c | |||
19af8da65c | |||
80785e3c3b | |||
4580ee44b4 | |||
2d3985ca40 | |||
c8be68db40 | |||
f5d021d6b6 | |||
1c41605b53 | |||
8f03a105bb | |||
0e574856be | |||
493a6572ad | |||
abce4591d0 | |||
d1dc1247e1 | |||
f1ad68109a | |||
c4443bc355 | |||
857b402fe2 | |||
9b3e1b219b | |||
ab52c63935 | |||
bad9d7ced8 | |||
c8d9dc034d | |||
72168ef6a3 | |||
620a0afd2a | |||
b0414969be | |||
1673e1c0dd | |||
7351b6e417 | |||
561ed45e3e | |||
ca804f28c0 | |||
3898cb596a | |||
b8edd92698 | |||
8e480528d5 | |||
4113183155 | |||
adb8fdc5e9 | |||
c86e407985 | |||
71a84057cb | |||
9c5e5863c2 | |||
be67555756 | |||
988ec5ce69 | |||
f5a0d81c52 | |||
3b2bd16cd1 | |||
4b3df0a426 | |||
4cdf6deb50 | |||
f21dde06d3 | |||
2b11650589 | |||
fce998460f | |||
6da3125719 | |||
79410d071f | |||
1fcca6450d | |||
55271ad06c | |||
e675612599 | |||
42b5d50492 | |||
6394995def | |||
c649bc7964 | |||
ec85a06d01 | |||
fb9f5ce6f4 | |||
2b1c710953 | |||
ebf281220b | |||
6051b2fb2e | |||
11c3ca6766 | |||
b45dec2f1f | |||
c5e9f18c47 | |||
cebbf89cbe | |||
abb791d86b | |||
12f361f33a | |||
640c1ca9dd | |||
b7d54d44e1 | |||
95596f831f | |||
542371d562 | |||
958a4e2d0e | |||
8ce14c58a4 | |||
56e847b63c | |||
7fd1c9e140 | |||
ebaac253e2 | |||
9b470a367c | |||
e7806a36d1 | |||
ffd1dfe74d | |||
c39af9117f | |||
ed274c24d7 | |||
cecfcc0da7 | |||
f6edde0e50 | |||
04ac41a7e7 | |||
354920f86f | |||
bfd02503e7 | |||
d0bd293f8d | |||
41e887c358 | |||
e394e3d96a | |||
3288c583e4 | |||
ef14003781 | |||
3ac16bc966 | |||
1e82f7fe22 | |||
4182d904c5 | |||
bd8ed4e7bd | |||
60cb274a15 | |||
687bc9c935 | |||
d50bb8bc80 | |||
f19b403f19 | |||
34cd210907 | |||
1a5da9e899 | |||
8ba9b04d8c | |||
d895be36d2 | |||
65f8462e88 | |||
46e01af027 | |||
c9d0d72684 | |||
9046dc5a8f | |||
e2927bf0fa | |||
75a2b8d06c | |||
76df63a9d7 | |||
9c5b38b09a | |||
7142dab7ae | |||
3db3d975fc | |||
982b31af34 | |||
cc716ef16b | |||
88bcc7b75c | |||
3e6be6bd83 | |||
e59fee39c7 | |||
3e2c8310bb | |||
8264f1342d | |||
5d7e9c6f85 | |||
cdf42fcb34 | |||
cbe40b5dba | |||
a04e81b3ff | |||
0be97a34b6 | |||
68ab9a9f08 | |||
01488880ef | |||
bb84792cff | |||
1f421e95ff | |||
66bd799f05 | |||
2646d5eaf1 | |||
0367ba9856 | |||
9dec45317e | |||
a68ad5dd17 | |||
8e3eacb54f | |||
0184e1d7f6 | |||
c52d50f4f9 | |||
447da11d59 | |||
daa8e02d4f | |||
fd3ea2c2a8 | |||
0b839b9d7e | |||
11a022dcf7 | |||
94da4c72a5 | |||
461e07822a | |||
75499543e7 | |||
81f44d352f | |||
45da8fb996 | |||
4958a4cced | |||
587874841f | |||
648b0b9238 | |||
d3f4afa167 | |||
f762add4d6 | |||
b311e5f1f0 | |||
ff81890921 | |||
0e96177f5c | |||
efb0e52a0a | |||
2df7000090 | |||
5378a06c39 | |||
12c6ed2568 | |||
a2bf839b19 | |||
c065b185ae | |||
639518bed4 | |||
7de983cc15 | |||
1b4b61ddc6 | |||
bff7b85de2 | |||
9108ee209a | |||
f8bf1c6eb4 | |||
4da2a01614 | |||
bb0e654040 | |||
cce7d29410 | |||
13f8d3fa8a | |||
02866a8cae | |||
fa86d3e707 | |||
7d1d1d7b2a | |||
789ecc29b3 | |||
08f2998a85 | |||
c882d7663d | |||
bfea4384ba | |||
6520e3d734 | |||
23ace8aac5 | |||
62e35de320 | |||
22e4ab01a1 | |||
b3aeed0caa | |||
5f3833e965 | |||
8b1cd9c052 | |||
bb9bc3d7b5 | |||
89b3354a80 | |||
488c723726 | |||
76d0785b33 | |||
add0af11ad | |||
2c0adf4a8b | |||
e8ac78cce6 | |||
13d447c9fe | |||
da7523c5cf | |||
a074bc6f72 | |||
1553a44986 | |||
859ac352e6 | |||
2eb461b8ea | |||
e2e92f4ef5 | |||
f6f25cfcce | |||
c13dc88ff4 | |||
aa5dbb2887 | |||
ea76e04cda | |||
452007e079 | |||
37c75f747b | |||
c96d682483 | |||
6e63fd5126 | |||
f2678de811 | |||
4d7ad075b2 | |||
d2e1863593 | |||
bf91bca16d | |||
08ed17f47b | |||
76a45f16ad | |||
c15d296432 | |||
64a98c529c | |||
ed1388ed21 | |||
309d315956 | |||
eab2e46a56 | |||
85db1a8786 | |||
717f9d6829 | |||
fd93300ce8 | |||
b7e16b31ff | |||
a324d31518 | |||
fdb05e0e33 | |||
7c27d4a972 | |||
738de01cb4 | |||
325cab6a95 | |||
0b2821941a |
157
Bench.cpp
157
Bench.cpp
@@ -7,7 +7,6 @@
|
||||
void showMemory(const ConflictSet &cs);
|
||||
#endif
|
||||
|
||||
#define ANKERL_NANOBENCH_IMPLEMENT
|
||||
#include "third_party/nanobench.h"
|
||||
|
||||
constexpr int kNumKeys = 1000000;
|
||||
@@ -18,26 +17,26 @@ constexpr int kPrefixLen = 0;
|
||||
|
||||
constexpr int kMvccWindow = 100000;
|
||||
|
||||
std::span<const uint8_t> makeKey(Arena &arena, int index) {
|
||||
TrivialSpan makeKey(Arena &arena, int index) {
|
||||
|
||||
auto result =
|
||||
std::span<uint8_t>{new (arena) uint8_t[4 + kPrefixLen], 4 + kPrefixLen};
|
||||
uint8_t *buf = new (arena) uint8_t[4 + kPrefixLen];
|
||||
auto result = TrivialSpan{buf, 4 + kPrefixLen};
|
||||
index = __builtin_bswap32(index);
|
||||
memset(result.data(), 0, kPrefixLen);
|
||||
memcpy(result.data() + kPrefixLen, &index, 4);
|
||||
memset(buf, 0, kPrefixLen);
|
||||
memcpy(buf, &index, 4);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ConflictSet::ReadRange singleton(Arena &arena, std::span<const uint8_t> key) {
|
||||
auto r =
|
||||
std::span<uint8_t>(new (arena) uint8_t[key.size() + 1], key.size() + 1);
|
||||
memcpy(r.data(), key.data(), key.size());
|
||||
r[key.size()] = 0;
|
||||
ConflictSet::ReadRange singleton(Arena &arena, TrivialSpan key) {
|
||||
uint8_t *buf = new (arena) uint8_t[key.size() + 1];
|
||||
auto r = TrivialSpan(buf, key.size() + 1);
|
||||
memcpy(buf, key.data(), key.size());
|
||||
buf[key.size()] = 0;
|
||||
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, TrivialSpan key) {
|
||||
int index;
|
||||
for (index = key.size() - 1; index >= 0; index--)
|
||||
if ((key[index]) != 255)
|
||||
@@ -49,9 +48,10 @@ ConflictSet::ReadRange prefixRange(Arena &arena, std::span<const uint8_t> key) {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
auto r = std::span<uint8_t>(new (arena) uint8_t[index + 1], index + 1);
|
||||
memcpy(r.data(), key.data(), index + 1);
|
||||
r[r.size() - 1]++;
|
||||
uint8_t *buf = new (arena) uint8_t[index + 1];
|
||||
auto r = TrivialSpan(buf, index + 1);
|
||||
memcpy(buf, key.data(), index + 1);
|
||||
buf[r.size() - 1]++;
|
||||
return {{key.data(), int(key.size())}, {r.data(), int(r.size())}, 0};
|
||||
}
|
||||
|
||||
@@ -82,14 +82,7 @@ void benchConflictSet() {
|
||||
++version;
|
||||
}
|
||||
|
||||
// I don't know why std::less didn't work /shrug
|
||||
struct Less {
|
||||
bool operator()(const std::span<const uint8_t> &lhs,
|
||||
const std::span<const uint8_t> &rhs) const {
|
||||
return lhs < rhs;
|
||||
}
|
||||
};
|
||||
auto points = set<std::span<const uint8_t>, Less>(arena);
|
||||
auto points = set<TrivialSpan, std::less<>>(arena);
|
||||
|
||||
while (points.size() < kOpsPerTx * 2 + 1) {
|
||||
// TODO don't use rand?
|
||||
@@ -258,4 +251,120 @@ void benchConflictSet() {
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) { benchConflictSet(); }
|
||||
constexpr int kKeyLenForWorstCase = 50;
|
||||
|
||||
ConflictSet worstCaseConflictSetForRadixRangeRead(int cardinality) {
|
||||
ConflictSet cs{0};
|
||||
|
||||
for (int i = 0; i < kKeyLenForWorstCase; ++i) {
|
||||
for (int j = 0; j < cardinality; ++j) {
|
||||
auto b = std::vector<uint8_t>(i, 0);
|
||||
b.push_back(j);
|
||||
auto e = std::vector<uint8_t>(i, 255);
|
||||
e.push_back(255 - j);
|
||||
weaselab::ConflictSet::WriteRange w[] = {{
|
||||
{b.data(), int(b.size())},
|
||||
{nullptr, 0},
|
||||
},
|
||||
{
|
||||
{e.data(), int(e.size())},
|
||||
{nullptr, 0},
|
||||
}};
|
||||
std::sort(std::begin(w), std::end(w),
|
||||
[](const auto &lhs, const auto &rhs) {
|
||||
int cl = std::min(lhs.begin.len, rhs.begin.len);
|
||||
if (cl > 0) {
|
||||
int c = memcmp(lhs.begin.p, rhs.begin.p, cl);
|
||||
if (c != 0) {
|
||||
return c < 0;
|
||||
}
|
||||
}
|
||||
return lhs.begin.len < rhs.begin.len;
|
||||
});
|
||||
cs.addWrites(w, sizeof(w) / sizeof(w[0]), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Defeat short-circuiting on the left
|
||||
{
|
||||
auto k = std::vector<uint8_t>(kKeyLenForWorstCase, 0);
|
||||
weaselab::ConflictSet::WriteRange w[] = {
|
||||
{
|
||||
{k.data(), int(k.size())},
|
||||
{nullptr, 0},
|
||||
},
|
||||
};
|
||||
cs.addWrites(w, sizeof(w) / sizeof(w[0]), 1);
|
||||
}
|
||||
|
||||
// Defeat short-circuiting on the right
|
||||
{
|
||||
auto k = std::vector<uint8_t>(kKeyLenForWorstCase, 255);
|
||||
weaselab::ConflictSet::WriteRange w[] = {
|
||||
{
|
||||
{k.data(), int(k.size())},
|
||||
{nullptr, 0},
|
||||
},
|
||||
};
|
||||
cs.addWrites(w, sizeof(w) / sizeof(w[0]), 1);
|
||||
}
|
||||
|
||||
return cs;
|
||||
}
|
||||
|
||||
void benchWorstCaseForRadixRangeRead() {
|
||||
ankerl::nanobench::Bench bench;
|
||||
|
||||
std::unique_ptr<ConflictSet> cs[256];
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
cs[i] =
|
||||
std::make_unique<ConflictSet>(worstCaseConflictSetForRadixRangeRead(i));
|
||||
}
|
||||
|
||||
auto begin = std::vector<uint8_t>(kKeyLenForWorstCase - 1, 0);
|
||||
begin.push_back(1);
|
||||
auto end = std::vector<uint8_t>(kKeyLenForWorstCase - 1, 255);
|
||||
end.push_back(254);
|
||||
|
||||
weaselab::ConflictSet::ReadRange r[] = {
|
||||
{{begin.data(), int(begin.size())}, {end.data(), int(end.size())}, 0},
|
||||
};
|
||||
weaselab::ConflictSet::Result results[sizeof(r) / sizeof(r[0])];
|
||||
for (auto &result : results) {
|
||||
result = weaselab::ConflictSet::TooOld;
|
||||
}
|
||||
bench.batch(sizeof(r) / sizeof(r[0]));
|
||||
|
||||
bench.run("worst case for radix tree", [&]() {
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
cs[i]->check(r, results, sizeof(r) / sizeof(r[0]));
|
||||
for (auto result : results) {
|
||||
if (result != weaselab::ConflictSet::Commit) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// for (int i = 0; i < 256; ++i) {
|
||||
// bench.run("worst case for radix tree, span " + std::to_string(i), [&]() {
|
||||
// result = weaselab::ConflictSet::TooOld;
|
||||
// cs[i]->check(&r, &result, 1);
|
||||
// if (result != weaselab::ConflictSet::Commit) {
|
||||
// abort();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
void benchCreateAndDestroy() {
|
||||
ankerl::nanobench::Bench bench;
|
||||
|
||||
bench.run("create and destroy", [&]() { ConflictSet cs{0}; });
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
benchConflictSet();
|
||||
benchWorstCaseForRadixRangeRead();
|
||||
benchCreateAndDestroy();
|
||||
}
|
||||
|
325
CMakeLists.txt
325
CMakeLists.txt
@@ -1,14 +1,16 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(
|
||||
conflict-set
|
||||
VERSION 0.0.3
|
||||
VERSION 0.0.14
|
||||
DESCRIPTION
|
||||
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
|
||||
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
|
||||
LANGUAGES C CXX)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/version.txt ${PROJECT_VERSION})
|
||||
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/version.txt ${PROJECT_VERSION})
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.txt.in
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/paper/version.txt)
|
||||
|
||||
include(CMakePushCheckState)
|
||||
include(CheckCXXCompilerFlag)
|
||||
@@ -17,12 +19,6 @@ include(CheckCXXSourceCompiles)
|
||||
|
||||
set(DEFAULT_BUILD_TYPE "Release")
|
||||
|
||||
if(EMSCRIPTEN OR CMAKE_SYSTEM_NAME STREQUAL WASI)
|
||||
set(WASM ON)
|
||||
else()
|
||||
set(WASM OFF)
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
message(
|
||||
STATUS
|
||||
@@ -35,8 +31,34 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||
"MinSizeRel" "RelWithDebInfo")
|
||||
endif()
|
||||
|
||||
add_compile_options(-fdata-sections -ffunction-sections -Wswitch-enum
|
||||
-Werror=switch-enum -fPIC)
|
||||
add_compile_options(
|
||||
-Werror=switch-enum
|
||||
-Wswitch-enum
|
||||
-Wunused-variable
|
||||
-fPIC
|
||||
-fdata-sections
|
||||
-ffunction-sections
|
||||
-fno-jump-tables # https://github.com/llvm/llvm-project/issues/54247
|
||||
)
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
add_link_options("-Wno-unused-command-line-argument")
|
||||
find_program(LLVM_OBJCOPY llvm-objcopy)
|
||||
if(LLVM_OBJCOPY)
|
||||
set(CMAKE_OBJCOPY
|
||||
${LLVM_OBJCOPY}
|
||||
CACHE FILEPATH "path to objcopy binary" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
add_compile_options("-Wno-maybe-uninitialized")
|
||||
endif()
|
||||
|
||||
if(NOT APPLE)
|
||||
# This causes some versions of clang to crash on macos
|
||||
add_compile_options(-g -fno-omit-frame-pointer)
|
||||
endif()
|
||||
|
||||
set(full_relro_flags "-pie;LINKER:-z,relro,-z,now,-z,noexecstack")
|
||||
cmake_push_check_state()
|
||||
@@ -47,7 +69,23 @@ if(HAS_FULL_RELRO)
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
set(version_script_flags LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map)
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||
add_compile_options(-mbranch-protection=standard)
|
||||
else()
|
||||
add_compile_options(-fcf-protection)
|
||||
set(rewrite_endbr_flags "-fuse-ld=mold;LINKER:-z,rewrite-endbr")
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${rewrite_endbr_flags})
|
||||
check_cxx_source_compiles("int main(){}" HAS_REWRITE_ENDBR FAIL_REGEX
|
||||
"warning:")
|
||||
if(HAS_REWRITE_ENDBR)
|
||||
add_link_options(${rewrite_endbr_flags})
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
endif()
|
||||
|
||||
set(version_script_flags
|
||||
LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/linker.map)
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${version_script_flags})
|
||||
check_cxx_source_compiles("int main(){}" HAS_VERSION_SCRIPT FAIL_REGEX
|
||||
@@ -57,9 +95,11 @@ cmake_pop_check_state()
|
||||
option(USE_SIMD_FALLBACK
|
||||
"Use fallback implementations of functions that use SIMD" OFF)
|
||||
|
||||
option(DISABLE_TSAN "Disable TSAN" OFF)
|
||||
|
||||
# This is encouraged according to
|
||||
# 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_CURRENT_SOURCE_DIR}/third_party/valgrind)
|
||||
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
||||
|
||||
@@ -69,34 +109,11 @@ else()
|
||||
add_link_options(-Wl,--gc-sections)
|
||||
endif()
|
||||
|
||||
if(EMSCRIPTEN)
|
||||
# https://github.com/emscripten-core/emscripten/issues/15377#issuecomment-1285167486
|
||||
add_link_options(-lnodefs.js -lnoderawfs.js)
|
||||
add_link_options(-s ALLOW_MEMORY_GROWTH)
|
||||
endif()
|
||||
|
||||
if(NOT USE_SIMD_FALLBACK)
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_FLAGS -msimd128)
|
||||
check_include_file_cxx("wasm_simd128.h" HAS_WASM_SIMD)
|
||||
if(HAS_WASM_SIMD)
|
||||
add_compile_options(-msimd128)
|
||||
add_compile_definitions(HAS_WASM_SIMD)
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
cmake_push_check_state()
|
||||
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
||||
check_include_file_cxx("immintrin.h" HAS_AVX)
|
||||
if(HAS_AVX)
|
||||
if(USE_SIMD_FALLBACK)
|
||||
add_compile_definitions(USE_SIMD_FALLBACK)
|
||||
else()
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL x86_64)
|
||||
add_compile_options(-mavx)
|
||||
add_compile_definitions(HAS_AVX)
|
||||
endif()
|
||||
cmake_pop_check_state()
|
||||
|
||||
check_include_file_cxx("arm_neon.h" HAS_ARM_NEON)
|
||||
if(HAS_ARM_NEON)
|
||||
add_compile_definitions(HAS_ARM_NEON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -106,88 +123,96 @@ add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp)
|
||||
target_compile_options(${PROJECT_NAME}-object PRIVATE -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(${PROJECT_NAME}-object
|
||||
PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
if(NOT LD_EXE)
|
||||
set(LD_EXE ld)
|
||||
endif()
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.o
|
||||
COMMAND ${LD_EXE} -r $<TARGET_OBJECTS:${PROJECT_NAME}-object> -o
|
||||
${CMAKE_BINARY_DIR}/${PROJECT_NAME}.o
|
||||
DEPENDS $<TARGET_OBJECTS:${PROJECT_NAME}-object>
|
||||
COMMAND_EXPAND_LISTS)
|
||||
|
||||
add_library(${PROJECT_NAME} SHARED $<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||
add_library(${PROJECT_NAME} SHARED ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.o)
|
||||
set_target_properties(
|
||||
${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_BINARY_DIR}/radix_tree")
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/radix_tree")
|
||||
if(CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE CXX)
|
||||
else()
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE C)
|
||||
endif()
|
||||
|
||||
if(HAS_VERSION_SCRIPT)
|
||||
target_link_options(${PROJECT_NAME} PRIVATE
|
||||
LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map)
|
||||
target_link_options(
|
||||
${PROJECT_NAME} PRIVATE
|
||||
LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/linker.map)
|
||||
endif()
|
||||
|
||||
add_library(${PROJECT_NAME}-static STATIC
|
||||
$<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
add_library(${PROJECT_NAME}-static STATIC ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.o)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
set_target_properties(${PROJECT_NAME}-static PROPERTIES LINKER_LANGUAGE CXX)
|
||||
else()
|
||||
set_target_properties(${PROJECT_NAME}-static PROPERTIES LINKER_LANGUAGE C)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT_NAME}-static
|
||||
PRE_LINK
|
||||
COMMAND ${CMAKE_SOURCE_DIR}/privatize_symbols_macos.sh
|
||||
$<TARGET_OBJECTS:${PROJECT_NAME}-object>)
|
||||
else()
|
||||
if(NOT APPLE)
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT_NAME}-static
|
||||
POST_BUILD
|
||||
COMMAND
|
||||
${CMAKE_OBJCOPY}
|
||||
--keep-global-symbols=${CMAKE_SOURCE_DIR}/symbol-exports.txt
|
||||
--keep-global-symbols=${CMAKE_CURRENT_SOURCE_DIR}/symbol-exports.txt
|
||||
$<TARGET_FILE:${PROJECT_NAME}-static> || echo
|
||||
"Proceeding with all symbols global in static library")
|
||||
endif()
|
||||
|
||||
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
||||
|
||||
include(CTest)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
# disable tests if this is being used through e.g. FetchContent
|
||||
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND BUILD_TESTING)
|
||||
|
||||
add_library(nanobench ${CMAKE_CURRENT_SOURCE_DIR}/nanobench.cpp)
|
||||
|
||||
set(TEST_FLAGS -Wall -Wextra -Wunreachable-code -Wpedantic -UNDEBUG)
|
||||
|
||||
# corpus tests, which are tests curated by libfuzzer. The goal is to get broad
|
||||
# coverage with a small number of tests.
|
||||
|
||||
file(GLOB CORPUS_TESTS ${CMAKE_SOURCE_DIR}/corpus/*)
|
||||
file(GLOB CORPUS_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/corpus/*)
|
||||
|
||||
# extra testing that relies on shared libraries, which aren't available with
|
||||
# wasm
|
||||
if(NOT WASM)
|
||||
# Shared library version of FoundationDB's skip list implementation
|
||||
add_library(skip_list SHARED SkipList.cpp)
|
||||
target_compile_options(skip_list PRIVATE -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(skip_list PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
set_target_properties(skip_list PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_BINARY_DIR}/skip_list")
|
||||
set_target_properties(skip_list PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
set_target_properties(
|
||||
skip_list PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
|
||||
${PROJECT_VERSION_MAJOR})
|
||||
# Shared library version of FoundationDB's skip list implementation
|
||||
add_library(skip_list SHARED SkipList.cpp)
|
||||
target_compile_options(skip_list PRIVATE -fno-exceptions -fvisibility=hidden)
|
||||
target_include_directories(skip_list
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
set_target_properties(
|
||||
skip_list PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/skip_list")
|
||||
set_target_properties(skip_list PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
set_target_properties(skip_list PROPERTIES VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||
|
||||
# Shared library version of a std::unordered_map-based conflict set (point
|
||||
# queries only)
|
||||
add_library(hash_table SHARED HashTable.cpp)
|
||||
target_compile_options(hash_table PRIVATE -fno-exceptions
|
||||
-fvisibility=hidden)
|
||||
target_include_directories(hash_table PUBLIC ${CMAKE_SOURCE_DIR}/include)
|
||||
set_target_properties(
|
||||
hash_table PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_BINARY_DIR}/hash_table")
|
||||
set_target_properties(hash_table PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
set_target_properties(
|
||||
hash_table PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
|
||||
${PROJECT_VERSION_MAJOR})
|
||||
# Shared library version of a std::unordered_map-based conflict set (point
|
||||
# queries only)
|
||||
add_library(hash_table SHARED HashTable.cpp)
|
||||
target_compile_options(hash_table PRIVATE -fno-exceptions -fvisibility=hidden)
|
||||
target_include_directories(hash_table
|
||||
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
set_target_properties(
|
||||
hash_table PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/hash_table")
|
||||
set_target_properties(hash_table PROPERTIES OUTPUT_NAME ${PROJECT_NAME})
|
||||
set_target_properties(
|
||||
hash_table PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION
|
||||
${PROJECT_VERSION_MAJOR})
|
||||
|
||||
add_executable(driver_skip_list TestDriver.cpp)
|
||||
target_compile_options(driver_skip_list PRIVATE ${TEST_FLAGS})
|
||||
target_link_libraries(driver_skip_list PRIVATE skip_list)
|
||||
add_executable(driver_skip_list TestDriver.cpp)
|
||||
target_compile_options(driver_skip_list PRIVATE ${TEST_FLAGS})
|
||||
target_link_libraries(driver_skip_list PRIVATE skip_list)
|
||||
|
||||
# enable to test skip list
|
||||
if(0)
|
||||
foreach(TEST ${CORPUS_TESTS})
|
||||
get_filename_component(hash ${TEST} NAME)
|
||||
add_test(NAME skip_list_${hash} COMMAND driver_skip_list ${TEST})
|
||||
@@ -199,6 +224,7 @@ if(BUILD_TESTING)
|
||||
target_include_directories(conflict_set_main
|
||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
target_compile_definitions(conflict_set_main PRIVATE ENABLE_MAIN)
|
||||
target_link_libraries(conflict_set_main PRIVATE nanobench)
|
||||
|
||||
if(NOT APPLE)
|
||||
# libfuzzer target, to generate/manage corpus
|
||||
@@ -237,7 +263,7 @@ if(BUILD_TESTING)
|
||||
endforeach()
|
||||
|
||||
# tsan tests
|
||||
if(NOT CMAKE_CROSSCOMPILING AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
if(NOT CMAKE_CROSSCOMPILING AND NOT DISABLE_TSAN)
|
||||
add_executable(tsan_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||
target_compile_options(tsan_driver PRIVATE ${TEST_FLAGS} -fsanitize=thread)
|
||||
target_link_options(tsan_driver PRIVATE -fsanitize=thread)
|
||||
@@ -259,22 +285,48 @@ if(BUILD_TESTING)
|
||||
add_test(NAME conflict_set_blackbox_${hash} COMMAND driver ${TEST})
|
||||
endforeach()
|
||||
|
||||
# scripted tests. Written manually to fill in anything libfuzzer couldn't
|
||||
# find.
|
||||
add_executable(script_test ScriptTest.cpp)
|
||||
target_compile_options(script_test PRIVATE ${TEST_FLAGS})
|
||||
target_link_libraries(script_test PRIVATE ${PROJECT_NAME})
|
||||
file(GLOB SCRIPT_TESTS ${CMAKE_SOURCE_DIR}/script_tests/*)
|
||||
foreach(TEST ${SCRIPT_TESTS})
|
||||
get_filename_component(name ${TEST} NAME)
|
||||
add_test(NAME conflict_set_script_${name} COMMAND script_test ${TEST})
|
||||
endforeach()
|
||||
|
||||
find_program(VALGRIND_EXE valgrind)
|
||||
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
|
||||
add_test(NAME conflict_set_blackbox_valgrind
|
||||
COMMAND ${VALGRIND_EXE} --error-exitcode=99 --
|
||||
$<TARGET_FILE:driver> ${CORPUS_TESTS})
|
||||
list(LENGTH CORPUS_TESTS len)
|
||||
math(EXPR last "${len} - 1")
|
||||
set(partition_size 100)
|
||||
foreach(i RANGE 0 ${last} ${partition_size})
|
||||
list(SUBLIST CORPUS_TESTS ${i} ${partition_size} partition)
|
||||
add_test(NAME conflict_set_blackbox_valgrind_${i}
|
||||
COMMAND ${VALGRIND_EXE} --error-exitcode=99 --
|
||||
$<TARGET_FILE:driver> ${partition})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# scripted tests. Written manually to fill in anything libfuzzer couldn't
|
||||
# find.
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
find_package(Python3 REQUIRED COMPONENTS Interpreter)
|
||||
set_property(
|
||||
DIRECTORY
|
||||
APPEND
|
||||
PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py)
|
||||
execute_process(
|
||||
COMMAND ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py list
|
||||
OUTPUT_VARIABLE SCRIPT_TESTS)
|
||||
foreach(TEST ${SCRIPT_TESTS})
|
||||
add_test(
|
||||
NAME script_test_${TEST}
|
||||
COMMAND
|
||||
${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py test ${TEST}
|
||||
--build-dir ${CMAKE_CURRENT_BINARY_DIR})
|
||||
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
|
||||
add_test(
|
||||
NAME script_test_${TEST}_valgrind
|
||||
COMMAND
|
||||
${VALGRIND_EXE} ${Python3_EXECUTABLE}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_conflict_set.py test ${TEST}
|
||||
--build-dir ${CMAKE_CURRENT_BINARY_DIR})
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# api smoke tests
|
||||
@@ -298,38 +350,66 @@ if(BUILD_TESTING)
|
||||
add_test(NAME conflict_set_cxx_api_test COMMAND conflict_set_cxx_api_test)
|
||||
|
||||
# symbol visibility tests
|
||||
if(NOT WASM AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||
if(APPLE)
|
||||
set(symbol_exports ${CMAKE_SOURCE_DIR}/apple-symbol-exports.txt)
|
||||
set(symbol_imports ${CMAKE_SOURCE_DIR}/apple-symbol-imports.txt)
|
||||
set(symbol_exports ${CMAKE_CURRENT_SOURCE_DIR}/apple-symbol-exports.txt)
|
||||
set(symbol_imports ${CMAKE_CURRENT_SOURCE_DIR}/apple-symbol-imports.txt)
|
||||
else()
|
||||
set(symbol_exports ${CMAKE_SOURCE_DIR}/symbol-exports.txt)
|
||||
set(symbol_exports ${CMAKE_CURRENT_SOURCE_DIR}/symbol-exports.txt)
|
||||
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||
set(symbol_imports ${CMAKE_SOURCE_DIR}/aarch64-symbol-imports.txt)
|
||||
set(symbol_imports
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/aarch64-symbol-imports.txt)
|
||||
else()
|
||||
set(symbol_imports ${CMAKE_SOURCE_DIR}/symbol-imports.txt)
|
||||
set(symbol_imports ${CMAKE_CURRENT_SOURCE_DIR}/symbol-imports.txt)
|
||||
endif()
|
||||
endif()
|
||||
add_test(
|
||||
NAME conflict_set_shared_symbols
|
||||
COMMAND
|
||||
${CMAKE_SOURCE_DIR}/test_symbols.sh $<TARGET_FILE:${PROJECT_NAME}>
|
||||
${symbol_exports} ${symbol_imports})
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}> ${symbol_exports} ${symbol_imports})
|
||||
add_test(
|
||||
NAME conflict_set_static_symbols
|
||||
COMMAND
|
||||
${CMAKE_SOURCE_DIR}/test_symbols.sh
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
|
||||
$<TARGET_FILE:${PROJECT_NAME}-static> ${symbol_exports}
|
||||
${symbol_imports})
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
find_program(HARDENING_CHECK hardening-check)
|
||||
if(HARDENING_CHECK)
|
||||
add_test(NAME hardening_check
|
||||
COMMAND ${HARDENING_CHECK} $<TARGET_FILE:${PROJECT_NAME}>
|
||||
--nofortify --nostackprotector)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# bench
|
||||
add_executable(conflict_set_bench Bench.cpp)
|
||||
target_link_libraries(conflict_set_bench PRIVATE ${PROJECT_NAME})
|
||||
target_link_libraries(conflict_set_bench PRIVATE ${PROJECT_NAME} nanobench)
|
||||
set_target_properties(conflict_set_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||
add_executable(real_data_bench RealDataBench.cpp)
|
||||
target_link_libraries(real_data_bench PRIVATE ${PROJECT_NAME})
|
||||
set_target_properties(real_data_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||
|
||||
# fuzzer-based perf
|
||||
add_executable(driver_perf TestDriver.cpp)
|
||||
target_compile_definitions(driver_perf PRIVATE PERF_TEST=1)
|
||||
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)
|
||||
|
||||
add_executable(interleaving_test InterleavingTest.cpp)
|
||||
# work around lack of musttail for gcc
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_options(interleaving_test PRIVATE -Og
|
||||
-foptimize-sibling-calls)
|
||||
endif()
|
||||
target_link_libraries(interleaving_test PRIVATE nanobench)
|
||||
endif()
|
||||
|
||||
# packaging
|
||||
@@ -357,16 +437,17 @@ else()
|
||||
endif()
|
||||
|
||||
# macos
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0)
|
||||
if(APPLE)
|
||||
find_program(PANDOC_EXE pandoc)
|
||||
if(PANDOC_EXE)
|
||||
execute_process(COMMAND ${PANDOC_EXE} ${CMAKE_SOURCE_DIR}/README.md -o
|
||||
${CMAKE_BINARY_DIR}/README.txt)
|
||||
set(CPACK_RESOURCE_FILE_README ${CMAKE_BINARY_DIR}/README.txt)
|
||||
execute_process(COMMAND ${PANDOC_EXE} ${CMAKE_CURRENT_SOURCE_DIR}/README.md
|
||||
-o ${CMAKE_CURRENT_BINARY_DIR}/README.txt)
|
||||
set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_BINARY_DIR}/README.txt)
|
||||
endif()
|
||||
configure_file(${CMAKE_SOURCE_DIR}/LICENSE ${CMAKE_BINARY_DIR}/LICENSE.txt
|
||||
COPYONLY)
|
||||
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_BINARY_DIR}/LICENSE.txt)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/LICENSE
|
||||
${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt COPYONLY)
|
||||
set(CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt)
|
||||
endif()
|
||||
|
||||
include(CPack)
|
||||
|
6629
ConflictSet.cpp
6629
ConflictSet.cpp
File diff suppressed because it is too large
Load Diff
17
Dockerfile
17
Dockerfile
@@ -8,25 +8,27 @@ RUN chmod -R 777 /tmp
|
||||
RUN apt-get update
|
||||
RUN apt-get upgrade -y
|
||||
RUN TZ=America/Los_Angeles DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||
binutils-aarch64-linux-gnu \
|
||||
build-essential \
|
||||
ccache \
|
||||
clang \
|
||||
cmake \
|
||||
curl \
|
||||
doxygen \
|
||||
file \
|
||||
devscripts \
|
||||
g++-aarch64-linux-gnu \
|
||||
gcovr \
|
||||
git \
|
||||
gperf \
|
||||
graphviz \
|
||||
gnupg \
|
||||
libc6-dbg \
|
||||
lsb-release \
|
||||
mold \
|
||||
ninja-build \
|
||||
pre-commit \
|
||||
python3-requests \
|
||||
qemu-user \
|
||||
rpm \
|
||||
software-properties-common \
|
||||
texlive-full \
|
||||
wget \
|
||||
zstd
|
||||
|
||||
# Install recent valgrind from source
|
||||
@@ -42,6 +44,11 @@ RUN curl -Ls https://sourceware.org/pub/valgrind/valgrind-3.22.0.tar.bz2 -o valg
|
||||
cd .. && \
|
||||
rm -rf /tmp/*
|
||||
|
||||
# Recent clang
|
||||
RUN wget https://apt.llvm.org/llvm.sh && chmod +x ./llvm.sh && ./llvm.sh 20
|
||||
|
||||
RUN apt-get -y install clang llvm
|
||||
|
||||
# Set after building valgrind, which doesn't build with clang for some reason
|
||||
ENV CC=clang
|
||||
ENV CXX=clang++
|
||||
|
@@ -98,6 +98,13 @@ void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
||||
|
||||
int64_t ConflictSet::getBytes() const { return -1; }
|
||||
|
||||
void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const {
|
||||
*metrics = nullptr;
|
||||
*count = 0;
|
||||
}
|
||||
|
||||
double ConflictSet::MetricsV1::getValue() const { return 0; }
|
||||
|
||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||
: impl(new(safe_malloc(sizeof(Impl))) Impl{oldestVersion}) {}
|
||||
|
||||
|
256
InterleavingTest.cpp
Normal file
256
InterleavingTest.cpp
Normal file
@@ -0,0 +1,256 @@
|
||||
#include <alloca.h>
|
||||
#include <cassert>
|
||||
#ifdef __x86_64__
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
#include "third_party/nanobench.h"
|
||||
|
||||
struct Job {
|
||||
int *input;
|
||||
// Returned void* is a function pointer to the next continuation. We have to
|
||||
// use void* because otherwise the type would be recursive.
|
||||
typedef void *(*continuation)(Job *);
|
||||
continuation next;
|
||||
};
|
||||
|
||||
void *stepJob(Job *j) {
|
||||
auto done = --(*j->input) == 0;
|
||||
#ifdef __x86_64__
|
||||
_mm_clflush(j->input);
|
||||
#endif
|
||||
return done ? nullptr : (void *)stepJob;
|
||||
}
|
||||
|
||||
void sequential(Job **jobs, int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
do {
|
||||
jobs[i]->next = (Job::continuation)jobs[i]->next(jobs[i]);
|
||||
} while (jobs[i]->next);
|
||||
}
|
||||
}
|
||||
|
||||
void sequentialNoFuncPtr(Job **jobs, int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
while (stepJob(jobs[i]))
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
void interleaveSwapping(Job **jobs, int remaining) {
|
||||
int current = 0;
|
||||
while (remaining > 0) {
|
||||
auto next = (Job::continuation)jobs[current]->next(jobs[current]);
|
||||
jobs[current]->next = next;
|
||||
if (next == nullptr) {
|
||||
jobs[current] = jobs[remaining - 1];
|
||||
--remaining;
|
||||
} else {
|
||||
++current;
|
||||
}
|
||||
if (current == remaining) {
|
||||
current = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void interleaveBoundedCyclicList(Job **jobs, int count) {
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int kConcurrent = 32;
|
||||
Job *inProgress[kConcurrent];
|
||||
int nextJob[kConcurrent];
|
||||
|
||||
int started = std::min(kConcurrent, count);
|
||||
for (int i = 0; i < started; i++) {
|
||||
inProgress[i] = jobs[i];
|
||||
nextJob[i] = i + 1;
|
||||
}
|
||||
nextJob[started - 1] = 0;
|
||||
|
||||
int prevJob = started - 1;
|
||||
int job = 0;
|
||||
for (;;) {
|
||||
auto next = (Job::continuation)inProgress[job]->next(inProgress[job]);
|
||||
inProgress[job]->next = next;
|
||||
if (next == nullptr) {
|
||||
if (started == count) {
|
||||
if (prevJob == job)
|
||||
break;
|
||||
nextJob[prevJob] = nextJob[job];
|
||||
job = prevJob;
|
||||
} else {
|
||||
int temp = started++;
|
||||
inProgress[job] = jobs[temp];
|
||||
}
|
||||
}
|
||||
prevJob = job;
|
||||
job = nextJob[job];
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef __has_attribute
|
||||
#define __has_attribute(x) 0
|
||||
#endif
|
||||
|
||||
#if __has_attribute(musttail)
|
||||
#define MUSTTAIL __attribute__((musttail))
|
||||
#else
|
||||
#define MUSTTAIL
|
||||
#endif
|
||||
|
||||
struct Context {
|
||||
constexpr static int kConcurrent = 32;
|
||||
Job **jobs;
|
||||
Job *inProgress[kConcurrent];
|
||||
void (*continuation[kConcurrent])(Context *, int64_t prevJob, int64_t job,
|
||||
int64_t started, int64_t count);
|
||||
int nextJob[kConcurrent];
|
||||
};
|
||||
|
||||
void keepGoing(Context *context, int64_t prevJob, int64_t job, int64_t started,
|
||||
int64_t count) {
|
||||
prevJob = job;
|
||||
job = context->nextJob[job];
|
||||
MUSTTAIL return context->continuation[job](context, prevJob, job, started,
|
||||
count);
|
||||
}
|
||||
|
||||
void stepJobTailCall(Context *context, int64_t prevJob, int64_t job,
|
||||
int64_t started, int64_t count);
|
||||
|
||||
void complete(Context *context, int64_t prevJob, int64_t job, int64_t started,
|
||||
int64_t count) {
|
||||
if (started == count) {
|
||||
if (prevJob == job) {
|
||||
return;
|
||||
}
|
||||
context->nextJob[prevJob] = context->nextJob[job];
|
||||
job = prevJob;
|
||||
} else {
|
||||
context->inProgress[job] = context->jobs[started++];
|
||||
context->continuation[job] = stepJobTailCall;
|
||||
}
|
||||
prevJob = job;
|
||||
job = context->nextJob[job];
|
||||
MUSTTAIL return context->continuation[job](context, prevJob, job, started,
|
||||
count);
|
||||
}
|
||||
|
||||
void stepJobTailCall(Context *context, int64_t prevJob, int64_t job,
|
||||
int64_t started, int64_t count) {
|
||||
auto *j = context->inProgress[job];
|
||||
auto done = --(*j->input) == 0;
|
||||
#ifdef __x86_64__
|
||||
_mm_clflush(j->input);
|
||||
#endif
|
||||
if (done) {
|
||||
MUSTTAIL return complete(context, prevJob, job, started, count);
|
||||
} else {
|
||||
context->continuation[job] = stepJobTailCall;
|
||||
MUSTTAIL return keepGoing(context, prevJob, job, started, count);
|
||||
}
|
||||
}
|
||||
|
||||
void useTailCalls(Job **jobs, int count) {
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
Context context;
|
||||
context.jobs = jobs;
|
||||
int64_t started = std::min(Context::kConcurrent, count);
|
||||
for (int i = 0; i < started; i++) {
|
||||
context.inProgress[i] = jobs[i];
|
||||
context.nextJob[i] = i + 1;
|
||||
context.continuation[i] = stepJobTailCall;
|
||||
}
|
||||
context.nextJob[started - 1] = 0;
|
||||
int prevJob = started - 1;
|
||||
int job = 0;
|
||||
return context.continuation[job](&context, prevJob, job, started, count);
|
||||
}
|
||||
|
||||
void interleaveCyclicList(Job **jobs, int count) {
|
||||
auto *nextJob = (int *)alloca(sizeof(int) * count);
|
||||
|
||||
for (int i = 0; i < count - 1; ++i) {
|
||||
nextJob[i] = i + 1;
|
||||
}
|
||||
nextJob[count - 1] = 0;
|
||||
|
||||
int prevJob = count - 1;
|
||||
int job = 0;
|
||||
for (;;) {
|
||||
auto next = (Job::continuation)jobs[job]->next(jobs[job]);
|
||||
jobs[job]->next = next;
|
||||
if (next == nullptr) {
|
||||
if (prevJob == job)
|
||||
break;
|
||||
nextJob[prevJob] = nextJob[job];
|
||||
job = prevJob;
|
||||
}
|
||||
prevJob = job;
|
||||
job = nextJob[job];
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
ankerl::nanobench::Bench bench;
|
||||
|
||||
constexpr int kNumJobs = 10000;
|
||||
bench.relative(true);
|
||||
|
||||
Job jobs[kNumJobs];
|
||||
Job jobsCopy[kNumJobs];
|
||||
int iters = 0;
|
||||
int originalInput[kNumJobs];
|
||||
for (int i = 0; i < kNumJobs; ++i) {
|
||||
originalInput[i] = rand() % 5 + 3;
|
||||
jobs[i].input = new int{originalInput[i]};
|
||||
jobs[i].next = stepJob;
|
||||
iters += *jobs[i].input;
|
||||
}
|
||||
bench.batch(iters);
|
||||
|
||||
for (auto [scheduler, name] :
|
||||
{std::make_pair(sequentialNoFuncPtr, "sequentialNoFuncPtr"),
|
||||
std::make_pair(sequential, "sequential"),
|
||||
std::make_pair(useTailCalls, "useTailCalls"),
|
||||
std::make_pair(interleaveSwapping, "interleavingSwapping"),
|
||||
std::make_pair(interleaveBoundedCyclicList,
|
||||
"interleaveBoundedCyclicList"),
|
||||
std::make_pair(interleaveCyclicList, "interleaveCyclicList")}) {
|
||||
for (int i = 0; i < kNumJobs; ++i) {
|
||||
*jobs[i].input = originalInput[i];
|
||||
}
|
||||
memcpy(jobsCopy, jobs, sizeof(jobs));
|
||||
Job *ps[kNumJobs];
|
||||
for (int i = 0; i < kNumJobs; ++i) {
|
||||
ps[i] = jobsCopy + i;
|
||||
}
|
||||
scheduler(ps, kNumJobs);
|
||||
for (int i = 0; i < kNumJobs; ++i) {
|
||||
if (*jobsCopy[i].input != 0) {
|
||||
fprintf(stderr, "%s failed\n", name);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
bench.run(name, [&]() {
|
||||
for (int i = 0; i < kNumJobs; ++i) {
|
||||
*jobs[i].input = originalInput[i];
|
||||
}
|
||||
memcpy(jobsCopy, jobs, sizeof(jobs));
|
||||
Job *ps[kNumJobs];
|
||||
for (int i = 0; i < kNumJobs; ++i) {
|
||||
ps[i] = jobsCopy + i;
|
||||
}
|
||||
scheduler(ps, kNumJobs);
|
||||
});
|
||||
}
|
||||
for (int i = 0; i < kNumJobs; ++i) {
|
||||
delete jobs[i].input;
|
||||
}
|
||||
}
|
367
Internal.h
367
Internal.h
@@ -20,16 +20,44 @@ using namespace weaselab;
|
||||
#include <thread>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <callgrind.h>
|
||||
|
||||
#define DEBUG_VERBOSE 0
|
||||
#define SHOW_MEMORY 0
|
||||
|
||||
[[nodiscard]] inline auto
|
||||
operator<=>(const std::span<const uint8_t> &lhs,
|
||||
const std::span<const uint8_t> &rhs) noexcept {
|
||||
// std::span is not trivially constructible. We want a span that leaves its
|
||||
// members uninitialized for performance reasons.
|
||||
struct TrivialSpan {
|
||||
TrivialSpan() = default;
|
||||
TrivialSpan(const uint8_t *begin, int len) : begin(begin), len(len) {}
|
||||
|
||||
uint8_t back() const {
|
||||
assert(len > 0);
|
||||
return begin[len - 1];
|
||||
}
|
||||
uint8_t front() const {
|
||||
assert(len > 0);
|
||||
return begin[0];
|
||||
}
|
||||
uint8_t operator[](int i) const {
|
||||
assert(0 <= i);
|
||||
assert(i < len);
|
||||
return begin[i];
|
||||
}
|
||||
int size() const { return len; }
|
||||
TrivialSpan subspan(int offset, int len) { return {begin + offset, len}; }
|
||||
const uint8_t *data() const { return begin; }
|
||||
|
||||
private:
|
||||
const uint8_t *begin;
|
||||
int len;
|
||||
};
|
||||
|
||||
static_assert(std::is_trivial_v<TrivialSpan>);
|
||||
|
||||
[[nodiscard]] inline auto operator<=>(const TrivialSpan &lhs,
|
||||
const TrivialSpan &rhs) noexcept {
|
||||
int cl = std::min<int>(lhs.size(), rhs.size());
|
||||
if (cl > 0) {
|
||||
if (auto c = memcmp(lhs.data(), rhs.data(), cl) <=> 0; c != 0) {
|
||||
@@ -39,7 +67,7 @@ operator<=>(const std::span<const uint8_t> &lhs,
|
||||
return lhs.size() <=> rhs.size();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto operator<=>(const std::span<const uint8_t> &lhs,
|
||||
[[nodiscard]] inline auto operator<=>(const TrivialSpan &lhs,
|
||||
const ConflictSet::Key &rhs) noexcept {
|
||||
int cl = std::min<int>(lhs.size(), rhs.len);
|
||||
if (cl > 0) {
|
||||
@@ -47,7 +75,18 @@ operator<=>(const std::span<const uint8_t> &lhs,
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return lhs.size() <=> size_t(rhs.len);
|
||||
return lhs.size() <=> rhs.len;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline auto operator<=>(const ConflictSet::Key &lhs,
|
||||
const ConflictSet::Key &rhs) noexcept {
|
||||
int cl = std::min<int>(lhs.len, rhs.len);
|
||||
if (cl > 0) {
|
||||
if (auto c = memcmp(lhs.p, rhs.p, cl) <=> 0; c != 0) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return lhs.len <=> rhs.len;
|
||||
}
|
||||
|
||||
// This header contains code that we want to reuse outside of ConflictSet.cpp or
|
||||
@@ -99,8 +138,7 @@ __attribute__((always_inline)) inline void safe_free(void *p, size_t s) {
|
||||
mallocBytesDelta -= s;
|
||||
#if SHOW_MEMORY
|
||||
mallocBytes -= s;
|
||||
free(p);
|
||||
#else
|
||||
#endif
|
||||
#ifndef NDEBUG
|
||||
(char *&)p -= kMallocHeaderSize;
|
||||
size_t expected;
|
||||
@@ -108,7 +146,6 @@ __attribute__((always_inline)) inline void safe_free(void *p, size_t s) {
|
||||
assert(s == expected);
|
||||
#endif
|
||||
free(p);
|
||||
#endif
|
||||
}
|
||||
|
||||
// ==================== BEGIN ARENA IMPL ====================
|
||||
@@ -257,10 +294,75 @@ template <class T> struct ArenaAlloc {
|
||||
void deallocate(T *, size_t) noexcept {}
|
||||
};
|
||||
|
||||
template <class T> using Vector = std::vector<T, ArenaAlloc<T>>;
|
||||
template <class T> auto vector(Arena &arena) {
|
||||
return Vector<T>(ArenaAlloc<T>(&arena));
|
||||
}
|
||||
template <class T> struct Vector {
|
||||
static_assert(std::is_trivially_destructible_v<T>);
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
|
||||
explicit Vector(Arena *arena)
|
||||
: arena(arena), t(nullptr), size_(0), capacity(0) {}
|
||||
|
||||
void append(std::span<const T> slice) {
|
||||
if (size_ + int(slice.size()) > capacity) {
|
||||
grow(std::max<int>(size_ + slice.size(), capacity * 2));
|
||||
}
|
||||
if (slice.size() > 0) {
|
||||
memcpy(const_cast<std::remove_const_t<T> *>(t) + size_, slice.data(),
|
||||
slice.size() * sizeof(T));
|
||||
}
|
||||
size_ += slice.size();
|
||||
}
|
||||
|
||||
// Caller must write to the returned slice
|
||||
std::span<T> unsafePrepareAppend(int appendSize) {
|
||||
if (size_ + appendSize > capacity) {
|
||||
grow(std::max<int>(size_ + appendSize, capacity * 2));
|
||||
}
|
||||
auto result = std::span<T>(t + size_, appendSize);
|
||||
size_ += appendSize;
|
||||
return result;
|
||||
}
|
||||
|
||||
void push_back(const T &t) { append(std::span<const T>(&t, 1)); }
|
||||
|
||||
T *begin() { return t; }
|
||||
T *end() { return t + size_; }
|
||||
T *data() { return t; }
|
||||
T &back() {
|
||||
assert(size_ > 0);
|
||||
return t[size_ - 1];
|
||||
}
|
||||
T &operator[](int i) {
|
||||
assert(i >= 0 && i < size_);
|
||||
return t[i];
|
||||
}
|
||||
|
||||
void pop_back() {
|
||||
assert(size_ > 0);
|
||||
--size_;
|
||||
}
|
||||
|
||||
int size() const { return size_; }
|
||||
|
||||
operator std::span<const T>() const { return std::span(t, size_); }
|
||||
|
||||
private:
|
||||
void grow(int newCapacity) {
|
||||
capacity = newCapacity;
|
||||
auto old = std::span<const T>(*this);
|
||||
t = (T *)new (std::align_val_t(alignof(T)), *arena)
|
||||
uint8_t[capacity * sizeof(T)];
|
||||
size_ = 0;
|
||||
append(old);
|
||||
}
|
||||
|
||||
Arena *arena;
|
||||
T *t;
|
||||
int size_;
|
||||
int capacity;
|
||||
};
|
||||
|
||||
template <class T> auto vector(Arena &arena) { return Vector<T>(&arena); }
|
||||
|
||||
template <class T, class C> using Set = std::set<T, C, ArenaAlloc<T>>;
|
||||
template <class T, class C = std::less<T>> auto set(Arena &arena) {
|
||||
return Set<T, C>(ArenaAlloc<T>(&arena));
|
||||
@@ -414,13 +516,15 @@ inline uint32_t Arbitrary::bounded(uint32_t s) {
|
||||
// ==================== END ARBITRARY IMPL ====================
|
||||
|
||||
struct ReferenceImpl {
|
||||
explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) {
|
||||
explicit ReferenceImpl(int64_t oldestVersion)
|
||||
: oldestVersion(oldestVersion), newestVersion(oldestVersion) {
|
||||
writeVersionMap[""] = oldestVersion;
|
||||
}
|
||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||
int count) const {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (reads[i].readVersion < oldestVersion) {
|
||||
if (reads[i].readVersion < oldestVersion ||
|
||||
reads[i].readVersion < newestVersion - 2e9) {
|
||||
results[i] = ConflictSet::TooOld;
|
||||
continue;
|
||||
}
|
||||
@@ -442,6 +546,8 @@ struct ReferenceImpl {
|
||||
}
|
||||
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
assert(writeVersion >= newestVersion);
|
||||
newestVersion = writeVersion;
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto begin =
|
||||
std::string((const char *)writes[i].begin.p, writes[i].begin.len);
|
||||
@@ -461,17 +567,18 @@ struct ReferenceImpl {
|
||||
}
|
||||
|
||||
void setOldestVersion(int64_t oldestVersion) {
|
||||
assert(oldestVersion >= oldestVersion);
|
||||
assert(oldestVersion >= this->oldestVersion);
|
||||
this->oldestVersion = oldestVersion;
|
||||
}
|
||||
|
||||
int64_t oldestVersion;
|
||||
int64_t newestVersion;
|
||||
std::map<std::string, int64_t> writeVersionMap;
|
||||
};
|
||||
|
||||
using Key = ConflictSet::Key;
|
||||
|
||||
inline Key operator"" _s(const char *str, size_t size) {
|
||||
inline Key operator""_s(const char *str, size_t size) {
|
||||
return {reinterpret_cast<const uint8_t *>(str), int(size)};
|
||||
}
|
||||
|
||||
@@ -502,7 +609,7 @@ inline std::string printable(const Key &key) {
|
||||
return printable(std::string_view((const char *)key.p, key.len));
|
||||
}
|
||||
|
||||
inline std::string printable(std::span<const uint8_t> key) {
|
||||
inline std::string printable(TrivialSpan key) {
|
||||
return printable(std::string_view((const char *)key.data(), key.size()));
|
||||
}
|
||||
|
||||
@@ -520,41 +627,52 @@ inline const char *resultToStr(ConflictSet::Result r) {
|
||||
|
||||
namespace {
|
||||
|
||||
template <class ConflictSetImpl> struct TestDriver {
|
||||
Arbitrary arbitrary;
|
||||
explicit TestDriver(const uint8_t *data, size_t size)
|
||||
: arbitrary({data, size}) {}
|
||||
template <class ConflictSetImpl, bool kEnableAssertions = true>
|
||||
struct TestDriver {
|
||||
Arbitrary *arbitrary;
|
||||
explicit TestDriver(Arbitrary &a) : arbitrary(&a) {
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
fprintf(stderr, "%p Initial version: {%" PRId64 "}\n", this, writeVersion);
|
||||
#endif
|
||||
}
|
||||
|
||||
int64_t writeVersion = 0;
|
||||
int64_t oldestVersion = 0;
|
||||
int64_t oldestVersion = arbitrary->next();
|
||||
int64_t writeVersion = oldestVersion;
|
||||
ConflictSetImpl cs{oldestVersion};
|
||||
ReferenceImpl refImpl{oldestVersion};
|
||||
|
||||
constexpr static auto kMaxKeyLen = 8;
|
||||
constexpr static auto kMaxKeySuffixLen = 8;
|
||||
|
||||
bool ok = true;
|
||||
|
||||
const int prefixLen = arbitrary->bounded(512);
|
||||
const int prefixByte = arbitrary->randT<uint8_t>();
|
||||
|
||||
// Call until it returns true, for "done". Check internal invariants etc
|
||||
// between calls to next.
|
||||
bool next() {
|
||||
if (!arbitrary.hasEntropy()) {
|
||||
assert(cs.getBytes() >= 0);
|
||||
if (!arbitrary->hasEntropy()) {
|
||||
return true;
|
||||
}
|
||||
Arena arena;
|
||||
{
|
||||
int numPointWrites = arbitrary.bounded(100);
|
||||
int numRangeWrites = arbitrary.bounded(100);
|
||||
int64_t v = ++writeVersion;
|
||||
int numPointWrites = arbitrary->bounded(100);
|
||||
int numRangeWrites = arbitrary->bounded(100);
|
||||
int64_t v =
|
||||
(writeVersion +=
|
||||
arbitrary->bounded(10) ? arbitrary->bounded(10) : arbitrary->next());
|
||||
auto *writes =
|
||||
new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites];
|
||||
auto keys = set<std::string_view>(arena);
|
||||
while (int(keys.size()) < numPointWrites + numRangeWrites * 2) {
|
||||
if (!arbitrary.hasEntropy()) {
|
||||
if (!arbitrary->hasEntropy()) {
|
||||
return true;
|
||||
}
|
||||
int keyLen = arbitrary.bounded(kMaxKeyLen);
|
||||
int keyLen = prefixLen + arbitrary->bounded(kMaxKeySuffixLen);
|
||||
auto *begin = new (arena) uint8_t[keyLen];
|
||||
arbitrary.randomBytes(begin, keyLen);
|
||||
memset(begin, prefixByte, prefixLen);
|
||||
arbitrary->randomBytes(begin + prefixLen, keyLen - prefixLen);
|
||||
keys.insert(std::string_view((const char *)begin, keyLen));
|
||||
}
|
||||
|
||||
@@ -564,7 +682,7 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
rangesRemaining = numRangeWrites;
|
||||
pointsRemaining > 0 || rangesRemaining > 0; ++i) {
|
||||
bool pointRead = pointsRemaining > 0 && rangesRemaining > 0
|
||||
? bool(arbitrary.bounded(2))
|
||||
? bool(arbitrary->bounded(2))
|
||||
: pointsRemaining > 0;
|
||||
if (pointRead) {
|
||||
assert(pointsRemaining > 0);
|
||||
@@ -583,45 +701,121 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
++iter;
|
||||
--rangesRemaining;
|
||||
}
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
if (writes[i].end.len == 0) {
|
||||
fprintf(stderr, "Write: {%s} -> %" PRId64 "\n",
|
||||
printable(writes[i].begin).c_str(), writeVersion);
|
||||
} else {
|
||||
fprintf(stderr, "Write: [%s, %s) -> %" PRId64 "\n",
|
||||
printable(writes[i].begin).c_str(),
|
||||
printable(writes[i].end).c_str(), writeVersion);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
assert(iter == keys.end());
|
||||
assert(i == numPointWrites + numRangeWrites);
|
||||
|
||||
// Test non-canonical writes
|
||||
if (numPointWrites > 0) {
|
||||
int overlaps = arbitrary->bounded(numPointWrites);
|
||||
for (int i = 0; i < numPointWrites + numRangeWrites && overlaps > 0;
|
||||
++i) {
|
||||
if (writes[i].end.len == 0) {
|
||||
int keyLen = prefixLen + arbitrary->bounded(kMaxKeySuffixLen);
|
||||
auto *begin = new (arena) uint8_t[keyLen];
|
||||
memset(begin, prefixByte, prefixLen);
|
||||
arbitrary->randomBytes(begin + prefixLen, keyLen - prefixLen);
|
||||
writes[i].end.len = keyLen;
|
||||
writes[i].end.p = begin;
|
||||
auto c = TrivialSpan(writes[i].begin.p, writes[i].begin.len) <=>
|
||||
TrivialSpan(writes[i].end.p, writes[i].end.len);
|
||||
if (c > 0) {
|
||||
using std::swap;
|
||||
swap(writes[i].begin, writes[i].end);
|
||||
} else if (c == 0) {
|
||||
// It's a point write after all, I guess
|
||||
writes[i].end.len = 0;
|
||||
}
|
||||
--overlaps;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (arbitrary->bounded(2)) {
|
||||
// Shuffle writes
|
||||
for (int i = numPointWrites + numRangeWrites - 1; i > 0; --i) {
|
||||
int j = arbitrary->bounded(i + 1);
|
||||
if (i != j) {
|
||||
using std::swap;
|
||||
swap(writes[i], writes[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oldestVersion +=
|
||||
arbitrary->bounded(10) ? arbitrary->bounded(10) : arbitrary->next();
|
||||
oldestVersion = std::min(oldestVersion, writeVersion);
|
||||
|
||||
#ifdef THREAD_TEST
|
||||
std::latch ready{1};
|
||||
std::thread thread2{[&]() {
|
||||
ready.count_down();
|
||||
ConflictSet::MetricsV1 *m;
|
||||
int count;
|
||||
cs.getMetricsV1(&m, &count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
m[i].getValue();
|
||||
}
|
||||
}};
|
||||
ready.wait();
|
||||
#endif
|
||||
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
for (int i = 0; i < numPointWrites + numRangeWrites; ++i) {
|
||||
if (writes[i].end.len == 0) {
|
||||
fprintf(stderr, "%p Write: {%s}\n", this,
|
||||
printable(writes[i].begin).c_str());
|
||||
} else {
|
||||
fprintf(stderr, "%p Write: [%s, %s)\n", this,
|
||||
printable(writes[i].begin).c_str(),
|
||||
printable(writes[i].end).c_str());
|
||||
}
|
||||
}
|
||||
fprintf(stderr, "%p Write @ %" PRId64 "\n", this, v);
|
||||
#endif
|
||||
|
||||
CALLGRIND_START_INSTRUMENTATION;
|
||||
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||
CALLGRIND_STOP_INSTRUMENTATION;
|
||||
|
||||
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||
if constexpr (kEnableAssertions) {
|
||||
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||
}
|
||||
|
||||
oldestVersion = std::max<int64_t>(writeVersion - arbitrary.bounded(10),
|
||||
oldestVersion);
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
fprintf(stderr, "%p Set oldest version: %" PRId64 "\n", this,
|
||||
oldestVersion);
|
||||
#endif
|
||||
CALLGRIND_START_INSTRUMENTATION;
|
||||
cs.setOldestVersion(oldestVersion);
|
||||
refImpl.setOldestVersion(oldestVersion);
|
||||
CALLGRIND_STOP_INSTRUMENTATION;
|
||||
|
||||
if constexpr (kEnableAssertions) {
|
||||
refImpl.setOldestVersion(oldestVersion);
|
||||
}
|
||||
|
||||
#ifdef THREAD_TEST
|
||||
thread2.join();
|
||||
#endif
|
||||
}
|
||||
{
|
||||
int numPointReads = arbitrary.bounded(100);
|
||||
int numRangeReads = arbitrary.bounded(100);
|
||||
int64_t v = std::max<int64_t>(writeVersion - arbitrary.bounded(10), 0);
|
||||
int numPointReads = arbitrary->bounded(100);
|
||||
int numRangeReads = arbitrary->bounded(100);
|
||||
|
||||
int64_t v = std::max<int64_t>(writeVersion - (arbitrary->bounded(10)
|
||||
? arbitrary->bounded(10)
|
||||
: arbitrary->next()),
|
||||
0);
|
||||
auto *reads =
|
||||
new (arena) ConflictSet::ReadRange[numPointReads + numRangeReads];
|
||||
auto keys = set<std::string_view>(arena);
|
||||
while (int(keys.size()) < numPointReads + numRangeReads * 2) {
|
||||
if (!arbitrary.hasEntropy()) {
|
||||
if (!arbitrary->hasEntropy()) {
|
||||
return true;
|
||||
}
|
||||
int keyLen = arbitrary.bounded(kMaxKeyLen);
|
||||
int keyLen = prefixLen + arbitrary->bounded(kMaxKeySuffixLen);
|
||||
auto *begin = new (arena) uint8_t[keyLen];
|
||||
arbitrary.randomBytes(begin, keyLen);
|
||||
memset(begin, prefixByte, prefixLen);
|
||||
arbitrary->randomBytes(begin + prefixLen, keyLen - prefixLen);
|
||||
keys.insert(std::string_view((const char *)begin, keyLen));
|
||||
}
|
||||
|
||||
@@ -630,7 +824,7 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
for (int pointsRemaining = numPointReads, rangesRemaining = numRangeReads;
|
||||
pointsRemaining > 0 || rangesRemaining > 0; ++i) {
|
||||
bool pointRead = pointsRemaining > 0 && rangesRemaining > 0
|
||||
? bool(arbitrary.bounded(2))
|
||||
? bool(arbitrary->bounded(2))
|
||||
: pointsRemaining > 0;
|
||||
if (pointRead) {
|
||||
assert(pointsRemaining > 0);
|
||||
@@ -652,12 +846,12 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
reads[i].readVersion = v;
|
||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||
if (reads[i].end.len == 0) {
|
||||
fprintf(stderr, "Read: {%s} @ %d\n",
|
||||
printable(reads[i].begin).c_str(), int(reads[i].readVersion));
|
||||
fprintf(stderr, "%p Read: {%s} @ %" PRId64 "\n", this,
|
||||
printable(reads[i].begin).c_str(), reads[i].readVersion);
|
||||
} else {
|
||||
fprintf(stderr, "Read: [%s, %s) @ %d\n",
|
||||
fprintf(stderr, "%p Read: [%s, %s) @ %" PRId64 "\n", this,
|
||||
printable(reads[i].begin).c_str(),
|
||||
printable(reads[i].end).c_str(), int(reads[i].readVersion));
|
||||
printable(reads[i].end).c_str(), reads[i].readVersion);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -674,7 +868,15 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
std::latch ready{1};
|
||||
std::thread thread2{[&]() {
|
||||
ready.count_down();
|
||||
// Call all const methods
|
||||
cs.check(reads, results3, numPointReads + numRangeReads);
|
||||
cs.getBytes();
|
||||
ConflictSet::MetricsV1 *m;
|
||||
int count;
|
||||
cs.getMetricsV1(&m, &count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
m[i].getValue();
|
||||
}
|
||||
}};
|
||||
ready.wait();
|
||||
#endif
|
||||
@@ -683,26 +885,40 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
cs.check(reads, results1, numPointReads + numRangeReads);
|
||||
CALLGRIND_STOP_INSTRUMENTATION;
|
||||
|
||||
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
||||
if constexpr (kEnableAssertions) {
|
||||
// Call remaining const methods
|
||||
cs.getBytes();
|
||||
ConflictSet::MetricsV1 *m;
|
||||
int count;
|
||||
cs.getMetricsV1(&m, &count);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
m[i].getValue();
|
||||
}
|
||||
|
||||
auto compareResults = [reads](ConflictSet::Result *results1,
|
||||
ConflictSet::Result *results2, int count) {
|
||||
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
||||
}
|
||||
|
||||
auto compareResults = [reads, this](ConflictSet::Result *results1,
|
||||
ConflictSet::Result *results2,
|
||||
int count) {
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (results1[i] != results2[i]) {
|
||||
if (reads[i].end.len == 0) {
|
||||
fprintf(stderr,
|
||||
"Expected %s, got %s for read of {%s} at version %" PRId64
|
||||
"\n",
|
||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
||||
printable(reads[i].begin).c_str(), reads[i].readVersion);
|
||||
} else {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Expected %s, got %s for read of [%s, %s) at version %" PRId64
|
||||
"%p Expected %s, got %s for read of {%s} at version %" PRId64
|
||||
"\n",
|
||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
||||
printable(reads[i].begin).c_str(),
|
||||
printable(reads[i].end).c_str(), reads[i].readVersion);
|
||||
(void *)this, resultToStr(results2[i]),
|
||||
resultToStr(results1[i]), printable(reads[i].begin).c_str(),
|
||||
reads[i].readVersion);
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"%p Expected %s, got %s for read of [%s, %s) at version "
|
||||
"%" PRId64 "\n",
|
||||
(void *)this, resultToStr(results2[i]),
|
||||
resultToStr(results1[i]),
|
||||
printable(reads[i].begin).c_str(),
|
||||
printable(reads[i].end).c_str(), reads[i].readVersion);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -710,9 +926,12 @@ template <class ConflictSetImpl> struct TestDriver {
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!compareResults(results1, results2, numPointReads + numRangeReads)) {
|
||||
ok = false;
|
||||
return true;
|
||||
if constexpr (kEnableAssertions) {
|
||||
if (!compareResults(results1, results2,
|
||||
numPointReads + numRangeReads)) {
|
||||
ok = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef THREAD_TEST
|
||||
|
57
Jenkinsfile
vendored
57
Jenkinsfile
vendored
@@ -11,11 +11,11 @@ def CleanBuildAndTest(String cmakeArgs) {
|
||||
catchError {
|
||||
sh '''
|
||||
cd build
|
||||
ctest --no-compress-output --test-output-size-passed 100000 --test-output-size-failed 100000 -T Test -j `nproc` --timeout 90
|
||||
ctest --no-compress-output --test-output-size-passed 100000 --test-output-size-failed 100000 -T Test -j `nproc` --timeout 90 > /dev/null
|
||||
zstd Testing/*/Test.xml
|
||||
'''
|
||||
}
|
||||
xunit tools: [CTest(pattern: 'build/Testing/*/Test.xml')], reduceLog: false, skipPublishingChecks: false
|
||||
xunit tools: [CTest(pattern: 'build/Testing/*/Test.xml')], skipPublishingChecks: false
|
||||
minio bucket: 'jenkins', credentialsId: 'jenkins-minio', excludes: '', host: 'minio.weaselab.dev', includes: 'build/Testing/*/Test.xml.zst', targetFolder: '${JOB_NAME}/${BUILD_NUMBER}/${STAGE_NAME}/'
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ pipeline {
|
||||
sh 'pre-commit run --all-files --show-diff-on-failure'
|
||||
}
|
||||
}
|
||||
stage('Clang') {
|
||||
stage('64 bit versions') {
|
||||
agent {
|
||||
dockerfile {
|
||||
args '-v /home/jenkins/ccache:/ccache'
|
||||
@@ -44,8 +44,18 @@ pipeline {
|
||||
}
|
||||
}
|
||||
steps {
|
||||
CleanBuildAndTest("")
|
||||
recordIssues(tools: [clang()])
|
||||
CleanBuildAndTest("-DCMAKE_CXX_FLAGS=-DUSE_64_BIT=1")
|
||||
}
|
||||
}
|
||||
stage('Debug') {
|
||||
agent {
|
||||
dockerfile {
|
||||
args '-v /home/jenkins/ccache:/ccache'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
CleanBuildAndTest("-DCMAKE_BUILD_TYPE=Debug")
|
||||
}
|
||||
}
|
||||
stage('SIMD fallback') {
|
||||
@@ -59,7 +69,7 @@ pipeline {
|
||||
CleanBuildAndTest("-DUSE_SIMD_FALLBACK=ON")
|
||||
}
|
||||
}
|
||||
stage('Release [gcc]') {
|
||||
stage('Release [clang]') {
|
||||
agent {
|
||||
dockerfile {
|
||||
args '-v /home/jenkins/ccache:/ccache'
|
||||
@@ -67,8 +77,8 @@ pipeline {
|
||||
}
|
||||
}
|
||||
steps {
|
||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
||||
recordIssues(tools: [gcc()])
|
||||
CleanBuildAndTest("-DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
||||
recordIssues(tools: [clang()])
|
||||
sh '''
|
||||
cd build
|
||||
cpack -G DEB
|
||||
@@ -81,7 +91,19 @@ pipeline {
|
||||
minio bucket: 'jenkins', credentialsId: 'jenkins-minio', excludes: '', host: 'minio.weaselab.dev', includes: 'build/*.deb,build/*.rpm,paper/*.pdf', targetFolder: '${JOB_NAME}/${BUILD_NUMBER}/${STAGE_NAME}/'
|
||||
}
|
||||
}
|
||||
stage('Release [gcc,aarch64]') {
|
||||
stage('gcc') {
|
||||
agent {
|
||||
dockerfile {
|
||||
args '-v /home/jenkins/ccache:/ccache'
|
||||
reuseNode true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++")
|
||||
recordIssues(tools: [gcc()])
|
||||
}
|
||||
}
|
||||
stage('Release [clang,aarch64]') {
|
||||
agent {
|
||||
dockerfile {
|
||||
args '-v /home/jenkins/ccache:/ccache'
|
||||
@@ -106,11 +128,18 @@ pipeline {
|
||||
}
|
||||
}
|
||||
steps {
|
||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug")
|
||||
sh '''
|
||||
gcovr -f ConflictSet.cpp --cobertura > build/coverage.xml
|
||||
'''
|
||||
cobertura autoUpdateHealth: false, autoUpdateStability: false, coberturaReportFile: 'build/coverage.xml', conditionalCoverageTargets: '70, 0, 0', failUnhealthy: false, failUnstable: false, lineCoverageTargets: '80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets: '80, 0, 0', onlyStable: false, sourceEncoding: 'ASCII', zoomCoverageChart: false
|
||||
script {
|
||||
gcov_args = "-f ConflictSet.cpp -f LongestCommonPrefix.h -f Metrics.h --gcov-executable 'llvm-cov gcov' --exclude-noncode-lines"
|
||||
}
|
||||
CleanBuildAndTest("-DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON")
|
||||
sh """
|
||||
gcovr ${gcov_args} --cobertura > build/coverage.xml
|
||||
"""
|
||||
recordCoverage qualityGates: [[criticality: 'NOTE', metric: 'MODULE']], tools: [[parser: 'COBERTURA', pattern: 'build/coverage.xml']]
|
||||
sh """
|
||||
gcovr ${gcov_args}
|
||||
gcovr ${gcov_args} --fail-under-line 100 > /dev/null
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
185
LongestCommonPrefix.h
Normal file
185
LongestCommonPrefix.h
Normal file
@@ -0,0 +1,185 @@
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <bit>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef HAS_AVX
|
||||
#include <immintrin.h>
|
||||
#elif HAS_ARM_NEON
|
||||
#include <arm_neon.h>
|
||||
#endif
|
||||
|
||||
#ifndef __SANITIZE_THREAD__
|
||||
#if defined(__has_feature)
|
||||
#if __has_feature(thread_sanitizer)
|
||||
#define __SANITIZE_THREAD__
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(HAS_AVX) || defined(HAS_ARM_NEON)
|
||||
constexpr int kStride = 64;
|
||||
#else
|
||||
constexpr int kStride = 16;
|
||||
#endif
|
||||
|
||||
constexpr int kUnrollFactor = 4;
|
||||
|
||||
inline bool compareStride(const uint8_t *ap, const uint8_t *bp) {
|
||||
#if defined(HAS_ARM_NEON)
|
||||
static_assert(kStride == 64);
|
||||
uint8x16_t x[4]; // GCOVR_EXCL_LINE
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
x[i] = vceqq_u8(vld1q_u8(ap + i * 16), vld1q_u8(bp + i * 16));
|
||||
}
|
||||
auto results = vreinterpretq_u16_u8(
|
||||
vandq_u8(vandq_u8(x[0], x[1]), vandq_u8(x[2], x[3])));
|
||||
bool eq = vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(results, 4)), 0) ==
|
||||
uint64_t(-1);
|
||||
#elif defined(HAS_AVX)
|
||||
static_assert(kStride == 64);
|
||||
__m128i x[4]; // GCOVR_EXCL_LINE
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
x[i] = _mm_cmpeq_epi8(_mm_loadu_si128((__m128i *)(ap + i * 16)),
|
||||
_mm_loadu_si128((__m128i *)(bp + i * 16)));
|
||||
}
|
||||
auto eq =
|
||||
_mm_movemask_epi8(_mm_and_si128(_mm_and_si128(x[0], x[1]),
|
||||
_mm_and_si128(x[2], x[3]))) == 0xffff;
|
||||
#else
|
||||
// Hope it gets vectorized
|
||||
auto eq = memcmp(ap, bp, kStride) == 0;
|
||||
#endif
|
||||
return eq;
|
||||
}
|
||||
|
||||
// Precondition: ap[:kStride] != bp[:kStride]
|
||||
inline int firstNeqStride(const uint8_t *ap, const uint8_t *bp) {
|
||||
#if defined(HAS_AVX)
|
||||
static_assert(kStride == 64);
|
||||
uint64_t c[kStride / 16]; // GCOVR_EXCL_LINE
|
||||
for (int i = 0; i < kStride; i += 16) {
|
||||
const auto a = _mm_loadu_si128((__m128i *)(ap + i));
|
||||
const auto b = _mm_loadu_si128((__m128i *)(bp + i));
|
||||
const auto compared = _mm_cmpeq_epi8(a, b);
|
||||
c[i / 16] = _mm_movemask_epi8(compared) & 0xffff;
|
||||
}
|
||||
return std::countr_zero(~(c[0] | c[1] << 16 | c[2] << 32 | c[3] << 48));
|
||||
#elif defined(HAS_ARM_NEON)
|
||||
static_assert(kStride == 64);
|
||||
for (int i = 0; i < kStride; i += 16) {
|
||||
// 0xff for each match
|
||||
uint16x8_t results =
|
||||
vreinterpretq_u16_u8(vceqq_u8(vld1q_u8(ap + i), vld1q_u8(bp + i)));
|
||||
// 0xf for each mismatch
|
||||
uint64_t bitfield =
|
||||
~vget_lane_u64(vreinterpret_u64_u8(vshrn_n_u16(results, 4)), 0);
|
||||
if (bitfield) {
|
||||
return i + (std::countr_zero(bitfield) >> 2);
|
||||
}
|
||||
}
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
#else
|
||||
int i = 0;
|
||||
for (; i < kStride - 1; ++i) {
|
||||
if (*ap++ != *bp++) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
#endif
|
||||
}
|
||||
|
||||
// This gets covered in local development
|
||||
// GCOVR_EXCL_START
|
||||
#if defined(HAS_AVX) && !defined(__SANITIZE_THREAD__)
|
||||
__attribute__((target("avx512f,avx512bw"))) inline int
|
||||
longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) {
|
||||
int i = 0;
|
||||
int end = cl & ~63;
|
||||
while (i < end) {
|
||||
const uint64_t eq =
|
||||
_mm512_cmpeq_epi8_mask(_mm512_loadu_epi8(ap), _mm512_loadu_epi8(bp));
|
||||
if (eq != uint64_t(-1)) {
|
||||
return i + std::countr_one(eq);
|
||||
}
|
||||
i += 64;
|
||||
ap += 64;
|
||||
bp += 64;
|
||||
}
|
||||
if (i < cl) {
|
||||
const uint64_t mask = (uint64_t(1) << (cl - i)) - 1;
|
||||
const uint64_t eq = _mm512_cmpeq_epi8_mask(
|
||||
_mm512_maskz_loadu_epi8(mask, ap), _mm512_maskz_loadu_epi8(mask, bp));
|
||||
return i + std::countr_one(eq & mask);
|
||||
}
|
||||
assert(i == cl);
|
||||
return i;
|
||||
}
|
||||
__attribute__((target("default")))
|
||||
#endif
|
||||
// GCOVR_EXCL_STOP
|
||||
|
||||
inline int
|
||||
longestCommonPrefix(const uint8_t *ap, const uint8_t *bp, int cl) {
|
||||
if (!(cl >= 0)) {
|
||||
__builtin_unreachable(); // GCOVR_EXCL_LINE
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
int end; // GCOVR_EXCL_LINE
|
||||
|
||||
// kStride * kUnrollCount at a time
|
||||
end = cl & ~(kStride * kUnrollFactor - 1);
|
||||
while (i < end) {
|
||||
for (int j = 0; j < kUnrollFactor; ++j) {
|
||||
if (!compareStride(ap, bp)) {
|
||||
return i + firstNeqStride(ap, bp);
|
||||
}
|
||||
i += kStride;
|
||||
ap += kStride;
|
||||
bp += kStride;
|
||||
}
|
||||
}
|
||||
|
||||
// kStride at a time
|
||||
end = cl & ~(kStride - 1);
|
||||
while (i < end) {
|
||||
if (!compareStride(ap, bp)) {
|
||||
return i + firstNeqStride(ap, bp);
|
||||
}
|
||||
i += kStride;
|
||||
ap += kStride;
|
||||
bp += kStride;
|
||||
}
|
||||
|
||||
// word at a time
|
||||
end = cl & ~(sizeof(uint64_t) - 1);
|
||||
while (i < end) {
|
||||
uint64_t a; // GCOVR_EXCL_LINE
|
||||
uint64_t b; // GCOVR_EXCL_LINE
|
||||
memcpy(&a, ap, 8);
|
||||
memcpy(&b, bp, 8);
|
||||
const auto mismatched = a ^ b;
|
||||
if (mismatched) {
|
||||
return i + std::countr_zero(mismatched) / 8;
|
||||
}
|
||||
i += 8;
|
||||
ap += 8;
|
||||
bp += 8;
|
||||
}
|
||||
|
||||
// byte at a time
|
||||
while (i < cl) {
|
||||
if (*ap != *bp) {
|
||||
break;
|
||||
}
|
||||
++ap;
|
||||
++bp;
|
||||
++i;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
64
Metrics.h
Normal file
64
Metrics.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "ConflictSet.h"
|
||||
#include "Internal.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <atomic>
|
||||
#include <tuple>
|
||||
|
||||
struct Metric {
|
||||
Metric *prev;
|
||||
const char *name;
|
||||
const char *help;
|
||||
weaselab::ConflictSet::MetricsV1::Type type;
|
||||
std::atomic<int64_t> value;
|
||||
|
||||
protected:
|
||||
Metric(Metric *&metricList, int &metricsCount, const char *name,
|
||||
const char *help, weaselab::ConflictSet::MetricsV1::Type type)
|
||||
: prev(std::exchange(metricList, this)), name(name), help(help),
|
||||
type(type), value(0) {
|
||||
++metricsCount;
|
||||
}
|
||||
};
|
||||
|
||||
struct Gauge : private Metric {
|
||||
Gauge(Metric *&metricList, int &metricsCount, const char *name,
|
||||
const char *help)
|
||||
: Metric(metricList, metricsCount, name, help,
|
||||
weaselab::ConflictSet::MetricsV1::Gauge) {}
|
||||
|
||||
void set(int64_t value) {
|
||||
this->value.store(value, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
struct Counter : private Metric {
|
||||
Counter(Metric *&metricList, int &metricsCount, const char *name,
|
||||
const char *help)
|
||||
: Metric(metricList, metricsCount, name, help,
|
||||
weaselab::ConflictSet::MetricsV1::Counter) {}
|
||||
// Expensive. Accumulate locally and then call add instead of repeatedly
|
||||
// calling add.
|
||||
void add(int64_t value) {
|
||||
assert(value >= 0);
|
||||
static_assert(std::atomic<int64_t>::is_always_lock_free);
|
||||
this->value.fetch_add(value, std::memory_order_relaxed);
|
||||
}
|
||||
};
|
||||
|
||||
inline weaselab::ConflictSet::MetricsV1 *initMetrics(Metric *metricsList,
|
||||
int metricsCount) {
|
||||
weaselab::ConflictSet::MetricsV1 *metrics =
|
||||
(weaselab::ConflictSet::MetricsV1 *)safe_malloc(metricsCount *
|
||||
sizeof(metrics[0]));
|
||||
for (auto [i, m] = std::make_tuple(metricsCount - 1, metricsList); i >= 0;
|
||||
--i, m = m->prev) {
|
||||
metrics[i].name = m->name;
|
||||
metrics[i].help = m->help;
|
||||
metrics[i].p = m;
|
||||
metrics[i].type = m->type;
|
||||
}
|
||||
return metrics;
|
||||
}
|
104
README.md
104
README.md
@@ -1,84 +1,40 @@
|
||||
A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys.
|
||||
|
||||
Intended to replace FoundationDB's skip list.
|
||||
Intended as an alternative to FoundationDB's skip list.
|
||||
|
||||
Hardware for all benchmarks is a mac m1 2020.
|
||||
Hardware for all benchmarks is an AMD Ryzen 9 7900 with (2x32GB) 5600MT/s CL28-34-34-89 1.35V RAM.
|
||||
|
||||
# FoundationDB's benchmark
|
||||
Compiler is `Ubuntu clang version 20.0.0 (++20241029082144+7544d3af0e28-1~exp1~20241029082307.506)`.
|
||||
|
||||
# Microbenchmark
|
||||
|
||||
## Skip list
|
||||
|
||||
```
|
||||
New conflict set: 1.957 sec
|
||||
0.639 Mtransactions/sec
|
||||
2.555 Mkeys/sec
|
||||
Detect only: 1.845 sec
|
||||
0.678 Mtransactions/sec
|
||||
2.710 Mkeys/sec
|
||||
Skiplist only: 1.263 sec
|
||||
0.990 Mtransactions/sec
|
||||
3.960 Mkeys/sec
|
||||
Performance counters:
|
||||
Build: 0.0546
|
||||
Add: 0.0563
|
||||
Detect: 1.84
|
||||
D.Sort: 0.412
|
||||
D.Combine: 0.0141
|
||||
D.CheckRead: 0.671
|
||||
D.CheckIntraBatch: 0.0068
|
||||
D.MergeWrite: 0.592
|
||||
D.RemoveBefore: 0.146
|
||||
```
|
||||
| ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|
||||
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
||||
| 159.65 | 6,263,576.52 | 1.6% | 2,972.36 | 820.37 | 3.623 | 504.59 | 0.0% | 0.01 | `point reads`
|
||||
| 156.32 | 6,397,320.65 | 0.7% | 2,913.62 | 806.87 | 3.611 | 490.19 | 0.0% | 0.01 | `prefix reads`
|
||||
| 229.18 | 4,363,293.65 | 1.2% | 3,541.05 | 1,219.75 | 2.903 | 629.33 | 0.0% | 0.01 | `range reads`
|
||||
| 363.37 | 2,752,026.30 | 0.3% | 5,273.63 | 1,951.54 | 2.702 | 851.66 | 1.7% | 0.01 | `point writes`
|
||||
| 364.99 | 2,739,787.02 | 0.3% | 5,250.92 | 1,958.54 | 2.681 | 839.24 | 1.7% | 0.01 | `prefix writes`
|
||||
| 242.26 | 4,127,796.58 | 2.9% | 3,117.33 | 1,304.41 | 2.390 | 541.07 | 2.8% | 0.02 | `range writes`
|
||||
| 562.48 | 1,777,855.27 | 0.8% | 7,305.21 | 3,034.34 | 2.408 | 1,329.30 | 1.3% | 0.01 | `monotonic increasing point writes`
|
||||
| 122,688.57 | 8,150.72 | 0.7% | 798,766.00 | 666,842.00 | 1.198 | 144,584.50 | 0.1% | 0.01 | `worst case for radix tree`
|
||||
| 41.71 | 23,976,459.34 | 1.7% | 885.00 | 219.17 | 4.038 | 132.00 | 0.0% | 0.01 | `create and destroy`
|
||||
|
||||
## Radix tree (this implementation)
|
||||
|
||||
```
|
||||
New conflict set: 1.366 sec
|
||||
0.915 Mtransactions/sec
|
||||
3.660 Mkeys/sec
|
||||
Detect only: 1.248 sec
|
||||
1.002 Mtransactions/sec
|
||||
4.007 Mkeys/sec
|
||||
Skiplist only: 0.573 sec
|
||||
2.182 Mtransactions/sec
|
||||
8.730 Mkeys/sec
|
||||
Performance counters:
|
||||
Build: 0.0594
|
||||
Add: 0.0572
|
||||
Detect: 1.25
|
||||
D.Sort: 0.418
|
||||
D.Combine: 0.0149
|
||||
D.CheckRead: 0.232
|
||||
D.CheckIntraBatch: 0.0067
|
||||
D.MergeWrite: 0.341
|
||||
D.RemoveBefore: 0.232
|
||||
```
|
||||
|
||||
# Our benchmark
|
||||
|
||||
## 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` |
|
||||
|
||||
## 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% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|
||||
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
||||
| 12.63 | 79,186,868.18 | 1.4% | 241.61 | 64.76 | 3.731 | 31.64 | 0.8% | 0.01 | `point reads`
|
||||
| 14.48 | 69,078,073.40 | 0.3% | 292.42 | 74.69 | 3.915 | 41.49 | 0.5% | 0.01 | `prefix reads`
|
||||
| 34.37 | 29,094,694.11 | 0.2% | 759.53 | 179.77 | 4.225 | 100.38 | 0.2% | 0.01 | `range reads`
|
||||
| 19.34 | 51,713,896.36 | 0.7% | 369.70 | 101.81 | 3.631 | 47.88 | 0.6% | 0.01 | `point writes`
|
||||
| 39.16 | 25,538,968.61 | 0.2% | 653.16 | 206.77 | 3.159 | 89.62 | 0.8% | 0.01 | `prefix writes`
|
||||
| 40.58 | 24,642,681.12 | 4.7% | 718.44 | 216.44 | 3.319 | 99.28 | 0.6% | 0.01 | `range writes`
|
||||
| 78.77 | 12,694,520.69 | 3.8% | 1,395.55 | 421.73 | 3.309 | 249.81 | 0.1% | 0.01 | `monotonic increasing point writes`
|
||||
| 287,760.50 | 3,475.11 | 0.5% | 3,929,266.50 | 1,550,225.50 | 2.535 | 639,064.00 | 0.0% | 0.01 | `worst case for radix tree`
|
||||
| 104.76 | 9,545,250.65 | 3.1% | 2,000.00 | 552.82 | 3.618 | 342.00 | 0.0% | 0.01 | `create and destroy`
|
||||
|
||||
# "Real data" test
|
||||
|
||||
@@ -87,13 +43,13 @@ Point queries only, best of three runs. Gc ratio is the ratio of time spent doin
|
||||
## skip list
|
||||
|
||||
```
|
||||
Check: 11.3385 seconds, 329.718 MB/s, Add: 5.35612 seconds, 131.072 MB/s, Gc ratio: 45.7173%
|
||||
Check: 4.39702 seconds, 370.83 MB/s, Add: 4.50025 seconds, 124.583 MB/s, Gc ratio: 29.1333%, Peak idle memory: 5.51852e+06
|
||||
```
|
||||
|
||||
## radix tree
|
||||
|
||||
```
|
||||
Check: 2.48583 seconds, 1503.93 MB/s, Add: 2.12768 seconds, 329.954 MB/s, Gc ratio: 41.7943%
|
||||
Check: 0.987757 seconds, 1650.76 MB/s, Add: 1.24815 seconds, 449.186 MB/s, Gc ratio: 41.4675%, Peak idle memory: 2.02872e+06
|
||||
```
|
||||
|
||||
## hash table
|
||||
@@ -101,5 +57,5 @@ Check: 2.48583 seconds, 1503.93 MB/s, Add: 2.12768 seconds, 329.954 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)
|
||||
|
||||
```
|
||||
Check: 1.83386 seconds, 2038.6 MB/s, Add: 0.601411 seconds, 1167.32 MB/s, Gc ratio: 48.9776%
|
||||
Check: 0.84256 seconds, 1935.23 MB/s, Add: 0.697204 seconds, 804.146 MB/s, Gc ratio: 35.4091%
|
||||
```
|
||||
|
@@ -5,7 +5,7 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <string_view>
|
||||
#include <span>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
@@ -64,7 +64,7 @@ int main(int argc, const char **argv) {
|
||||
auto *const mapOriginal = begin;
|
||||
const auto sizeOriginal = size;
|
||||
|
||||
using StringView = std::basic_string_view<uint8_t>;
|
||||
using StringView = std::span<const uint8_t>;
|
||||
|
||||
StringView write;
|
||||
std::vector<StringView> reads;
|
||||
@@ -78,9 +78,9 @@ int main(int argc, const char **argv) {
|
||||
end = (uint8_t *)memchr(begin, '\n', size);
|
||||
|
||||
if (line.size() > 0 && line[0] == 'P') {
|
||||
write = line.substr(2, line.size());
|
||||
write = line.subspan(2, line.size());
|
||||
} else if (line.size() > 0 && line[0] == 'L') {
|
||||
reads.push_back(line.substr(2, line.size()));
|
||||
reads.push_back(line.subspan(2, line.size()));
|
||||
} else if (line.empty()) {
|
||||
{
|
||||
readRanges.resize(reads.size());
|
||||
@@ -129,6 +129,16 @@ int main(int argc, const char **argv) {
|
||||
close(fd);
|
||||
}
|
||||
|
||||
ConflictSet::MetricsV1 *metrics;
|
||||
int metricsCount;
|
||||
cs.getMetricsV1(&metrics, &metricsCount);
|
||||
for (int i = 0; i < metricsCount; ++i) {
|
||||
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||
printf("# TYPE %s %s\n", metrics[i].name,
|
||||
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||
}
|
||||
|
||||
printf("Check: %g seconds, %g MB/s, Add: %g seconds, %g MB/s, Gc ratio: "
|
||||
"%g%%, Peak idle memory: %g\n",
|
||||
checkTime, checkBytes / checkTime * 1e-6, addTime,
|
||||
|
155
ScriptTest.cpp
155
ScriptTest.cpp
@@ -1,155 +0,0 @@
|
||||
#include "Internal.h"
|
||||
#include <ConflictSet.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
inline size_t getPageSize() {
|
||||
static size_t kPageSize = sysconf(_SC_PAGESIZE);
|
||||
return kPageSize;
|
||||
}
|
||||
|
||||
/// Helper for rounding up to page size (or some other alignment)
|
||||
constexpr inline size_t rightAlign(size_t offset, size_t alignment) {
|
||||
return offset % alignment == 0 ? offset
|
||||
: ((offset / alignment) + 1) * alignment;
|
||||
}
|
||||
|
||||
using StringView = std::basic_string_view<uint8_t>;
|
||||
|
||||
inline StringView operator"" _v(const char *str, size_t size) {
|
||||
return {reinterpret_cast<const uint8_t *>(str), size};
|
||||
}
|
||||
|
||||
int main(int argc, const char **argv) {
|
||||
|
||||
ConflictSet cs{0};
|
||||
ReferenceImpl ref{0};
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
int fd = open(argv[i], O_RDONLY);
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == -1) {
|
||||
int err = errno;
|
||||
fprintf(stderr, "stat error %s - %s\n", argv[i], strerror(err));
|
||||
fflush(stderr);
|
||||
abort();
|
||||
}
|
||||
|
||||
int64_t size = rightAlign(st.st_size, getPageSize());
|
||||
const uint8_t *begin =
|
||||
(uint8_t *)mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
madvise((void *)begin, size, MADV_SEQUENTIAL);
|
||||
auto *const mapOriginal = begin;
|
||||
const auto sizeOriginal = size;
|
||||
|
||||
StringView b;
|
||||
StringView e;
|
||||
int64_t v = 0;
|
||||
int64_t lastWriteVersion = 0;
|
||||
int64_t lastOldestVersion = 0;
|
||||
std::vector<ConflictSet::WriteRange> writeRanges;
|
||||
std::vector<ConflictSet::ReadRange> readRanges;
|
||||
std::vector<ConflictSet::Result> results;
|
||||
|
||||
for (uint8_t *end = (uint8_t *)memchr(begin, '\n', size); end != nullptr;) {
|
||||
StringView line{begin, static_cast<size_t>(end - begin)};
|
||||
size -= end - begin + 1;
|
||||
begin = end + 1;
|
||||
end = (uint8_t *)memchr(begin, '\n', size);
|
||||
|
||||
if (line.starts_with("begin"_v)) {
|
||||
b = line.substr("begin "_v.size(), line.size());
|
||||
printf("b <- %.*s\n", int(b.size()), b.data());
|
||||
} else if (line.starts_with("end"_v)) {
|
||||
e = line.substr("end "_v.size(), line.size());
|
||||
printf("e <- %.*s\n", int(e.size()), e.data());
|
||||
} else if (line.starts_with("version"_v)) {
|
||||
line = line.substr("version "_v.size(), line.size());
|
||||
v = 0;
|
||||
for (auto c : line) {
|
||||
v = v * 10 + int(c) - int('0');
|
||||
}
|
||||
printf("v <- %" PRId64 "\n", v);
|
||||
} else if (line.starts_with("pointread"_v)) {
|
||||
printf("pointread\n");
|
||||
ConflictSet::ReadRange r;
|
||||
r.begin.p = b.data();
|
||||
r.begin.len = b.size();
|
||||
r.end.len = 0;
|
||||
r.readVersion = v;
|
||||
readRanges.push_back(r);
|
||||
} else if (line.starts_with("pointwrite"_v)) {
|
||||
printf("pointwrite\n");
|
||||
assert(writeRanges.empty() ||
|
||||
(writeRanges.back().end.len == 0 ? writeRanges.back().begin
|
||||
: writeRanges.back().end) < b);
|
||||
ConflictSet::WriteRange w;
|
||||
w.begin.p = b.data();
|
||||
w.begin.len = b.size();
|
||||
w.end.len = 0;
|
||||
writeRanges.push_back(w);
|
||||
} else if (line.starts_with("rangeread"_v)) {
|
||||
printf("rangeread\n");
|
||||
ConflictSet::ReadRange r;
|
||||
r.begin.p = b.data();
|
||||
r.begin.len = b.size();
|
||||
r.end.p = e.data();
|
||||
r.end.len = e.size();
|
||||
r.readVersion = v;
|
||||
readRanges.push_back(r);
|
||||
} else if (line.starts_with("rangewrite"_v)) {
|
||||
printf("rangewrite\n");
|
||||
assert(b < e);
|
||||
assert(writeRanges.empty() ||
|
||||
(writeRanges.back().end.len == 0 ? writeRanges.back().begin
|
||||
: writeRanges.back().end) < b);
|
||||
ConflictSet::WriteRange w;
|
||||
w.begin.p = b.data();
|
||||
w.begin.len = b.size();
|
||||
w.end.p = e.data();
|
||||
w.end.len = e.size();
|
||||
writeRanges.push_back(w);
|
||||
} else if (line.starts_with("check"_v)) {
|
||||
printf("check\n");
|
||||
Arena arena;
|
||||
auto *expected = new (arena) ConflictSet::Result[readRanges.size()];
|
||||
auto *actual = new (arena) ConflictSet::Result[readRanges.size()];
|
||||
ref.check(readRanges.data(), expected, readRanges.size());
|
||||
cs.check(readRanges.data(), actual, readRanges.size());
|
||||
for (int i = 0; i < int(readRanges.size()); ++i) {
|
||||
if (expected[i] != actual[i]) {
|
||||
fprintf(stderr, "Expected %s, got %s at index %d\n",
|
||||
resultToStr(expected[i]), resultToStr(actual[i]), i);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
readRanges = {};
|
||||
} else if (line.starts_with("addwrites"_v)) {
|
||||
printf("addwrites\n");
|
||||
assert(v > lastWriteVersion);
|
||||
lastWriteVersion = v;
|
||||
cs.addWrites(writeRanges.data(), writeRanges.size(), v);
|
||||
ref.addWrites(writeRanges.data(), writeRanges.size(), v);
|
||||
writeRanges = {};
|
||||
} else if (line.starts_with("setoldest"_v)) {
|
||||
printf("setoldest\n");
|
||||
assert(v > lastOldestVersion);
|
||||
lastOldestVersion = v;
|
||||
cs.setOldestVersion(v);
|
||||
ref.setOldestVersion(v);
|
||||
} else if (line.empty() || line.starts_with(";"_v)) {
|
||||
// skip
|
||||
} else {
|
||||
printf("Unrecognized line: %.*s\n", int(line.size()), line.data());
|
||||
}
|
||||
}
|
||||
munmap((void *)mapOriginal, sizeOriginal);
|
||||
close(fd);
|
||||
}
|
||||
}
|
511
ServerBench.cpp
Normal file
511
ServerBench.cpp
Normal file
@@ -0,0 +1,511 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <errno.h>
|
||||
#include <netdb.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/uio.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "ConflictSet.h"
|
||||
#include "Internal.h"
|
||||
#include "third_party/nadeau.h"
|
||||
|
||||
constexpr int kCacheLine = 64; // TODO mac m1 is 128
|
||||
|
||||
template <class T> struct TxQueue {
|
||||
|
||||
explicit TxQueue(int lgSlotCount)
|
||||
: slotCount(1 << lgSlotCount), slotCountMask(slotCount - 1),
|
||||
slots(new T[slotCount]) {
|
||||
// Otherwise we can't tell the difference between full and empty.
|
||||
assert(!(slotCountMask & 0x80000000));
|
||||
}
|
||||
|
||||
/// Call from producer thread, after ensuring consumer is no longer accessing
|
||||
/// it somehow
|
||||
~TxQueue() { delete[] slots; }
|
||||
|
||||
/// Must be called from the producer thread
|
||||
void push(T t) {
|
||||
if (wouldBlock()) {
|
||||
// Wait for pops to change and try again
|
||||
consumer.pops.wait(producer.lastPopRead, std::memory_order_relaxed);
|
||||
producer.lastPopRead = consumer.pops.load(std::memory_order_acquire);
|
||||
}
|
||||
slots[producer.pushesNonAtomic++ & slotCountMask] = std::move(t);
|
||||
// seq_cst so that the notify can't be ordered before the store
|
||||
producer.pushes.store(producer.pushesNonAtomic, std::memory_order_seq_cst);
|
||||
// We have to notify every time, since we don't know if this is the last
|
||||
// push ever
|
||||
producer.pushes.notify_one();
|
||||
}
|
||||
|
||||
/// Must be called from the producer thread
|
||||
uint32_t outstanding() {
|
||||
return producer.pushesNonAtomic -
|
||||
consumer.pops.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
/// Returns true if a call to push might block. Must be called from the
|
||||
/// producer thread.
|
||||
bool wouldBlock() {
|
||||
// See if we can determine that overflow won't happen entirely from state
|
||||
// local to the producer
|
||||
if (producer.pushesNonAtomic - producer.lastPopRead == slotCount - 1) {
|
||||
// Re-read pops with memory order
|
||||
producer.lastPopRead = consumer.pops.load(std::memory_order_acquire);
|
||||
return producer.pushesNonAtomic - producer.lastPopRead == slotCount - 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Valid until the next pop, or until this queue is destroyed.
|
||||
T *pop() {
|
||||
// See if we can determine that there's an entry we can pop entirely from
|
||||
// state local to the consumer
|
||||
if (consumer.lastPushRead - consumer.popsNonAtomic == 0) {
|
||||
// Re-read pushes with memory order and try again
|
||||
consumer.lastPushRead = producer.pushes.load(std::memory_order_acquire);
|
||||
if (consumer.lastPushRead - consumer.popsNonAtomic == 0) {
|
||||
// Wait for pushes to change and try again
|
||||
producer.pushes.wait(consumer.lastPushRead, std::memory_order_relaxed);
|
||||
consumer.lastPushRead = producer.pushes.load(std::memory_order_acquire);
|
||||
}
|
||||
}
|
||||
auto result = &slots[consumer.popsNonAtomic++ & slotCountMask];
|
||||
// We only have to write pops with memory order if we've run out of items.
|
||||
// We know that we'll eventually run out.
|
||||
if (consumer.lastPushRead - consumer.popsNonAtomic == 0) {
|
||||
// seq_cst so that the notify can't be ordered before the store
|
||||
consumer.pops.store(consumer.popsNonAtomic, std::memory_order_seq_cst);
|
||||
consumer.pops.notify_one();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint32_t slotCount;
|
||||
const uint32_t slotCountMask;
|
||||
T *slots;
|
||||
struct alignas(kCacheLine) ProducerState {
|
||||
std::atomic<uint32_t> pushes{0};
|
||||
uint32_t pushesNonAtomic{0};
|
||||
uint32_t lastPopRead{0};
|
||||
};
|
||||
struct alignas(kCacheLine) ConsumerState {
|
||||
std::atomic<uint32_t> pops{0};
|
||||
uint32_t popsNonAtomic{0};
|
||||
uint32_t lastPushRead{0};
|
||||
};
|
||||
ProducerState producer;
|
||||
ConsumerState consumer;
|
||||
};
|
||||
|
||||
std::atomic<int64_t> transactions;
|
||||
|
||||
int64_t safeUnaryMinus(int64_t x) {
|
||||
return x == std::numeric_limits<int64_t>::min() ? x : -x;
|
||||
}
|
||||
|
||||
void tupleAppend(std::string &output, int64_t value) {
|
||||
if (value == 0) {
|
||||
output.push_back(0x14);
|
||||
return;
|
||||
}
|
||||
uint32_t size = 8 - __builtin_clrsbll(value) / 8;
|
||||
int typeCode = 0x14 + (value < 0 ? -1 : 1) * size;
|
||||
output.push_back(typeCode);
|
||||
if (value < 0) {
|
||||
value = ~safeUnaryMinus(value);
|
||||
}
|
||||
uint64_t swap = __builtin_bswap64(value);
|
||||
output.insert(output.end(), (uint8_t *)&swap + 8 - size,
|
||||
(uint8_t *)&swap + 8);
|
||||
}
|
||||
|
||||
void tupleAppend(std::string &output, std::string_view value) {
|
||||
output.push_back('\x02');
|
||||
for (auto c : value) {
|
||||
if (c == '\x00') {
|
||||
output.push_back('\x00');
|
||||
output.push_back('\xff');
|
||||
} else {
|
||||
output.push_back(c);
|
||||
}
|
||||
}
|
||||
output.push_back('\x00');
|
||||
}
|
||||
|
||||
template <class... Ts> std::string tupleKey(const Ts &...ts) {
|
||||
std::string result;
|
||||
(tupleAppend(result, ts), ...);
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr int kTotalKeyRange = 1'000'000'000;
|
||||
constexpr int kWindowSize = 1'000'000;
|
||||
constexpr int kNumReadKeysPerTx = 10;
|
||||
constexpr int kNumWriteKeysPerTx = 5;
|
||||
|
||||
struct Transaction {
|
||||
std::vector<std::string> keys;
|
||||
std::vector<weaselab::ConflictSet::ReadRange> reads;
|
||||
std::vector<weaselab::ConflictSet::WriteRange> writes;
|
||||
int64_t version;
|
||||
int64_t oldestVersion;
|
||||
Transaction() = default;
|
||||
explicit Transaction(int64_t version)
|
||||
: version(version), oldestVersion(version - kWindowSize) {
|
||||
std::vector<int64_t> keyIndices;
|
||||
for (int i = 0; i < std::max(kNumReadKeysPerTx, kNumWriteKeysPerTx); ++i) {
|
||||
keyIndices.push_back(rand() % kTotalKeyRange);
|
||||
}
|
||||
std::sort(keyIndices.begin(), keyIndices.end());
|
||||
constexpr std::string_view fullString =
|
||||
"this is a string, where a prefix of it is used as an element of the "
|
||||
"tuple forming the key";
|
||||
for (int i = 0; i < int(keyIndices.size()); ++i) {
|
||||
keys.push_back(
|
||||
tupleKey(0x100, keyIndices[i] / fullString.size(),
|
||||
fullString.substr(0, keyIndices[i] % fullString.size())));
|
||||
// printf("%s\n", printable(keys.back()).c_str());
|
||||
}
|
||||
for (int i = 0; i < kNumWriteKeysPerTx; ++i) {
|
||||
writes.push_back({{(const uint8_t *)keys[i].data(), int(keys[i].size())},
|
||||
{nullptr, 0}});
|
||||
}
|
||||
reads.push_back({{(const uint8_t *)keys[0].data(), int(keys[0].size())},
|
||||
{(const uint8_t *)keys[1].data(), int(keys[1].size())},
|
||||
version - kWindowSize});
|
||||
static_assert(kNumReadKeysPerTx >= 3);
|
||||
for (int i = 2; i < kNumReadKeysPerTx; ++i) {
|
||||
reads.push_back({{(const uint8_t *)keys[i].data(), int(keys[i].size())},
|
||||
{nullptr, 0},
|
||||
version - kWindowSize});
|
||||
}
|
||||
}
|
||||
|
||||
Transaction(Transaction &&) = default;
|
||||
Transaction &operator=(Transaction &&) = default;
|
||||
Transaction(Transaction const &) = delete;
|
||||
Transaction const &operator=(Transaction const &) = delete;
|
||||
};
|
||||
|
||||
struct Resolver {
|
||||
|
||||
void resolve(const weaselab::ConflictSet::ReadRange *reads, int readCount,
|
||||
const weaselab::ConflictSet::WriteRange *writes, int writeCount,
|
||||
int64_t newVersion, int64_t newOldestVersion) {
|
||||
results.resize(readCount);
|
||||
cs.check(reads, results.data(), readCount);
|
||||
cs.addWrites(writes, writeCount, newVersion);
|
||||
cs.setOldestVersion(newOldestVersion);
|
||||
}
|
||||
|
||||
ConflictSet cs{0};
|
||||
|
||||
private:
|
||||
std::vector<weaselab::ConflictSet::Result> results;
|
||||
};
|
||||
|
||||
// Adapted from getaddrinfo man page
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
#include <linux/perf_event.h>
|
||||
struct PerfCounter {
|
||||
PerfCounter(int type, int config, const std::string &labels = {},
|
||||
int groupLeaderFd = -1)
|
||||
: labels(labels) {
|
||||
struct perf_event_attr pe;
|
||||
|
||||
memset(&pe, 0, sizeof(pe));
|
||||
pe.type = type;
|
||||
pe.size = sizeof(pe);
|
||||
pe.config = config;
|
||||
pe.inherit = 1;
|
||||
pe.exclude_kernel = 1;
|
||||
pe.exclude_hv = 1;
|
||||
|
||||
fd = perf_event_open(&pe, 0, -1, groupLeaderFd, 0);
|
||||
if (fd < 0 && errno != ENOENT && errno != EINVAL) {
|
||||
perror(labels.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
int64_t total() const {
|
||||
int64_t count;
|
||||
if (read(fd, &count, sizeof(count)) != sizeof(count)) {
|
||||
perror("read instructions from perf");
|
||||
abort();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
PerfCounter(PerfCounter &&other)
|
||||
: fd(std::exchange(other.fd, -1)), labels(std::move(other.labels)) {}
|
||||
PerfCounter &operator=(PerfCounter &&other) {
|
||||
fd = std::exchange(other.fd, -1);
|
||||
labels = std::move(other.labels);
|
||||
return *this;
|
||||
}
|
||||
|
||||
~PerfCounter() {
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
bool ok() const { return fd >= 0; }
|
||||
const std::string &getLabels() const { return labels; }
|
||||
int getFd() const { return fd; }
|
||||
|
||||
private:
|
||||
int fd;
|
||||
std::string labels;
|
||||
static long perf_event_open(struct perf_event_attr *hw_event, pid_t pid,
|
||||
int cpu, int group_fd, unsigned long flags) {
|
||||
int ret;
|
||||
|
||||
ret = syscall(SYS_perf_event_open, hw_event, pid, cpu, group_fd, flags);
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc != 3) {
|
||||
goto fail;
|
||||
}
|
||||
{
|
||||
int listenFd = getListenFd(argv[1], argv[2]);
|
||||
|
||||
Resolver resolver;
|
||||
auto &cs = resolver.cs;
|
||||
weaselab::ConflictSet::MetricsV1 *metrics;
|
||||
int metricsCount;
|
||||
cs.getMetricsV1(&metrics, &metricsCount);
|
||||
|
||||
#ifdef __linux__
|
||||
PerfCounter instructions{PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS};
|
||||
PerfCounter cycles{PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES, "",
|
||||
instructions.getFd()};
|
||||
|
||||
std::vector<PerfCounter> cacheCounters;
|
||||
for (auto [id, idStr] : std::initializer_list<std::pair<int, std::string>>{
|
||||
{PERF_COUNT_HW_CACHE_L1D, "l1d"},
|
||||
{PERF_COUNT_HW_CACHE_L1I, "l1i"},
|
||||
{PERF_COUNT_HW_CACHE_LL, "ll"},
|
||||
{PERF_COUNT_HW_CACHE_DTLB, "dtlb"},
|
||||
{PERF_COUNT_HW_CACHE_ITLB, "itlb"},
|
||||
{PERF_COUNT_HW_CACHE_BPU, "bpu"},
|
||||
{PERF_COUNT_HW_CACHE_NODE, "node"},
|
||||
}) {
|
||||
for (auto [op, opStr] :
|
||||
std::initializer_list<std::pair<int, std::string>>{
|
||||
{PERF_COUNT_HW_CACHE_OP_READ, "read"},
|
||||
{PERF_COUNT_HW_CACHE_OP_WRITE, "write"},
|
||||
{PERF_COUNT_HW_CACHE_OP_PREFETCH, "prefetch"},
|
||||
}) {
|
||||
int groupLeaderFd = -1;
|
||||
for (auto [result, resultStr] :
|
||||
std::initializer_list<std::pair<int, std::string>>{
|
||||
{PERF_COUNT_HW_CACHE_RESULT_MISS, "miss"},
|
||||
{PERF_COUNT_HW_CACHE_RESULT_ACCESS, "access"},
|
||||
}) {
|
||||
auto labels = "{id=\"" + idStr + "\", op=\"" + opStr +
|
||||
"\", result=\"" + resultStr + "\"}";
|
||||
cacheCounters.emplace_back(PERF_TYPE_HW_CACHE,
|
||||
id | (op << 8) | (result << 16), labels,
|
||||
groupLeaderFd);
|
||||
if (!cacheCounters.back().ok()) {
|
||||
cacheCounters.pop_back();
|
||||
} else {
|
||||
if (groupLeaderFd == -1) {
|
||||
groupLeaderFd = cacheCounters.back().getFd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
TxQueue<std::unique_ptr<Transaction>> queue{10};
|
||||
|
||||
auto workloadThread = std::thread{[&]() {
|
||||
for (int64_t version = kWindowSize;;
|
||||
++version, transactions.fetch_add(1, std::memory_order_relaxed)) {
|
||||
auto tx = std::make_unique<Transaction>(version);
|
||||
queue.push(std::move(tx));
|
||||
}
|
||||
}};
|
||||
|
||||
auto resolverThread = std::thread{[&]() {
|
||||
for (;;) {
|
||||
auto tx = queue.pop()->get();
|
||||
resolver.resolve(tx->reads.data(), tx->reads.size(), tx->writes.data(),
|
||||
tx->writes.size(), tx->version, tx->oldestVersion);
|
||||
}
|
||||
}};
|
||||
|
||||
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";
|
||||
#ifdef __linux__
|
||||
body += "# HELP instructions_total Total number of instructions\n"
|
||||
"# TYPE instructions_total counter\n"
|
||||
"instructions_total ";
|
||||
body += std::to_string(instructions.total());
|
||||
body += "\n";
|
||||
body += "# HELP cycles_total Total number of cycles\n"
|
||||
"# TYPE cycles_total counter\n"
|
||||
"cycles_total ";
|
||||
body += std::to_string(cycles.total());
|
||||
body += "\n";
|
||||
body += "# HELP cache_event_total Total number of cache events\n"
|
||||
"# TYPE cache_event_total counter\n";
|
||||
for (const auto &counter : cacheCounters) {
|
||||
body += "cache_event_total" + counter.getLabels() + " " +
|
||||
std::to_string(counter.total()) + "\n";
|
||||
}
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
452
SkipList.cpp
452
SkipList.cpp
@@ -16,11 +16,17 @@
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* This source code is modified to compile outside of FoundationDB
|
||||
*/
|
||||
|
||||
#include "ConflictSet.h"
|
||||
#include "Internal.h"
|
||||
#include "Metrics.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) {
|
||||
auto result =
|
||||
@@ -38,7 +44,7 @@ std::span<const uint8_t> copyToArena(Arena &arena,
|
||||
}
|
||||
|
||||
using Version = int64_t;
|
||||
#define force_inline __attribute__((always_inline))
|
||||
#define force_inline inline __attribute__((always_inline))
|
||||
using StringRef = std::span<const uint8_t>;
|
||||
|
||||
struct KeyRangeRef {
|
||||
@@ -50,11 +56,124 @@ struct KeyRangeRef {
|
||||
: begin(begin), end(keyAfter(arena, begin)) {}
|
||||
};
|
||||
|
||||
static thread_local uint32_t g_seed = 0;
|
||||
struct KeyInfo {
|
||||
StringRef key;
|
||||
bool begin;
|
||||
bool write;
|
||||
|
||||
static inline int skfastrand() {
|
||||
g_seed = g_seed * 1664525L + 1013904223L;
|
||||
return g_seed;
|
||||
KeyInfo() = default;
|
||||
KeyInfo(StringRef key, bool begin, bool write)
|
||||
: key(key), begin(begin), write(write) {}
|
||||
};
|
||||
|
||||
force_inline int extra_ordering(const KeyInfo &ki) {
|
||||
return ki.begin * 2 + (ki.write ^ ki.begin);
|
||||
}
|
||||
|
||||
// returns true if done with string
|
||||
force_inline bool getCharacter(const KeyInfo &ki, int character,
|
||||
int &outputCharacter) {
|
||||
// normal case
|
||||
if (character < ki.key.size()) {
|
||||
outputCharacter = 5 + ki.key.begin()[character];
|
||||
return false;
|
||||
}
|
||||
|
||||
// termination
|
||||
if (character == ki.key.size()) {
|
||||
outputCharacter = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (character == ki.key.size() + 1) {
|
||||
// end/begin+read/write relative sorting
|
||||
outputCharacter = extra_ordering(ki);
|
||||
return false;
|
||||
}
|
||||
|
||||
outputCharacter = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool operator<(const KeyInfo &lhs, const KeyInfo &rhs) {
|
||||
int i = std::min(lhs.key.size(), rhs.key.size());
|
||||
int c = memcmp(lhs.key.data(), rhs.key.data(), i);
|
||||
if (c != 0)
|
||||
return c < 0;
|
||||
|
||||
// Always sort shorter keys before longer keys.
|
||||
if (lhs.key.size() < rhs.key.size()) {
|
||||
return true;
|
||||
}
|
||||
if (lhs.key.size() > rhs.key.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// When the keys are the same length, use the extra ordering constraint.
|
||||
return extra_ordering(lhs) < extra_ordering(rhs);
|
||||
}
|
||||
|
||||
bool operator==(const KeyInfo &lhs, const KeyInfo &rhs) {
|
||||
return !(lhs < rhs || rhs < lhs);
|
||||
}
|
||||
|
||||
struct SortTask {
|
||||
int begin;
|
||||
int size;
|
||||
int character;
|
||||
SortTask(int begin, int size, int character)
|
||||
: begin(begin), size(size), character(character) {}
|
||||
};
|
||||
|
||||
void sortPoints(std::vector<KeyInfo> &points) {
|
||||
std::vector<SortTask> tasks;
|
||||
std::vector<KeyInfo> newPoints;
|
||||
std::vector<int> counts;
|
||||
|
||||
tasks.emplace_back(0, points.size(), 0);
|
||||
|
||||
while (tasks.size()) {
|
||||
SortTask st = tasks.back();
|
||||
tasks.pop_back();
|
||||
|
||||
if (st.size < 10) {
|
||||
std::sort(points.begin() + st.begin, points.begin() + st.begin + st.size);
|
||||
continue;
|
||||
}
|
||||
|
||||
newPoints.resize(st.size);
|
||||
counts.assign(256 + 5, 0);
|
||||
|
||||
// get counts
|
||||
int c;
|
||||
bool allDone = true;
|
||||
for (int i = st.begin; i < st.begin + st.size; i++) {
|
||||
allDone &= getCharacter(points[i], st.character, c);
|
||||
counts[c]++;
|
||||
}
|
||||
if (allDone)
|
||||
continue;
|
||||
|
||||
// calculate offsets from counts and build next level of tasks
|
||||
int total = 0;
|
||||
for (int i = 0; i < counts.size(); i++) {
|
||||
int temp = counts[i];
|
||||
if (temp > 1)
|
||||
tasks.emplace_back(st.begin + total, temp, st.character + 1);
|
||||
counts[i] = total;
|
||||
total += temp;
|
||||
}
|
||||
|
||||
// put in their places
|
||||
for (int i = st.begin; i < st.begin + st.size; i++) {
|
||||
getCharacter(points[i], st.character, c);
|
||||
newPoints[counts[c]++] = points[i];
|
||||
}
|
||||
|
||||
// copy back into original points array
|
||||
for (int i = 0; i < st.size; i++)
|
||||
points[st.begin + i] = newPoints[i];
|
||||
}
|
||||
}
|
||||
|
||||
static int compare(const StringRef &a, const StringRef &b) {
|
||||
@@ -82,20 +201,24 @@ struct ReadConflictRange {
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr int MaxLevels = 26;
|
||||
|
||||
struct RandomLevel {
|
||||
explicit RandomLevel(uint32_t seed) : seed(seed) {}
|
||||
|
||||
int next() {
|
||||
int result = __builtin_clz(seed | (uint32_t(-1) >> (MaxLevels - 1)));
|
||||
seed = seed * 1664525L + 1013904223L;
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t seed;
|
||||
};
|
||||
|
||||
class SkipList {
|
||||
private:
|
||||
static constexpr int MaxLevels = 26;
|
||||
|
||||
int randomLevel() const {
|
||||
uint32_t i = uint32_t(skfastrand()) >> (32 - (MaxLevels - 1));
|
||||
int level = 0;
|
||||
while (i & 1) {
|
||||
i >>= 1;
|
||||
level++;
|
||||
}
|
||||
assert(level < MaxLevels);
|
||||
return level;
|
||||
}
|
||||
RandomLevel randomLevel{0};
|
||||
|
||||
// Represent a node in the SkipList. The node has multiple (i.e., level)
|
||||
// pointers to other nodes, and keeps a record of the max versions for each
|
||||
@@ -268,13 +391,21 @@ public:
|
||||
}
|
||||
|
||||
explicit SkipList(Version version = 0) {
|
||||
#if DEBUG_VERBOSE
|
||||
fprintf(stderr, "skip_list: create\n");
|
||||
#endif
|
||||
header = Node::create(StringRef(), MaxLevels - 1);
|
||||
for (int l = 0; l < MaxLevels; l++) {
|
||||
header->setNext(l, nullptr);
|
||||
header->setMaxVersion(l, version);
|
||||
}
|
||||
}
|
||||
~SkipList() { destroy(); }
|
||||
~SkipList() {
|
||||
#if DEBUG_VERBOSE
|
||||
fprintf(stderr, "skip_list: destroy\n");
|
||||
#endif
|
||||
destroy();
|
||||
}
|
||||
SkipList(SkipList &&other) noexcept : header(other.header) {
|
||||
other.header = nullptr;
|
||||
}
|
||||
@@ -285,27 +416,33 @@ public:
|
||||
}
|
||||
void swap(SkipList &other) { std::swap(header, other.header); }
|
||||
|
||||
void addConflictRanges(const Finger *fingers, int rangeCount,
|
||||
Version version) {
|
||||
// Returns the change in the number of entries
|
||||
int64_t addConflictRanges(const Finger *fingers, int rangeCount,
|
||||
Version version) {
|
||||
int64_t result = rangeCount;
|
||||
for (int r = rangeCount - 1; r >= 0; r--) {
|
||||
const Finger &startF = fingers[r * 2];
|
||||
const Finger &endF = fingers[r * 2 + 1];
|
||||
|
||||
if (endF.found() == nullptr)
|
||||
if (endF.found() == nullptr) {
|
||||
++result;
|
||||
insert(endF, endF.finger[0]->getMaxVersion(0));
|
||||
}
|
||||
|
||||
remove(startF, endF);
|
||||
result -= remove(startF, endF);
|
||||
insert(startF, version);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void detectConflicts(ReadConflictRange *ranges, int count,
|
||||
ConflictSet::Result *transactionConflictStatus) const {
|
||||
// Return number of iterations of main loop
|
||||
int detectConflicts(ReadConflictRange *ranges, int count,
|
||||
ConflictSet::Result *transactionConflictStatus) const {
|
||||
const int M = 16;
|
||||
int nextJob[M];
|
||||
CheckMax inProgress[M];
|
||||
if (!count)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
int started = std::min(M, count);
|
||||
for (int i = 0; i < started; i++) {
|
||||
@@ -316,8 +453,9 @@ public:
|
||||
|
||||
int prevJob = started - 1;
|
||||
int job = 0;
|
||||
int iters = 0;
|
||||
// vtune: 340 parts
|
||||
while (true) {
|
||||
for (;; ++iters) {
|
||||
if (inProgress[job].advance()) {
|
||||
if (started == count) {
|
||||
if (prevJob == job)
|
||||
@@ -333,6 +471,7 @@ public:
|
||||
prevJob = job;
|
||||
job = nextJob[job];
|
||||
}
|
||||
return iters;
|
||||
}
|
||||
|
||||
void find(const StringRef *values, Finger *results, int *temp, int count) {
|
||||
@@ -426,9 +565,10 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
void remove(const Finger &start, const Finger &end) {
|
||||
// Returns the number of entries removed
|
||||
int64_t remove(const Finger &start, const Finger &end) {
|
||||
if (start.finger[0] == end.finger[0])
|
||||
return;
|
||||
return 0;
|
||||
|
||||
Node *x = start.finger[0]->getNext(0);
|
||||
|
||||
@@ -437,17 +577,20 @@ private:
|
||||
if (start.finger[i] != end.finger[i])
|
||||
start.finger[i]->setNext(i, end.finger[i]->getNext(i));
|
||||
|
||||
int64_t result = 0;
|
||||
while (true) {
|
||||
Node *next = x->getNext(0);
|
||||
x->destroy();
|
||||
++result;
|
||||
if (x == end.finger[0])
|
||||
break;
|
||||
x = next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void insert(const Finger &f, Version version) {
|
||||
int level = randomLevel();
|
||||
int level = randomLevel.next();
|
||||
// std::cout << std::string((const char*)value,length) << " level: " <<
|
||||
// level << std::endl;
|
||||
Node *x = Node::create(f.value, level);
|
||||
@@ -563,16 +706,27 @@ private:
|
||||
};
|
||||
};
|
||||
|
||||
struct SkipListConflictSet {};
|
||||
struct ReadContext {
|
||||
int64_t commits_accum = 0;
|
||||
int64_t conflicts_accum = 0;
|
||||
int64_t too_olds_accum = 0;
|
||||
int64_t check_bytes_accum = 0;
|
||||
};
|
||||
|
||||
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
Impl(int64_t oldestVersion)
|
||||
: oldestVersion(oldestVersion), skipList(oldestVersion) {}
|
||||
: oldestVersion(oldestVersion), newestVersion(oldestVersion),
|
||||
skipList(oldestVersion) {
|
||||
metrics = initMetrics(metricsList, metricsCount);
|
||||
}
|
||||
~Impl() { safe_free(metrics, metricsCount * sizeof(metrics[0])); }
|
||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||
int count) const {
|
||||
int count) {
|
||||
ReadContext tls;
|
||||
Arena arena;
|
||||
auto *ranges = new (arena) ReadConflictRange[count];
|
||||
for (int i = 0; i < count; ++i) {
|
||||
tls.check_bytes_accum += reads[i].begin.len + reads[i].end.len;
|
||||
ranges[i].begin = {reads[i].begin.p, size_t(reads[i].begin.len)};
|
||||
ranges[i].end = reads[i].end.len > 0
|
||||
? StringRef{reads[i].end.p, size_t(reads[i].end.len)}
|
||||
@@ -580,48 +734,96 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
ranges[i].version = reads[i].readVersion;
|
||||
results[i] = ConflictSet::Commit;
|
||||
}
|
||||
skipList.detectConflicts(ranges, count, results);
|
||||
int iters = skipList.detectConflicts(ranges, count, results);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
if (reads[i].readVersion < oldestVersion) {
|
||||
if (reads[i].readVersion < oldestVersion ||
|
||||
reads[i].readVersion < newestVersion - 2e9) {
|
||||
results[i] = TooOld;
|
||||
}
|
||||
tls.commits_accum += results[i] == Commit;
|
||||
tls.conflicts_accum += results[i] == Conflict;
|
||||
tls.too_olds_accum += results[i] == TooOld;
|
||||
}
|
||||
range_read_iterations_total.add(iters);
|
||||
range_read_total.add(count);
|
||||
commits_total.add(tls.commits_accum);
|
||||
conflicts_total.add(tls.conflicts_accum);
|
||||
too_olds_total.add(tls.too_olds_accum);
|
||||
check_bytes_total.add(tls.check_bytes_accum);
|
||||
}
|
||||
|
||||
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
auto points = std::vector<KeyInfo>(count * 2);
|
||||
Arena arena;
|
||||
const int stringCount = count * 2;
|
||||
|
||||
for (int r = 0; r < count; r++) {
|
||||
points.emplace_back(StringRef(writes[r].begin.p, writes[r].begin.len),
|
||||
true, true);
|
||||
points.emplace_back(
|
||||
writes[r].end.len > 0
|
||||
? StringRef{writes[r].end.p, size_t(writes[r].end.len)}
|
||||
: keyAfter(arena, points.back().key),
|
||||
false, true);
|
||||
}
|
||||
|
||||
if (!std::is_sorted(points.begin(), points.end())) {
|
||||
sortPoints(points);
|
||||
}
|
||||
|
||||
int activeWriteCount = 0;
|
||||
std::vector<std::pair<StringRef, StringRef>> combinedWriteConflictRanges;
|
||||
for (const KeyInfo &point : points) {
|
||||
if (point.write) {
|
||||
if (point.begin) {
|
||||
activeWriteCount++;
|
||||
if (activeWriteCount == 1)
|
||||
combinedWriteConflictRanges.emplace_back(point.key, StringRef());
|
||||
} else /*if (point.end)*/ {
|
||||
activeWriteCount--;
|
||||
if (activeWriteCount == 0)
|
||||
combinedWriteConflictRanges.back().second = point.key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(writeVersion >= newestVersion);
|
||||
newestVersion = writeVersion;
|
||||
const int stringCount = combinedWriteConflictRanges.size() * 2;
|
||||
|
||||
const int stripeSize = 16;
|
||||
SkipList::Finger fingers[stripeSize];
|
||||
int temp[stripeSize];
|
||||
int stripes = (stringCount + stripeSize - 1) / stripeSize;
|
||||
StringRef values[stripeSize];
|
||||
int64_t writeVersions[stripeSize / 2];
|
||||
int ss = stringCount - (stripes - 1) * stripeSize;
|
||||
int64_t entryDelta = 0;
|
||||
for (int s = stripes - 1; s >= 0; s--) {
|
||||
for (int i = 0; i * 2 < ss; ++i) {
|
||||
const auto &w = writes[s * stripeSize / 2 + i];
|
||||
values[i * 2] = {w.begin.p, size_t(w.begin.len)};
|
||||
values[i * 2 + 1] = w.end.len > 0
|
||||
? StringRef{w.end.p, size_t(w.end.len)}
|
||||
: keyAfter(arena, values[i * 2]);
|
||||
keyUpdates += 3;
|
||||
const auto &w = combinedWriteConflictRanges[s * stripeSize / 2 + i];
|
||||
values[i * 2] = w.first;
|
||||
values[i * 2 + 1] = w.second;
|
||||
}
|
||||
skipList.find(values, fingers, temp, ss);
|
||||
skipList.addConflictRanges(fingers, ss / 2, writeVersion);
|
||||
entryDelta += skipList.addConflictRanges(fingers, ss / 2, writeVersion);
|
||||
ss = stripeSize;
|
||||
}
|
||||
|
||||
// Run gc at least 200% the rate we're inserting entries
|
||||
keyUpdates += std::max<int64_t>(entryDelta, 0) * 2;
|
||||
}
|
||||
|
||||
void setOldestVersion(int64_t oldestVersion) {
|
||||
// This isn't 100% accurate. It overcounts if you hit the end
|
||||
gc_iterations_total.add(keyUpdates);
|
||||
|
||||
assert(oldestVersion >= this->oldestVersion);
|
||||
this->oldestVersion = oldestVersion;
|
||||
SkipList::Finger finger;
|
||||
int temp;
|
||||
std::span<const uint8_t> key = removalKey;
|
||||
skipList.find(&key, &finger, &temp, 1);
|
||||
skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 10));
|
||||
skipList.removeBefore(oldestVersion, finger, std::exchange(keyUpdates, 0));
|
||||
removalArena = Arena();
|
||||
removalKey = copyToArena(
|
||||
removalArena, {finger.getValue().data(), finger.getValue().size()});
|
||||
@@ -629,54 +831,149 @@ struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||
|
||||
int64_t totalBytes = 0;
|
||||
|
||||
MetricsV1 *metrics;
|
||||
int metricsCount = 0;
|
||||
Metric *metricsList = nullptr;
|
||||
|
||||
#define GAUGE(name, help) \
|
||||
Gauge name { metricsList, metricsCount, #name, help }
|
||||
#define COUNTER(name, help) \
|
||||
Counter name { metricsList, metricsCount, #name, help }
|
||||
// ==================== METRICS DEFINITIONS ====================
|
||||
COUNTER(range_read_total, "Total number of range reads checked");
|
||||
COUNTER(range_read_iterations_total,
|
||||
"Total number of iterations of the main loops for range read checks");
|
||||
COUNTER(commits_total,
|
||||
"Total number of checks where the result is \"commit\"");
|
||||
COUNTER(conflicts_total,
|
||||
"Total number of checks where the result is \"conflict\"");
|
||||
COUNTER(too_olds_total,
|
||||
"Total number of checks where the result is \"too old\"");
|
||||
COUNTER(check_bytes_total, "Total number of key bytes checked");
|
||||
GAUGE(memory_bytes, "Total number of bytes in use");
|
||||
COUNTER(nodes_allocated_total,
|
||||
"The total number of physical tree nodes allocated");
|
||||
COUNTER(nodes_released_total,
|
||||
"The total number of physical tree nodes released");
|
||||
COUNTER(insert_iterations_total,
|
||||
"The total number of iterations of the main loop for insertion. "
|
||||
"Includes searches where the entry already existed, and so insertion "
|
||||
"did not take place");
|
||||
COUNTER(entries_inserted_total,
|
||||
"The total number of entries inserted in the tree");
|
||||
COUNTER(entries_erased_total,
|
||||
"The total number of entries erased from the tree");
|
||||
COUNTER(
|
||||
gc_iterations_total,
|
||||
"The total number of iterations of the main loop for garbage collection");
|
||||
COUNTER(write_bytes_total, "Total number of key bytes in calls to addWrites");
|
||||
GAUGE(oldest_version,
|
||||
"The lowest version that doesn't result in \"TooOld\" for checks");
|
||||
GAUGE(newest_version, "The version of the most recent call to addWrites");
|
||||
// ==================== END METRICS DEFINITIONS ====================
|
||||
#undef GAUGE
|
||||
#undef COUNTER
|
||||
|
||||
void getMetricsV1(MetricsV1 **metrics, int *count) {
|
||||
*metrics = this->metrics;
|
||||
*count = metricsCount;
|
||||
}
|
||||
|
||||
private:
|
||||
int64_t keyUpdates = 10;
|
||||
int64_t keyUpdates = 0;
|
||||
Arena removalArena;
|
||||
std::span<const uint8_t> removalKey;
|
||||
int64_t oldestVersion;
|
||||
int64_t newestVersion;
|
||||
SkipList skipList;
|
||||
};
|
||||
|
||||
// Internal entry points. Public entry points should just delegate to these
|
||||
|
||||
void internal_check(ConflictSet::Impl *impl,
|
||||
const ConflictSet::ReadRange *reads,
|
||||
ConflictSet::Result *results, int count) {
|
||||
impl->check(reads, results, count);
|
||||
}
|
||||
void internal_addWrites(ConflictSet::Impl *impl,
|
||||
const ConflictSet::WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
mallocBytesDelta = 0;
|
||||
impl->addWrites(writes, count, writeVersion);
|
||||
impl->totalBytes += mallocBytesDelta;
|
||||
impl->memory_bytes.set(impl->totalBytes);
|
||||
#if SHOW_MEMORY
|
||||
if (impl->totalBytes != mallocBytes) {
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void internal_setOldestVersion(ConflictSet::Impl *impl, int64_t oldestVersion) {
|
||||
mallocBytesDelta = 0;
|
||||
impl->setOldestVersion(oldestVersion);
|
||||
impl->totalBytes += mallocBytesDelta;
|
||||
impl->memory_bytes.set(impl->totalBytes);
|
||||
#if SHOW_MEMORY
|
||||
if (impl->totalBytes != mallocBytes) {
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
ConflictSet::Impl *internal_create(int64_t oldestVersion) {
|
||||
mallocBytesDelta = 0;
|
||||
auto *result = new (safe_malloc(sizeof(ConflictSet::Impl)))
|
||||
ConflictSet::Impl{oldestVersion};
|
||||
result->totalBytes += mallocBytesDelta;
|
||||
return result;
|
||||
}
|
||||
|
||||
void internal_destroy(ConflictSet::Impl *impl) {
|
||||
impl->~Impl();
|
||||
safe_free(impl, sizeof(ConflictSet::Impl));
|
||||
}
|
||||
|
||||
int64_t internal_getBytes(ConflictSet::Impl *impl) { return impl->totalBytes; }
|
||||
|
||||
void internal_getMetricsV1(ConflictSet::Impl *impl,
|
||||
ConflictSet::MetricsV1 **metrics, int *count) {
|
||||
return impl->getMetricsV1(metrics, count);
|
||||
}
|
||||
|
||||
double internal_getMetricValue(const ConflictSet::MetricsV1 *metric) {
|
||||
return ((Metric *)metric->p)->value.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void ConflictSet::check(const ReadRange *reads, Result *results,
|
||||
int count) const {
|
||||
impl->check(reads, results, count);
|
||||
internal_check(impl, reads, results, count);
|
||||
}
|
||||
|
||||
void ConflictSet::addWrites(const WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
mallocBytesDelta = 0;
|
||||
impl->addWrites(writes, count, writeVersion);
|
||||
impl->totalBytes += mallocBytesDelta;
|
||||
#if SHOW_MEMORY
|
||||
if (impl->totalBytes != mallocBytes) {
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
internal_addWrites(impl, writes, count, writeVersion);
|
||||
}
|
||||
|
||||
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
||||
mallocBytesDelta = 0;
|
||||
impl->setOldestVersion(oldestVersion);
|
||||
impl->totalBytes += mallocBytesDelta;
|
||||
#if SHOW_MEMORY
|
||||
if (impl->totalBytes != mallocBytes) {
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
internal_setOldestVersion(impl, oldestVersion);
|
||||
}
|
||||
|
||||
int64_t ConflictSet::getBytes() const { return impl->totalBytes; }
|
||||
int64_t ConflictSet::getBytes() const { return internal_getBytes(impl); }
|
||||
|
||||
void ConflictSet::getMetricsV1(MetricsV1 **metrics, int *count) const {
|
||||
return internal_getMetricsV1(impl, metrics, count);
|
||||
}
|
||||
|
||||
double ConflictSet::MetricsV1::getValue() const {
|
||||
return internal_getMetricValue(this);
|
||||
}
|
||||
|
||||
ConflictSet::ConflictSet(int64_t oldestVersion)
|
||||
: impl((mallocBytesDelta = 0,
|
||||
new(safe_malloc(sizeof(Impl))) Impl{oldestVersion})) {
|
||||
impl->totalBytes += mallocBytesDelta;
|
||||
}
|
||||
: impl(internal_create(oldestVersion)) {}
|
||||
|
||||
ConflictSet::~ConflictSet() {
|
||||
if (impl) {
|
||||
impl->~Impl();
|
||||
safe_free(impl, sizeof(Impl));
|
||||
internal_destroy(impl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,37 +994,34 @@ extern "C" {
|
||||
__attribute__((__visibility__("default"))) void
|
||||
ConflictSet_check(void *cs, const ConflictSet_ReadRange *reads,
|
||||
ConflictSet_Result *results, int count) {
|
||||
((ConflictSet::Impl *)cs)->check(reads, results, count);
|
||||
internal_check((ConflictSet::Impl *)cs, reads, results, count);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void
|
||||
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
|
||||
int64_t writeVersion) {
|
||||
((ConflictSet::Impl *)cs)->addWrites(writes, count, writeVersion);
|
||||
internal_addWrites((ConflictSet::Impl *)cs, writes, count, writeVersion);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void
|
||||
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
|
||||
((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion);
|
||||
internal_setOldestVersion((ConflictSet::Impl *)cs, oldestVersion);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void *
|
||||
ConflictSet_create(int64_t oldestVersion) {
|
||||
return new (safe_malloc(sizeof(ConflictSet::Impl)))
|
||||
ConflictSet::Impl{oldestVersion};
|
||||
return internal_create(oldestVersion);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
||||
using Impl = ConflictSet::Impl;
|
||||
((Impl *)cs)->~Impl();
|
||||
safe_free(cs, sizeof(Impl));
|
||||
internal_destroy((ConflictSet::Impl *)cs);
|
||||
}
|
||||
__attribute__((__visibility__("default"))) int64_t
|
||||
ConflictSet_getBytes(void *cs) {
|
||||
using Impl = ConflictSet::Impl;
|
||||
return ((Impl *)cs)->totalBytes;
|
||||
return internal_getBytes((ConflictSet::Impl *)cs);
|
||||
}
|
||||
}
|
||||
|
||||
#if SHOW_MEMORY
|
||||
struct __attribute__((visibility("default"))) PeakPrinter {
|
||||
~PeakPrinter() {
|
||||
printf("--- skip_list ---\n");
|
||||
printf("malloc bytes: %g\n", double(mallocBytes));
|
||||
printf("Peak malloc bytes: %g\n", double(peakMallocBytes));
|
||||
}
|
||||
|
@@ -3,17 +3,68 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#ifndef PERF_TEST
|
||||
#define PERF_TEST 0
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::ifstream t(argv[i], std::ios::binary);
|
||||
std::stringstream buffer;
|
||||
buffer << t.rdbuf();
|
||||
auto str = buffer.str();
|
||||
TestDriver<ConflictSet> driver{(const uint8_t *)str.data(), str.size()};
|
||||
while (!driver.next())
|
||||
;
|
||||
if (!driver.ok) {
|
||||
abort();
|
||||
Arbitrary arbitrary({(const uint8_t *)str.data(), str.size()});
|
||||
TestDriver<ConflictSet, !PERF_TEST> driver1{arbitrary};
|
||||
TestDriver<ConflictSet, !PERF_TEST> driver2{arbitrary};
|
||||
bool done1 = false;
|
||||
bool done2 = false;
|
||||
for (;;) {
|
||||
if (!done1) {
|
||||
done1 = driver1.next();
|
||||
if (!driver1.ok) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
if (!done2) {
|
||||
done2 = driver2.next();
|
||||
if (!driver2.ok) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
if (done1 && done2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ConflictSet::MetricsV1 *metrics;
|
||||
int metricsCount;
|
||||
driver1.cs.getMetricsV1(&metrics, &metricsCount);
|
||||
printf("#################### METRICS for ConflictSet 1 for %s "
|
||||
"####################\n",
|
||||
argv[i]);
|
||||
for (int i = 0; i < metricsCount; ++i) {
|
||||
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||
printf("# TYPE %s %s\n", metrics[i].name,
|
||||
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||
}
|
||||
puts("");
|
||||
}
|
||||
{
|
||||
ConflictSet::MetricsV1 *metrics;
|
||||
int metricsCount;
|
||||
driver2.cs.getMetricsV1(&metrics, &metricsCount);
|
||||
printf("#################### METRICS for ConflictSet 2 for %s "
|
||||
"####################\n",
|
||||
argv[i]);
|
||||
for (int i = 0; i < metricsCount; ++i) {
|
||||
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||
printf("# TYPE %s %s\n", metrics[i].name,
|
||||
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||
}
|
||||
puts("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,11 @@
|
||||
__aarch64_ldadd8_relax
|
||||
__getauxval@GLIBC_2.17
|
||||
__stack_chk_fail@GLIBC_2.17
|
||||
__stack_chk_guard@GLIBC_2.17
|
||||
abort@GLIBC_2.17
|
||||
free@GLIBC_2.17
|
||||
malloc@GLIBC_2.17
|
||||
memcmp@GLIBC_2.17
|
||||
memcpy@GLIBC_2.17
|
||||
memmove@GLIBC_2.17
|
||||
memset@GLIBC_2.17
|
@@ -1,7 +1,8 @@
|
||||
set(CMAKE_SYSTEM_NAME Linux)
|
||||
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
set(CMAKE_C_COMPILER "/usr/bin/aarch64-linux-gnu-gcc")
|
||||
set(CMAKE_CXX_COMPILER "/usr/bin/aarch64-linux-gnu-g++")
|
||||
set(CMAKE_C_COMPILER "clang;--target=aarch64-linux-gnu")
|
||||
set(CMAKE_CXX_COMPILER "clang++;--target=aarch64-linux-gnu")
|
||||
set(CMAKE_FIND_ROOT_PATH /usr/aarch64-linux-gnu)
|
||||
set(CMAKE_CROSSCOMPILING_EMULATOR "qemu-aarch64;-L;/usr/aarch64-linux-gnu/")
|
||||
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE arm64)
|
||||
set(LD_EXE "/usr/bin/aarch64-linux-gnu-ld")
|
||||
|
@@ -13,5 +13,7 @@ __ZN8weaselab11ConflictSetC2Ex
|
||||
__ZN8weaselab11ConflictSetD1Ev
|
||||
__ZN8weaselab11ConflictSetD2Ev
|
||||
__ZN8weaselab11ConflictSetaSEOS0_
|
||||
__ZNK8weaselab11ConflictSet12getMetricsV1EPPNS0_9MetricsV1EPi
|
||||
__ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
|
||||
__ZNK8weaselab11ConflictSet8getBytesEv
|
||||
__ZNK8weaselab11ConflictSet9MetricsV18getValueEv
|
@@ -1,7 +1,12 @@
|
||||
___chkstk_darwin
|
||||
___stack_chk_fail
|
||||
___stack_chk_guard
|
||||
__tlv_bootstrap
|
||||
_abort
|
||||
_bzero
|
||||
_free
|
||||
_malloc
|
||||
_memcmp
|
||||
_memcpy
|
||||
_memmove
|
||||
_memmove
|
||||
dyld_stub_binder
|
132
conflict_set.py
132
conflict_set.py
@@ -4,22 +4,6 @@ import os
|
||||
|
||||
from typing import Optional
|
||||
|
||||
_lib = None
|
||||
for f in (
|
||||
os.path.dirname(__file__) + "/build/radix_tree/libconflict-set.so.0",
|
||||
os.path.dirname(__file__) + "/build/radix_tree/libconflict-set.0.dylib",
|
||||
):
|
||||
try:
|
||||
_lib = ctypes.cdll.LoadLibrary(f)
|
||||
except:
|
||||
pass
|
||||
|
||||
if _lib is None:
|
||||
import sys
|
||||
|
||||
print("Could not find libconflict-set", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
class _Key(ctypes.Structure):
|
||||
_fields_ = [("p", ctypes.POINTER(ctypes.c_ubyte)), ("len", ctypes.c_int)]
|
||||
@@ -37,28 +21,6 @@ class WriteRange(ctypes.Structure):
|
||||
_fields_ = [("begin", _Key), ("end", _Key)]
|
||||
|
||||
|
||||
_lib.ConflictSet_create.argtypes = (ctypes.c_int64,)
|
||||
_lib.ConflictSet_create.restype = ctypes.c_void_p
|
||||
|
||||
_lib.ConflictSet_check.argtypes = (
|
||||
ctypes.c_void_p,
|
||||
ctypes.POINTER(ReadRange),
|
||||
ctypes.POINTER(ctypes.c_int),
|
||||
ctypes.c_int,
|
||||
)
|
||||
|
||||
_lib.ConflictSet_addWrites.argtypes = (
|
||||
ctypes.c_void_p,
|
||||
ctypes.POINTER(WriteRange),
|
||||
ctypes.c_int,
|
||||
ctypes.c_int64,
|
||||
)
|
||||
|
||||
_lib.ConflictSet_setOldestVersion.argtypes = (ctypes.c_void_p, ctypes.c_int64)
|
||||
|
||||
_lib.ConflictSet_destroy.argtypes = (ctypes.c_void_p,)
|
||||
|
||||
|
||||
class Result(enum.Enum):
|
||||
COMMIT = 0
|
||||
CONFLICT = 1
|
||||
@@ -66,55 +28,111 @@ class Result(enum.Enum):
|
||||
|
||||
|
||||
def write(begin: bytes, end: Optional[bytes] = None) -> WriteRange:
|
||||
b = (ctypes.c_ubyte * len(begin))()
|
||||
b.value = begin
|
||||
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
|
||||
|
||||
if end is None:
|
||||
e = (ctypes.c_ubyte * 0)()
|
||||
e.value = b""
|
||||
else:
|
||||
e = (ctypes.c_ubyte * len(end))()
|
||||
e.value = end
|
||||
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
|
||||
return WriteRange(_Key(b, len(b)), _Key(e, len(e)))
|
||||
|
||||
|
||||
def read(version: int, begin: bytes, end: Optional[bytes] = None) -> ReadRange:
|
||||
b = (ctypes.c_ubyte * len(begin))()
|
||||
b.value = begin
|
||||
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
|
||||
if end is None:
|
||||
e = (ctypes.c_ubyte * 0)()
|
||||
e.value = b""
|
||||
else:
|
||||
e = (ctypes.c_ubyte * len(end))()
|
||||
e.value = end
|
||||
e = (ctypes.c_ubyte * len(end)).from_buffer(bytearray(end))
|
||||
return ReadRange(_Key(b, len(b)), _Key(e, len(e)), version)
|
||||
|
||||
|
||||
class ConflictSet:
|
||||
def __init__(self, version: int = 0) -> None:
|
||||
self.p = _lib.ConflictSet_create(version)
|
||||
def __init__(
|
||||
self,
|
||||
version: int = 0,
|
||||
build_dir: Optional[str] = None,
|
||||
implementation: Optional[str] = None,
|
||||
) -> None:
|
||||
self._lib = None
|
||||
if build_dir is None:
|
||||
build_dir = os.path.dirname(__file__) + "/build"
|
||||
if implementation is None:
|
||||
implementation = "radix_tree"
|
||||
for f in (
|
||||
build_dir + "/" + implementation + "/libconflict-set.so.0",
|
||||
os.path.dirname(__file__)
|
||||
+ "/build/"
|
||||
+ implementation
|
||||
+ "/libconflict-set.0.dylib",
|
||||
):
|
||||
try:
|
||||
self._lib = ctypes.cdll.LoadLibrary(f)
|
||||
except:
|
||||
pass
|
||||
|
||||
if self._lib is None:
|
||||
import sys
|
||||
|
||||
print(
|
||||
"Could not find libconflict-set implementation " + implementation,
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
self._lib.ConflictSet_create.argtypes = (ctypes.c_int64,)
|
||||
self._lib.ConflictSet_create.restype = ctypes.c_void_p
|
||||
|
||||
self._lib.ConflictSet_check.argtypes = (
|
||||
ctypes.c_void_p,
|
||||
ctypes.POINTER(ReadRange),
|
||||
ctypes.POINTER(ctypes.c_int),
|
||||
ctypes.c_int,
|
||||
)
|
||||
|
||||
self._lib.ConflictSet_addWrites.argtypes = (
|
||||
ctypes.c_void_p,
|
||||
ctypes.POINTER(WriteRange),
|
||||
ctypes.c_int,
|
||||
ctypes.c_int64,
|
||||
)
|
||||
|
||||
self._lib.ConflictSet_setOldestVersion.argtypes = (
|
||||
ctypes.c_void_p,
|
||||
ctypes.c_int64,
|
||||
)
|
||||
|
||||
self._lib.ConflictSet_destroy.argtypes = (ctypes.c_void_p,)
|
||||
|
||||
self._lib.ConflictSet_getBytes.argtypes = (ctypes.c_void_p,)
|
||||
self._lib.ConflictSet_getBytes.restype = ctypes.c_int64
|
||||
|
||||
self.p = self._lib.ConflictSet_create(version)
|
||||
|
||||
def addWrites(self, version: int, *writes: WriteRange):
|
||||
_lib.ConflictSet_addWrites(
|
||||
self._lib.ConflictSet_addWrites(
|
||||
self.p, (WriteRange * len(writes))(*writes), len(writes), version
|
||||
)
|
||||
|
||||
def check(self, *reads: ReadRange) -> list[Result]:
|
||||
r = (ctypes.c_int * len(reads))()
|
||||
_lib.ConflictSet_check(self.p, *reads, r, 1)
|
||||
self._lib.ConflictSet_check(
|
||||
self.p, (ReadRange * len(reads))(*reads), r, len(reads)
|
||||
)
|
||||
return [Result(x) for x in r]
|
||||
|
||||
def setOldestVersion(self, version: int) -> None:
|
||||
_lib.ConflictSet_setOldestVersion(self.p, version)
|
||||
self._lib.ConflictSet_setOldestVersion(self.p, version)
|
||||
|
||||
def getBytes(self) -> int:
|
||||
return self._lib.ConflictSet_getBytes(self.p)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def close(self) -> None:
|
||||
if self.p is not None:
|
||||
_lib.ConflictSet_destroy(self.p)
|
||||
self._lib.ConflictSet_destroy(self.p)
|
||||
self.p = None
|
||||
|
||||
def __exit__(self, exception_type, exception_value, exception_traceback):
|
||||
if self.p is not None:
|
||||
_lib.ConflictSet_destroy(self.p)
|
||||
self.p = None
|
||||
self.close()
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#include "ConflictSet.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
|
||||
using namespace weaselab;
|
||||
|
||||
@@ -21,4 +22,14 @@ int main(void) {
|
||||
assert(result == ConflictSet::Conflict);
|
||||
int64_t bytes = cs.getBytes();
|
||||
assert(bytes > 0);
|
||||
|
||||
ConflictSet::MetricsV1 *metrics;
|
||||
int metricsCount;
|
||||
cs.getMetricsV1(&metrics, &metricsCount);
|
||||
for (int i = 0; i < metricsCount; ++i) {
|
||||
printf("# HELP %s %s\n", metrics[i].name, metrics[i].help);
|
||||
printf("# TYPE %s %s\n", metrics[i].name,
|
||||
metrics[i].type == metrics[i].Counter ? "counter" : "gauge");
|
||||
printf("%s %g\n", metrics[i].name, metrics[i].getValue());
|
||||
}
|
||||
}
|
||||
|
BIN
corpus/003fdafe6e5f359e1043927b266e6ed6193562bc
Normal file
BIN
corpus/003fdafe6e5f359e1043927b266e6ed6193562bc
Normal file
Binary file not shown.
BIN
corpus/00533f61fd89afb9deb3dc978b368a3b3abf3992
Normal file
BIN
corpus/00533f61fd89afb9deb3dc978b368a3b3abf3992
Normal file
Binary file not shown.
BIN
corpus/00850bfa9ce0f9e64b8688db48dc6562be637d97
Normal file
BIN
corpus/00850bfa9ce0f9e64b8688db48dc6562be637d97
Normal file
Binary file not shown.
BIN
corpus/008e3be49d62c3e7fa3785b882bdb65ee4b68977
Normal file
BIN
corpus/008e3be49d62c3e7fa3785b882bdb65ee4b68977
Normal file
Binary file not shown.
BIN
corpus/00c2f9be831326008b88fa8daa1f6a2ba54ff0ab
Normal file
BIN
corpus/00c2f9be831326008b88fa8daa1f6a2ba54ff0ab
Normal file
Binary file not shown.
BIN
corpus/00c877491b1fcc8bb8fb36874a7922de7f3f4df2
Normal file
BIN
corpus/00c877491b1fcc8bb8fb36874a7922de7f3f4df2
Normal file
Binary file not shown.
BIN
corpus/00c9af5b41b4ae33c12623b6683b5a0315a6a4d4
Normal file
BIN
corpus/00c9af5b41b4ae33c12623b6683b5a0315a6a4d4
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/00df1fd720151ee1fb20574f080e75190d715723
Normal file
BIN
corpus/00df1fd720151ee1fb20574f080e75190d715723
Normal file
Binary file not shown.
BIN
corpus/01466ed53837dddee4968104f692156ec738a946
Normal file
BIN
corpus/01466ed53837dddee4968104f692156ec738a946
Normal file
Binary file not shown.
BIN
corpus/0155e58949c6b4380c2dba35c72183c883ee12c8
Normal file
BIN
corpus/0155e58949c6b4380c2dba35c72183c883ee12c8
Normal file
Binary file not shown.
BIN
corpus/015d4b680de41003e894d6fd8b1216b4b5765b3b
Normal file
BIN
corpus/015d4b680de41003e894d6fd8b1216b4b5765b3b
Normal file
Binary file not shown.
BIN
corpus/0164e1ae452d063faa26b90d924f52189f268011
Normal file
BIN
corpus/0164e1ae452d063faa26b90d924f52189f268011
Normal file
Binary file not shown.
BIN
corpus/01700660cf8938f7861559ae8f5fc00a55532679
Normal file
BIN
corpus/01700660cf8938f7861559ae8f5fc00a55532679
Normal file
Binary file not shown.
BIN
corpus/01c2a61d2b6e7fe40b38e46287afd69e01e52408
Normal file
BIN
corpus/01c2a61d2b6e7fe40b38e46287afd69e01e52408
Normal file
Binary file not shown.
BIN
corpus/01e5c2c85ebe3fd89e03cc04d4a185e8aa5d3a8a
Normal file
BIN
corpus/01e5c2c85ebe3fd89e03cc04d4a185e8aa5d3a8a
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/026c293f183c14c6f173718d432b73f41e19cba8
Normal file
BIN
corpus/026c293f183c14c6f173718d432b73f41e19cba8
Normal file
Binary file not shown.
BIN
corpus/02781af3a149db8ecf015171b93eed4de428c8ce
Normal file
BIN
corpus/02781af3a149db8ecf015171b93eed4de428c8ce
Normal file
Binary file not shown.
BIN
corpus/027a04de710ae4e8998cabe76b61ed427c7748e9
Normal file
BIN
corpus/027a04de710ae4e8998cabe76b61ed427c7748e9
Normal file
Binary file not shown.
BIN
corpus/027eeb2ee154aca4151298a6c31f3b80f58d3fae
Normal file
BIN
corpus/027eeb2ee154aca4151298a6c31f3b80f58d3fae
Normal file
Binary file not shown.
BIN
corpus/0292e5991e94d8861e8240171e7ce2c0a9742616
Normal file
BIN
corpus/0292e5991e94d8861e8240171e7ce2c0a9742616
Normal file
Binary file not shown.
BIN
corpus/02b4c0e1317cc3d71ca9d05d3d855c9d0fbed0ed
Normal file
BIN
corpus/02b4c0e1317cc3d71ca9d05d3d855c9d0fbed0ed
Normal file
Binary file not shown.
BIN
corpus/02ce0b03d4e732ab6e7b264ccce3ea77d3787e5f
Normal file
BIN
corpus/02ce0b03d4e732ab6e7b264ccce3ea77d3787e5f
Normal file
Binary file not shown.
BIN
corpus/02df8cae475c41d1bbaefe0fc7e23fd35a9a23c8
Normal file
BIN
corpus/02df8cae475c41d1bbaefe0fc7e23fd35a9a23c8
Normal file
Binary file not shown.
BIN
corpus/03119dd29f68a6a8ff8f67a653e7f4ba01c6847a
Normal file
BIN
corpus/03119dd29f68a6a8ff8f67a653e7f4ba01c6847a
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/0325e533cf35c1e7ac95326c9b189911deffb27e
Normal file
BIN
corpus/0325e533cf35c1e7ac95326c9b189911deffb27e
Normal file
Binary file not shown.
BIN
corpus/033c4c71cb5ca5a3a933b9001be1d4246784fefb
Normal file
BIN
corpus/033c4c71cb5ca5a3a933b9001be1d4246784fefb
Normal file
Binary file not shown.
BIN
corpus/035247cc26d90aee0b4f9560833e5a495d890159
Normal file
BIN
corpus/035247cc26d90aee0b4f9560833e5a495d890159
Normal file
Binary file not shown.
BIN
corpus/03680111265d5f0b8816feb73069cf1f6538d4dd
Normal file
BIN
corpus/03680111265d5f0b8816feb73069cf1f6538d4dd
Normal file
Binary file not shown.
BIN
corpus/0370da7797ee28434459cf21350d311df2aed581
Normal file
BIN
corpus/0370da7797ee28434459cf21350d311df2aed581
Normal file
Binary file not shown.
BIN
corpus/037f4bcc441003fb4cc2cbfd4d986c201035a744
Normal file
BIN
corpus/037f4bcc441003fb4cc2cbfd4d986c201035a744
Normal file
Binary file not shown.
BIN
corpus/0381a9a3c6ea140cdd7ec8cf73eb2c56e01f8d54
Normal file
BIN
corpus/0381a9a3c6ea140cdd7ec8cf73eb2c56e01f8d54
Normal file
Binary file not shown.
BIN
corpus/038c312d57a8f6fd53a629f2819237fb5f88da51
Normal file
BIN
corpus/038c312d57a8f6fd53a629f2819237fb5f88da51
Normal file
Binary file not shown.
BIN
corpus/038d82d16c1b0e6f6f59db9c26d4cd82973fe380
Normal file
BIN
corpus/038d82d16c1b0e6f6f59db9c26d4cd82973fe380
Normal file
Binary file not shown.
BIN
corpus/0390280ec499c5f687428e700028c230e8ce944e
Normal file
BIN
corpus/0390280ec499c5f687428e700028c230e8ce944e
Normal file
Binary file not shown.
BIN
corpus/03c5f3b6189b03df9f473ad64e717ab2310e8a02
Normal file
BIN
corpus/03c5f3b6189b03df9f473ad64e717ab2310e8a02
Normal file
Binary file not shown.
BIN
corpus/03cbf22d59d0005921ca3e3c725fc9c165f9e873
Normal file
BIN
corpus/03cbf22d59d0005921ca3e3c725fc9c165f9e873
Normal file
Binary file not shown.
BIN
corpus/03d3918b737a86ed38fbeae6dff198d6913b90b2
Normal file
BIN
corpus/03d3918b737a86ed38fbeae6dff198d6913b90b2
Normal file
Binary file not shown.
BIN
corpus/03feca4da73a0c72f67bbaee135d10aec44b68ed
Normal file
BIN
corpus/03feca4da73a0c72f67bbaee135d10aec44b68ed
Normal file
Binary file not shown.
BIN
corpus/040b817f7a2fde4d7bec916534e003b3a8036239
Normal file
BIN
corpus/040b817f7a2fde4d7bec916534e003b3a8036239
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/04228353f0feb04662eebbe15dab859927d87982
Normal file
BIN
corpus/04228353f0feb04662eebbe15dab859927d87982
Normal file
Binary file not shown.
BIN
corpus/04239a60051c9a1779d9a896b84eff01f272f191
Normal file
BIN
corpus/04239a60051c9a1779d9a896b84eff01f272f191
Normal file
Binary file not shown.
BIN
corpus/042c4df1a3357a2b015db64e8e6b09549d3655c5
Normal file
BIN
corpus/042c4df1a3357a2b015db64e8e6b09549d3655c5
Normal file
Binary file not shown.
BIN
corpus/04338408516abc9c563802aadc522db002e0a5d0
Normal file
BIN
corpus/04338408516abc9c563802aadc522db002e0a5d0
Normal file
Binary file not shown.
BIN
corpus/043cb17815ddb8f3242a3c3dbf8e7c52263b0e30
Normal file
BIN
corpus/043cb17815ddb8f3242a3c3dbf8e7c52263b0e30
Normal file
Binary file not shown.
BIN
corpus/0480d4ba79c290ac8ecfb000bc62f204326a6e2d
Normal file
BIN
corpus/0480d4ba79c290ac8ecfb000bc62f204326a6e2d
Normal file
Binary file not shown.
BIN
corpus/04aae30fd4c1fa9678360ee983a87c11b2d687b4
Normal file
BIN
corpus/04aae30fd4c1fa9678360ee983a87c11b2d687b4
Normal file
Binary file not shown.
BIN
corpus/04b685b62d0428575c4b780e5aa5746a7b3c03c0
Normal file
BIN
corpus/04b685b62d0428575c4b780e5aa5746a7b3c03c0
Normal file
Binary file not shown.
BIN
corpus/04c53e268f5d34ac6033f80672ebf9b48975cfc8
Normal file
BIN
corpus/04c53e268f5d34ac6033f80672ebf9b48975cfc8
Normal file
Binary file not shown.
BIN
corpus/04c65b5774374b94914863a1c244cd4ca26873d2
Normal file
BIN
corpus/04c65b5774374b94914863a1c244cd4ca26873d2
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/051590b47c5269306a3a8894eb3d72d86c4a6e71
Normal file
BIN
corpus/051590b47c5269306a3a8894eb3d72d86c4a6e71
Normal file
Binary file not shown.
BIN
corpus/05184b61aacd101ea39bc6f3b9985fec132b8125
Normal file
BIN
corpus/05184b61aacd101ea39bc6f3b9985fec132b8125
Normal file
Binary file not shown.
BIN
corpus/053c56641f68bea3be49d74416d62ca583d6492e
Normal file
BIN
corpus/053c56641f68bea3be49d74416d62ca583d6492e
Normal file
Binary file not shown.
BIN
corpus/054623afeda876d353806d81077181229affdd36
Normal file
BIN
corpus/054623afeda876d353806d81077181229affdd36
Normal file
Binary file not shown.
BIN
corpus/0558cbe0f7b69d3e129a95d07a67e6bcd737f280
Normal file
BIN
corpus/0558cbe0f7b69d3e129a95d07a67e6bcd737f280
Normal file
Binary file not shown.
BIN
corpus/056fee409c5511f6a7456cd4791d9385f6d5ebbe
Normal file
BIN
corpus/056fee409c5511f6a7456cd4791d9385f6d5ebbe
Normal file
Binary file not shown.
BIN
corpus/05a091a7d5e921098504585a4201967860d310c6
Normal file
BIN
corpus/05a091a7d5e921098504585a4201967860d310c6
Normal file
Binary file not shown.
BIN
corpus/05a237bf01382822789d33bda1b24298e13bb031
Normal file
BIN
corpus/05a237bf01382822789d33bda1b24298e13bb031
Normal file
Binary file not shown.
BIN
corpus/05a9e20eac1e7efb9b40fa032c35f76f87622210
Normal file
BIN
corpus/05a9e20eac1e7efb9b40fa032c35f76f87622210
Normal file
Binary file not shown.
BIN
corpus/05b04a01fa702668fc100f7988f01cfd52915afd
Normal file
BIN
corpus/05b04a01fa702668fc100f7988f01cfd52915afd
Normal file
Binary file not shown.
Binary file not shown.
BIN
corpus/05e78ae4ba5007b2ad39b69b19604f8cb90650bd
Normal file
BIN
corpus/05e78ae4ba5007b2ad39b69b19604f8cb90650bd
Normal file
Binary file not shown.
BIN
corpus/060d0c1be7331609f5b8c1d1201bc06c1b203c21
Normal file
BIN
corpus/060d0c1be7331609f5b8c1d1201bc06c1b203c21
Normal file
Binary file not shown.
BIN
corpus/061a5fd2f5e90098ec417feda11d43b7ff3ecb79
Normal file
BIN
corpus/061a5fd2f5e90098ec417feda11d43b7ff3ecb79
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
corpus/065ec99952bffeb53f340d8cee645654f5b1e891
Normal file
BIN
corpus/065ec99952bffeb53f340d8cee645654f5b1e891
Normal file
Binary file not shown.
BIN
corpus/0677708940f8c88cd48e841993d34838d9b1215e
Normal file
BIN
corpus/0677708940f8c88cd48e841993d34838d9b1215e
Normal file
Binary file not shown.
BIN
corpus/069fd605e510b2218cf1b10ec79ae00763aeb195
Normal file
BIN
corpus/069fd605e510b2218cf1b10ec79ae00763aeb195
Normal file
Binary file not shown.
BIN
corpus/06ceaf9ecc159477b48e4633d19c883e9f57dc52
Normal file
BIN
corpus/06ceaf9ecc159477b48e4633d19c883e9f57dc52
Normal file
Binary file not shown.
BIN
corpus/0707680f7d9ce11dd30c6536f05848d598fb27d4
Normal file
BIN
corpus/0707680f7d9ce11dd30c6536f05848d598fb27d4
Normal file
Binary file not shown.
BIN
corpus/0707d9709690c9700f27fd73d97c0325fa81c162
Normal file
BIN
corpus/0707d9709690c9700f27fd73d97c0325fa81c162
Normal file
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user