Compare commits
722 Commits
40dca168ba
...
cf-integri
Author | SHA1 | Date | |
---|---|---|---|
c46f633dbf | |||
400350946c | |||
607a4ef6e2 | |||
b0750772ec | |||
86abc02188 | |||
a90e353fcd | |||
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 | |||
a40b5dcd74 | |||
193b1926ff | |||
1c900c5a8c | |||
90fdcdd51a | |||
eb3f6823eb | |||
1534e10b75 | |||
3c100ccee8 | |||
5cf45d1c35 | |||
4f97932893 | |||
24b0f6b7e4 | |||
e77c3fdee6 | |||
383b956bc0 | |||
5fad15305a | |||
ad91fb36a5 | |||
38c1481432 | |||
771ae896e7 | |||
5bf72bda61 | |||
a534f3b758 | |||
ae4fa889c7 | |||
e3d3b0ec0d | |||
dea8d6ae01 | |||
dbc6f2313a | |||
4f51878642 | |||
215865a462 | |||
348ebf016a | |||
377259ffa0 | |||
70220d95e7 | |||
71c39f9955 | |||
8cc17158fd | |||
ab211c646a | |||
7af961f141 | |||
a91df62608 | |||
0a1843a161 | |||
4edf0315d9 | |||
14515e186a | |||
b0085df5ad | |||
76a7e17b29 | |||
5cf43d1bfa | |||
25cc427ec5 | |||
c15c2e7b44 | |||
a4d1f91670 | |||
b7cdecaf71 | |||
cda28643a6 | |||
cdb5360b9a | |||
ef224a60f4 | |||
6222b74787 | |||
19edc6f78f | |||
3f9d01c46a | |||
db03c6f901 | |||
c1698b040b | |||
2e08b54785 | |||
aa6f237d50 | |||
becfd25139 | |||
d78b36821b | |||
ce79b47fbe | |||
727b7e642a | |||
cb4c2b7e1e | |||
ef9b789745 | |||
edd7bcaa1e | |||
be8ac879c5 | |||
83c7f66d67 | |||
a5710b8282 | |||
c31eebd5de | |||
ddeb059968 | |||
5a0bcf9a5a | |||
97717cec86 | |||
6a13c43a78 | |||
c6c438bae2 | |||
7d4f832b43 | |||
5b0c3c2428 | |||
f2b5e9b0bf | |||
8e0e65dac6 | |||
5aab76847a | |||
1a51aa00e5 | |||
3975bada0c | |||
a5330b6e23 | |||
2e246ec6a4 | |||
6d7e3c9849 | |||
671da5d096 | |||
303b368fc5 | |||
9f5a68e2c0 | |||
dfbb3ce5f1 | |||
e7719b6e0b | |||
83fedf1f9e | |||
8556caf360 | |||
9d13ca84f5 | |||
a79436ee9b | |||
e9c8537cf2 | |||
5b988efe6f | |||
e35d698b21 | |||
30496d14e7 | |||
eb93157ddf | |||
9cafef8bbb | |||
6f81580953 | |||
429fe5baed | |||
a0451e4423 | |||
a9b3d3d1c9 | |||
b817e3c749 | |||
ee36bda8f8 | |||
a8f4bd91c8 | |||
35086ee66a | |||
0f795cf163 | |||
a07c93ffff | |||
c68f563017 | |||
6b6a9bace9 | |||
3cb0765fdd | |||
351ff3df3b | |||
e818648cdc | |||
12540b8713 | |||
c2606cd26a | |||
4b72fc0b7b | |||
a9caa0249e | |||
08b2b7f41a | |||
26bd8b94cc | |||
55eaef5b1d | |||
797e6b4a3e | |||
ee86b5289b | |||
b779c0f6f7 | |||
ef802b8acd | |||
5371c2bede | |||
75c304bbe7 | |||
aefb83dbc6 | |||
b0ac7e41b9 | |||
4b6b2747bf | |||
1496aa106b | |||
71e117965e | |||
471b276947 | |||
b721bc80a9 | |||
5e4eab55fb | |||
1dcb380c73 | |||
87d650ff00 | |||
b8f6a8edf2 | |||
01f1d5850f | |||
cd567383c3 | |||
53a442abf9 | |||
6e212847ac | |||
44a023c2f4 | |||
e32bea7b29 | |||
504a93bb10 | |||
b79d8f71d3 | |||
34430dbbe7 | |||
06fcb2531e | |||
bd24a362e3 | |||
1437280ec7 | |||
e5051bac9e | |||
733f32b22e | |||
3fb8bf7c3b | |||
0c8cb8faa5 | |||
93e487c8fb | |||
d91538dcad | |||
43a768d152 | |||
2989866a6d | |||
60df97847c | |||
0038382661 | |||
782abc70d6 | |||
8802d17acd | |||
02afd47d8f | |||
987e93b190 | |||
81263f5abf | |||
2689901637 | |||
87dd70c4b6 | |||
451ac5b2b6 | |||
a8042ab20d | |||
8a36e72640 | |||
1519216d08 | |||
f2cd05c29d | |||
5e1fb1dac5 | |||
d1a6b293e9 | |||
be43143891 | |||
53bc36f628 | |||
0f360fa806 | |||
00389936a8 | |||
04f75d57e9 | |||
6a0344e821 | |||
2fcf3da29f | |||
c8495b1695 | |||
d81d02f11d | |||
be5f1b67c8 | |||
ec3aec4dff | |||
9a4eed9453 | |||
30abf7833d | |||
d9c0d24e58 | |||
3f121dc681 | |||
3ea0f405f2 | |||
1811342cb6 | |||
16aa52c071 | |||
b68e04ba80 | |||
45e8d68234 | |||
760a99098a | |||
e6a88852b3 | |||
b97f611a3c | |||
10436096d1 | |||
ad11782029 | |||
8bf3aa7f56 | |||
a4b03bc216 | |||
946694b8a5 | |||
7345eceab3 | |||
9b50393e15 | |||
6c8655798a | |||
86e99e4664 | |||
6b99b85f9e | |||
92fea7f56b | |||
717264b452 | |||
68bd39e130 | |||
25a1226667 | |||
d4d2dbcbda | |||
6d9b35396f | |||
0c177fb40f | |||
f38198a39d | |||
5f7789128e | |||
3f45535f89 | |||
2b0dbabb5c | |||
be7f643f14 | |||
6a08bdd40e | |||
333ac74d91 | |||
17ac9b38fb | |||
172dd40648 | |||
c97c7eee8e | |||
9fcfc44dc3 | |||
0d3475e229 | |||
70e3377eac | |||
adaa652d0d | |||
921005edb3 | |||
d43a8a5907 | |||
588e8eb87f | |||
df8d092a84 | |||
438a2e2abc | |||
305c218888 | |||
db60782c48 | |||
4f32ecc26e | |||
f84aa88202 | |||
921da1cb3f | |||
14de4ee297 | |||
7136b5a450 | |||
116c79d3de | |||
aaf0283f66 | |||
259f47664a | |||
9a47f2d03a | |||
b5d5085fd5 |
2
.clangd
2
.clangd
@@ -1,2 +1,2 @@
|
|||||||
CompileFlags:
|
CompileFlags:
|
||||||
Add: [-DENABLE_MAIN, -UNDEBUG, -DENABLE_FUZZ, -fexceptions]
|
Add: [-DENABLE_MAIN, -UNDEBUG, -DENABLE_FUZZ, -DTHREAD_TEST, -fexceptions]
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
.cache
|
.cache
|
||||||
|
__pycache__
|
||||||
build
|
build
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||||
rev: b111689e7b5cba60be3c62d5db2bd1357f4d36ca
|
rev: 6d365699efc33b1b432eab5b4ae331a19e1857de # frozen: v18.1.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: clang-format
|
- id: clang-format
|
||||||
exclude: ".*third_party/.*"
|
exclude: ".*third_party/.*"
|
||||||
- repo: https://github.com/cheshirekow/cmake-format-precommit
|
- repo: https://github.com/cheshirekow/cmake-format-precommit
|
||||||
rev: e2c2116d86a80e72e7146a06e68b7c228afc6319
|
rev: e2c2116d86a80e72e7146a06e68b7c228afc6319 # frozen: v0.6.13
|
||||||
hooks:
|
hooks:
|
||||||
- id: cmake-format
|
- id: cmake-format
|
||||||
- repo: local
|
- repo: local
|
||||||
@@ -13,6 +13,22 @@ repos:
|
|||||||
- id: debug verbose check
|
- id: debug verbose check
|
||||||
name: disallow checking in DEBUG_VERBOSE=1
|
name: disallow checking in DEBUG_VERBOSE=1
|
||||||
description: disallow checking in DEBUG_VERBOSE=1
|
description: disallow checking in DEBUG_VERBOSE=1
|
||||||
entry: '^#define DEBUG_VERBOSE 1$'
|
entry: "^#define DEBUG_VERBOSE 1$"
|
||||||
language: pygrep
|
language: pygrep
|
||||||
types: [c++]
|
types: [c++]
|
||||||
|
- repo: local
|
||||||
|
hooks:
|
||||||
|
- id: debug verbose check
|
||||||
|
name: disallow checking in SHOW_MEMORY=1
|
||||||
|
description: disallow checking in SHOW_MEMORY=1
|
||||||
|
entry: "^#define SHOW_MEMORY 1$"
|
||||||
|
language: pygrep
|
||||||
|
types: [c++]
|
||||||
|
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||||
|
rev: a23f6b85d0fdd5bb9d564e2579e678033debbdff # frozen: v0.10.0.1
|
||||||
|
hooks:
|
||||||
|
- id: shellcheck
|
||||||
|
- repo: https://github.com/psf/black
|
||||||
|
rev: 552baf822992936134cbd31a38f69c8cfe7c0f05 # frozen: 24.3.0
|
||||||
|
hooks:
|
||||||
|
- id: black
|
||||||
|
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"files.associations": {
|
||||||
|
"*.tikz": "latex"
|
||||||
|
},
|
||||||
|
"latex-workshop.view.pdf.invertMode.enabled": "compat",
|
||||||
|
"latex-workshop.view.pdf.invert": 1,
|
||||||
|
"latex-workshop.view.pdf.invertMode.sepia": 1,
|
||||||
|
"latex-workshop.view.pdf.invertMode.grayscale": 0.5
|
||||||
|
}
|
883
Bench.cpp
883
Bench.cpp
@@ -2,622 +2,41 @@
|
|||||||
#include "Internal.h"
|
#include "Internal.h"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#define ANKERL_NANOBENCH_IMPLEMENT
|
#if SHOW_MEMORY
|
||||||
|
void showMemory(const ConflictSet &cs);
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "third_party/nanobench.h"
|
#include "third_party/nanobench.h"
|
||||||
|
|
||||||
std::span<const uint8_t> keyAfter(Arena &arena, std::span<const uint8_t> key) {
|
constexpr int kNumKeys = 1000000;
|
||||||
auto result =
|
|
||||||
std::span<uint8_t>(new (arena) uint8_t[key.size() + 1], key.size() + 1);
|
constexpr int kOpsPerTx = 100;
|
||||||
memcpy(result.data(), key.data(), key.size());
|
|
||||||
result[result.size() - 1] = 0;
|
constexpr int kPrefixLen = 0;
|
||||||
|
|
||||||
|
constexpr int kMvccWindow = 100000;
|
||||||
|
|
||||||
|
TrivialSpan makeKey(Arena &arena, int index) {
|
||||||
|
|
||||||
|
uint8_t *buf = new (arena) uint8_t[4 + kPrefixLen];
|
||||||
|
auto result = TrivialSpan{buf, 4 + kPrefixLen};
|
||||||
|
index = __builtin_bswap32(index);
|
||||||
|
memset(buf, 0, kPrefixLen);
|
||||||
|
memcpy(buf, &index, 4);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
ConflictSet::ReadRange singleton(Arena &arena, TrivialSpan key) {
|
||||||
|
uint8_t *buf = new (arena) uint8_t[key.size() + 1];
|
||||||
using Version = int64_t;
|
auto r = TrivialSpan(buf, key.size() + 1);
|
||||||
#define force_inline __attribute__((always_inline))
|
memcpy(buf, key.data(), key.size());
|
||||||
using StringRef = std::span<const uint8_t>;
|
buf[key.size()] = 0;
|
||||||
|
return {{key.data(), int(key.size())}, {r.data(), int(r.size())}, 0};
|
||||||
struct KeyRangeRef {
|
|
||||||
StringRef begin;
|
|
||||||
StringRef end;
|
|
||||||
KeyRangeRef() {}
|
|
||||||
KeyRangeRef(StringRef begin, StringRef end) : begin(begin), end(end) {}
|
|
||||||
KeyRangeRef(Arena &arena, StringRef begin)
|
|
||||||
: begin(begin), end(keyAfter(arena, begin)) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
static thread_local uint32_t g_seed = 0;
|
|
||||||
|
|
||||||
static inline int skfastrand() {
|
|
||||||
g_seed = g_seed * 1664525L + 1013904223L;
|
|
||||||
return g_seed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int compare(const StringRef &a, const StringRef &b) {
|
ConflictSet::ReadRange prefixRange(Arena &arena, TrivialSpan key) {
|
||||||
int c = memcmp(a.data(), b.data(), std::min(a.size(), b.size()));
|
|
||||||
if (c < 0)
|
|
||||||
return -1;
|
|
||||||
if (c > 0)
|
|
||||||
return +1;
|
|
||||||
if (a.size() < b.size())
|
|
||||||
return -1;
|
|
||||||
if (a.size() == b.size())
|
|
||||||
return 0;
|
|
||||||
return +1;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ReadConflictRange {
|
|
||||||
StringRef begin, end;
|
|
||||||
Version version;
|
|
||||||
|
|
||||||
ReadConflictRange() {}
|
|
||||||
ReadConflictRange(StringRef begin, StringRef end, Version version)
|
|
||||||
: begin(begin), end(end), version(version) {}
|
|
||||||
bool operator<(const ReadConflictRange &rhs) const {
|
|
||||||
return compare(begin, rhs.begin) < 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// level.
|
|
||||||
struct Node {
|
|
||||||
int level() const { return nPointers - 1; }
|
|
||||||
uint8_t *value() {
|
|
||||||
return end() + nPointers * (sizeof(Node *) + sizeof(Version));
|
|
||||||
}
|
|
||||||
int length() const { return valueLength; }
|
|
||||||
|
|
||||||
// Returns the next node pointer at the given level.
|
|
||||||
Node *getNext(int level) { return *((Node **)end() + level); }
|
|
||||||
// Sets the next node pointer at the given level.
|
|
||||||
void setNext(int level, Node *n) { *((Node **)end() + level) = n; }
|
|
||||||
|
|
||||||
// Returns the max version at the given level.
|
|
||||||
Version getMaxVersion(int i) const {
|
|
||||||
return ((Version *)(end() + nPointers * sizeof(Node *)))[i];
|
|
||||||
}
|
|
||||||
// Sets the max version at the given level.
|
|
||||||
void setMaxVersion(int i, Version v) {
|
|
||||||
((Version *)(end() + nPointers * sizeof(Node *)))[i] = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return a node with initialized value but uninitialized pointers
|
|
||||||
// Memory layout: *this, (level+1) Node*, (level+1) Version, value
|
|
||||||
static Node *create(const StringRef &value, int level) {
|
|
||||||
int nodeSize = sizeof(Node) + value.size() +
|
|
||||||
(level + 1) * (sizeof(Node *) + sizeof(Version));
|
|
||||||
|
|
||||||
Node *n;
|
|
||||||
n = (Node *)new char[nodeSize];
|
|
||||||
|
|
||||||
n->nPointers = level + 1;
|
|
||||||
|
|
||||||
n->valueLength = value.size();
|
|
||||||
if (value.size() > 0) {
|
|
||||||
memcpy(n->value(), value.data(), value.size());
|
|
||||||
}
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre: level>0, all lower level nodes between this and getNext(level) have
|
|
||||||
// correct maxversions
|
|
||||||
void calcVersionForLevel(int level) {
|
|
||||||
Node *end = getNext(level);
|
|
||||||
Version v = getMaxVersion(level - 1);
|
|
||||||
for (Node *x = getNext(level - 1); x != end; x = x->getNext(level - 1))
|
|
||||||
v = std::max(v, x->getMaxVersion(level - 1));
|
|
||||||
setMaxVersion(level, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
void destroy() { delete[](char *) this; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
int getNodeSize() const {
|
|
||||||
return sizeof(Node) + valueLength +
|
|
||||||
nPointers * (sizeof(Node *) + sizeof(Version));
|
|
||||||
}
|
|
||||||
// Returns the first Node* pointer
|
|
||||||
uint8_t *end() { return (uint8_t *)(this + 1); }
|
|
||||||
uint8_t const *end() const { return (uint8_t const *)(this + 1); }
|
|
||||||
int nPointers, valueLength;
|
|
||||||
};
|
|
||||||
|
|
||||||
static force_inline bool less(const uint8_t *a, int aLen, const uint8_t *b,
|
|
||||||
int bLen) {
|
|
||||||
int c = memcmp(a, b, std::min(aLen, bLen));
|
|
||||||
if (c < 0)
|
|
||||||
return true;
|
|
||||||
if (c > 0)
|
|
||||||
return false;
|
|
||||||
return aLen < bLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node *header;
|
|
||||||
|
|
||||||
void destroy() {
|
|
||||||
Node *next, *x;
|
|
||||||
for (x = header; x; x = next) {
|
|
||||||
next = x->getNext(0);
|
|
||||||
x->destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Points the location (i.e., Node*) that value would appear in the SkipList.
|
|
||||||
// If the "value" is in the list, then finger[0] points to that exact node;
|
|
||||||
// otherwise, the finger points to Nodes that the value should be inserted
|
|
||||||
// before. Note the SkipList organizes all nodes at level 0, higher levels
|
|
||||||
// contain jump pointers.
|
|
||||||
struct Finger {
|
|
||||||
Node *finger[MaxLevels]; // valid for levels >= level
|
|
||||||
int level = MaxLevels;
|
|
||||||
Node *x = nullptr;
|
|
||||||
Node *alreadyChecked = nullptr;
|
|
||||||
StringRef value;
|
|
||||||
|
|
||||||
Finger() = default;
|
|
||||||
Finger(Node *header, const StringRef &ptr) : x(header), value(ptr) {}
|
|
||||||
|
|
||||||
void init(const StringRef &value, Node *header) {
|
|
||||||
this->value = value;
|
|
||||||
x = header;
|
|
||||||
alreadyChecked = nullptr;
|
|
||||||
level = MaxLevels;
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre: !finished()
|
|
||||||
force_inline void prefetch() {
|
|
||||||
Node *next = x->getNext(0);
|
|
||||||
__builtin_prefetch(next);
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre: !finished()
|
|
||||||
// Advances the pointer at the current level to a Node that's >= finger's
|
|
||||||
// value if possible; or move to the next level (i.e., level--). Returns
|
|
||||||
// true if we have advanced to the next level
|
|
||||||
force_inline bool advance() {
|
|
||||||
Node *next = x->getNext(level - 1);
|
|
||||||
|
|
||||||
if (next == alreadyChecked ||
|
|
||||||
!less(next->value(), next->length(), value.data(), value.size())) {
|
|
||||||
alreadyChecked = next;
|
|
||||||
level--;
|
|
||||||
finger[level] = x;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
x = next;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// pre: !finished()
|
|
||||||
force_inline void nextLevel() {
|
|
||||||
while (!advance())
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
force_inline bool finished() const { return level == 0; }
|
|
||||||
|
|
||||||
// Returns if the finger value is found in the SkipList.
|
|
||||||
force_inline Node *found() const {
|
|
||||||
// valid after finished returns true
|
|
||||||
Node *n = finger[0]->getNext(
|
|
||||||
0); // or alreadyChecked, but that is more easily invalidated
|
|
||||||
if (n && n->length() == value.size() &&
|
|
||||||
!memcmp(n->value(), value.data(), value.size()))
|
|
||||||
return n;
|
|
||||||
else
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
StringRef getValue() const {
|
|
||||||
Node *n = finger[0]->getNext(0);
|
|
||||||
return n ? StringRef(n->value(), n->length()) : StringRef();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns the total number of nodes in the list.
|
|
||||||
int count() const {
|
|
||||||
int count = 0;
|
|
||||||
Node *x = header->getNext(0);
|
|
||||||
while (x) {
|
|
||||||
x = x->getNext(0);
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
explicit SkipList(Version version = 0) {
|
|
||||||
header = Node::create(StringRef(), MaxLevels - 1);
|
|
||||||
for (int l = 0; l < MaxLevels; l++) {
|
|
||||||
header->setNext(l, nullptr);
|
|
||||||
header->setMaxVersion(l, version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
~SkipList() { destroy(); }
|
|
||||||
SkipList(SkipList &&other) noexcept : header(other.header) {
|
|
||||||
other.header = nullptr;
|
|
||||||
}
|
|
||||||
void operator=(SkipList &&other) noexcept {
|
|
||||||
destroy();
|
|
||||||
header = other.header;
|
|
||||||
other.header = nullptr;
|
|
||||||
}
|
|
||||||
void swap(SkipList &other) { std::swap(header, other.header); }
|
|
||||||
|
|
||||||
void addConflictRanges(const Finger *fingers, int rangeCount,
|
|
||||||
Version *version) {
|
|
||||||
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)
|
|
||||||
insert(endF, endF.finger[0]->getMaxVersion(0));
|
|
||||||
|
|
||||||
remove(startF, endF);
|
|
||||||
insert(startF, version[r]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void detectConflicts(ReadConflictRange *ranges, int count,
|
|
||||||
ConflictSet::Result *transactionConflictStatus) const {
|
|
||||||
const int M = 16;
|
|
||||||
int nextJob[M];
|
|
||||||
CheckMax inProgress[M];
|
|
||||||
if (!count)
|
|
||||||
return;
|
|
||||||
|
|
||||||
int started = std::min(M, count);
|
|
||||||
for (int i = 0; i < started; i++) {
|
|
||||||
inProgress[i].init(ranges[i], header, transactionConflictStatus + i);
|
|
||||||
nextJob[i] = i + 1;
|
|
||||||
}
|
|
||||||
nextJob[started - 1] = 0;
|
|
||||||
|
|
||||||
int prevJob = started - 1;
|
|
||||||
int job = 0;
|
|
||||||
// vtune: 340 parts
|
|
||||||
while (true) {
|
|
||||||
if (inProgress[job].advance()) {
|
|
||||||
if (started == count) {
|
|
||||||
if (prevJob == job)
|
|
||||||
break;
|
|
||||||
nextJob[prevJob] = nextJob[job];
|
|
||||||
job = prevJob;
|
|
||||||
} else {
|
|
||||||
int temp = started++;
|
|
||||||
inProgress[job].init(ranges[temp], header,
|
|
||||||
transactionConflictStatus + temp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
prevJob = job;
|
|
||||||
job = nextJob[job];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void find(const StringRef *values, Finger *results, int *temp, int count) {
|
|
||||||
// Relying on the ordering of values, descend until the values aren't all in
|
|
||||||
// the same part of the tree
|
|
||||||
|
|
||||||
// vtune: 11 parts
|
|
||||||
results[0].init(values[0], header);
|
|
||||||
const StringRef &endValue = values[count - 1];
|
|
||||||
while (results[0].level > 1) {
|
|
||||||
results[0].nextLevel();
|
|
||||||
Node *ac = results[0].alreadyChecked;
|
|
||||||
if (ac &&
|
|
||||||
less(ac->value(), ac->length(), endValue.data(), endValue.size()))
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init all the other fingers to start descending where we stopped
|
|
||||||
// the first one
|
|
||||||
|
|
||||||
// SOMEDAY: this loop showed up on vtune, could be faster?
|
|
||||||
// vtune: 8 parts
|
|
||||||
int startLevel = results[0].level + 1;
|
|
||||||
Node *x = startLevel < MaxLevels ? results[0].finger[startLevel] : header;
|
|
||||||
for (int i = 1; i < count; i++) {
|
|
||||||
results[i].level = startLevel;
|
|
||||||
results[i].x = x;
|
|
||||||
results[i].alreadyChecked = nullptr;
|
|
||||||
results[i].value = values[i];
|
|
||||||
for (int j = startLevel; j < MaxLevels; j++)
|
|
||||||
results[i].finger[j] = results[0].finger[j];
|
|
||||||
}
|
|
||||||
|
|
||||||
int *nextJob = temp;
|
|
||||||
for (int i = 0; i < count - 1; i++)
|
|
||||||
nextJob[i] = i + 1;
|
|
||||||
nextJob[count - 1] = 0;
|
|
||||||
|
|
||||||
int prevJob = count - 1;
|
|
||||||
int job = 0;
|
|
||||||
|
|
||||||
// vtune: 225 parts
|
|
||||||
while (true) {
|
|
||||||
Finger *f = &results[job];
|
|
||||||
f->advance();
|
|
||||||
if (f->finished()) {
|
|
||||||
if (prevJob == job)
|
|
||||||
break;
|
|
||||||
nextJob[prevJob] = nextJob[job];
|
|
||||||
} else {
|
|
||||||
f->prefetch();
|
|
||||||
prevJob = job;
|
|
||||||
}
|
|
||||||
job = nextJob[job];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int removeBefore(Version v, Finger &f, int nodeCount) {
|
|
||||||
// f.x, f.alreadyChecked?
|
|
||||||
|
|
||||||
int removedCount = 0;
|
|
||||||
bool wasAbove = true;
|
|
||||||
while (nodeCount--) {
|
|
||||||
Node *x = f.finger[0]->getNext(0);
|
|
||||||
if (!x)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// double prefetch gives +25% speed (single threaded)
|
|
||||||
Node *next = x->getNext(0);
|
|
||||||
__builtin_prefetch(next);
|
|
||||||
next = x->getNext(1);
|
|
||||||
__builtin_prefetch(next);
|
|
||||||
|
|
||||||
bool isAbove = x->getMaxVersion(0) >= v;
|
|
||||||
if (isAbove || wasAbove) { // f.nextItem
|
|
||||||
for (int l = 0; l <= x->level(); l++)
|
|
||||||
f.finger[l] = x;
|
|
||||||
} else { // f.eraseItem
|
|
||||||
removedCount++;
|
|
||||||
for (int l = 0; l <= x->level(); l++)
|
|
||||||
f.finger[l]->setNext(l, x->getNext(l));
|
|
||||||
for (int i = 1; i <= x->level(); i++)
|
|
||||||
f.finger[i]->setMaxVersion(
|
|
||||||
i, std::max(f.finger[i]->getMaxVersion(i), x->getMaxVersion(i)));
|
|
||||||
x->destroy();
|
|
||||||
}
|
|
||||||
wasAbove = isAbove;
|
|
||||||
}
|
|
||||||
|
|
||||||
return removedCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
void remove(const Finger &start, const Finger &end) {
|
|
||||||
if (start.finger[0] == end.finger[0])
|
|
||||||
return;
|
|
||||||
|
|
||||||
Node *x = start.finger[0]->getNext(0);
|
|
||||||
|
|
||||||
// vtune says: this loop is the expensive parts (6 parts)
|
|
||||||
for (int i = 0; i < MaxLevels; i++)
|
|
||||||
if (start.finger[i] != end.finger[i])
|
|
||||||
start.finger[i]->setNext(i, end.finger[i]->getNext(i));
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
Node *next = x->getNext(0);
|
|
||||||
x->destroy();
|
|
||||||
if (x == end.finger[0])
|
|
||||||
break;
|
|
||||||
x = next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void insert(const Finger &f, Version version) {
|
|
||||||
int level = randomLevel();
|
|
||||||
// std::cout << std::string((const char*)value,length) << " level: " <<
|
|
||||||
// level << std::endl;
|
|
||||||
Node *x = Node::create(f.value, level);
|
|
||||||
x->setMaxVersion(0, version);
|
|
||||||
for (int i = 0; i <= level; i++) {
|
|
||||||
x->setNext(i, f.finger[i]->getNext(i));
|
|
||||||
f.finger[i]->setNext(i, x);
|
|
||||||
}
|
|
||||||
// vtune says: this loop is the costly part of this function
|
|
||||||
for (int i = 1; i <= level; i++) {
|
|
||||||
f.finger[i]->calcVersionForLevel(i);
|
|
||||||
x->calcVersionForLevel(i);
|
|
||||||
}
|
|
||||||
for (int i = level + 1; i < MaxLevels; i++) {
|
|
||||||
Version v = f.finger[i]->getMaxVersion(i);
|
|
||||||
if (v >= version)
|
|
||||||
break;
|
|
||||||
f.finger[i]->setMaxVersion(i, version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CheckMax {
|
|
||||||
Finger start, end;
|
|
||||||
Version version;
|
|
||||||
ConflictSet::Result *result;
|
|
||||||
int state;
|
|
||||||
|
|
||||||
void init(const ReadConflictRange &r, Node *header,
|
|
||||||
ConflictSet::Result *result) {
|
|
||||||
this->start.init(r.begin, header);
|
|
||||||
this->end.init(r.end, header);
|
|
||||||
this->version = r.version;
|
|
||||||
this->state = 0;
|
|
||||||
this->result = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool noConflict() const { return true; }
|
|
||||||
bool conflict() {
|
|
||||||
*result = ConflictSet::Conflict;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if finished
|
|
||||||
force_inline bool advance() {
|
|
||||||
if (*result == ConflictSet::TooOld) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
switch (state) {
|
|
||||||
case 0:
|
|
||||||
// find where start and end fingers diverge
|
|
||||||
while (true) {
|
|
||||||
if (!start.advance()) {
|
|
||||||
start.prefetch();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
end.x = start.x;
|
|
||||||
while (!end.advance())
|
|
||||||
;
|
|
||||||
|
|
||||||
int l = start.level;
|
|
||||||
if (start.finger[l] != end.finger[l])
|
|
||||||
break;
|
|
||||||
// accept if the range spans the check range, but does not have a
|
|
||||||
// greater version
|
|
||||||
if (start.finger[l]->getMaxVersion(l) <= version)
|
|
||||||
return noConflict();
|
|
||||||
if (l == 0)
|
|
||||||
return conflict();
|
|
||||||
}
|
|
||||||
state = 1;
|
|
||||||
case 1: {
|
|
||||||
// check the end side of the pyramid
|
|
||||||
Node *e = end.finger[end.level];
|
|
||||||
while (e->getMaxVersion(end.level) > version) {
|
|
||||||
if (end.finished())
|
|
||||||
return conflict();
|
|
||||||
end.nextLevel();
|
|
||||||
Node *f = end.finger[end.level];
|
|
||||||
while (e != f) {
|
|
||||||
if (e->getMaxVersion(end.level) > version)
|
|
||||||
return conflict();
|
|
||||||
e = e->getNext(end.level);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the start side of the pyramid
|
|
||||||
Node *s = end.finger[start.level];
|
|
||||||
while (true) {
|
|
||||||
Node *nextS = start.finger[start.level]->getNext(start.level);
|
|
||||||
Node *p = nextS;
|
|
||||||
while (p != s) {
|
|
||||||
if (p->getMaxVersion(start.level) > version)
|
|
||||||
return conflict();
|
|
||||||
p = p->getNext(start.level);
|
|
||||||
}
|
|
||||||
if (start.finger[start.level]->getMaxVersion(start.level) <= version)
|
|
||||||
return noConflict();
|
|
||||||
s = nextS;
|
|
||||||
if (start.finished()) {
|
|
||||||
if (nextS->length() == start.value.size() &&
|
|
||||||
!memcmp(nextS->value(), start.value.data(), start.value.size()))
|
|
||||||
return noConflict();
|
|
||||||
else
|
|
||||||
return conflict();
|
|
||||||
}
|
|
||||||
start.nextLevel();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
__builtin_unreachable();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SkipListConflictSet {
|
|
||||||
SkipListConflictSet(int64_t oldestVersion)
|
|
||||||
: oldestVersion(oldestVersion), skipList(oldestVersion) {}
|
|
||||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
|
||||||
int count) const {
|
|
||||||
Arena arena;
|
|
||||||
auto *ranges = new (arena) ReadConflictRange[count];
|
|
||||||
for (int i = 0; i < count; ++i) {
|
|
||||||
ranges[i].begin = {reads[i].begin.p, size_t(reads[i].begin.len)};
|
|
||||||
ranges[i].end = {reads[i].end.p,
|
|
||||||
size_t(reads[i].end.len == 0 ? reads[i].begin.len + 1
|
|
||||||
: reads[i].end.len)};
|
|
||||||
ranges[i].version = reads[i].readVersion;
|
|
||||||
if (reads[i].readVersion < oldestVersion) {
|
|
||||||
results[i] = ConflictSet::TooOld;
|
|
||||||
} else {
|
|
||||||
results[i] = ConflictSet::Commit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
skipList.detectConflicts(ranges, count, results);
|
|
||||||
}
|
|
||||||
|
|
||||||
void addWrites(const ConflictSet::WriteRange *writes, int count) {
|
|
||||||
Arena arena;
|
|
||||||
const int stringCount = count * 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;
|
|
||||||
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.p, size_t(w.end.len == 0 ? w.begin.len + 1 : w.end.len)};
|
|
||||||
writeVersions[i] = w.writeVersion;
|
|
||||||
keyUpdates += 2;
|
|
||||||
}
|
|
||||||
skipList.find(values, fingers, temp, ss);
|
|
||||||
skipList.addConflictRanges(fingers, ss / 2, writeVersions);
|
|
||||||
ss = stripeSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setOldestVersion(int64_t 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, 0));
|
|
||||||
removalKey = std::basic_string<uint8_t>(finger.getValue().data(),
|
|
||||||
finger.getValue().size());
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
int64_t keyUpdates = 0;
|
|
||||||
std::basic_string<uint8_t> removalKey;
|
|
||||||
int64_t oldestVersion;
|
|
||||||
SkipList skipList;
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
return {key.data(), int(key.size()), r.data(), int(r.size())};
|
|
||||||
}
|
|
||||||
|
|
||||||
ConflictSet::ReadRange prefixRange(Arena &arena, std::span<const uint8_t> key) {
|
|
||||||
int index;
|
int index;
|
||||||
for (index = key.size() - 1; index >= 0; index--)
|
for (index = key.size() - 1; index >= 0; index--)
|
||||||
if ((key[index]) != 255)
|
if ((key[index]) != 255)
|
||||||
@@ -629,39 +48,18 @@ ConflictSet::ReadRange prefixRange(Arena &arena, std::span<const uint8_t> key) {
|
|||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto r = std::span<uint8_t>(new (arena) uint8_t[index + 1], index + 1);
|
uint8_t *buf = new (arena) uint8_t[index + 1];
|
||||||
memcpy(r.data(), key.data(), index + 1);
|
auto r = TrivialSpan(buf, index + 1);
|
||||||
r[r.size() - 1]++;
|
memcpy(buf, key.data(), index + 1);
|
||||||
return {key.data(), int(key.size()), r.data(), int(r.size())};
|
buf[r.size() - 1]++;
|
||||||
|
return {{key.data(), int(key.size())}, {r.data(), int(r.size())}, 0};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
void benchConflictSet() {
|
||||||
|
|
||||||
constexpr int kNumKeys = 1000000;
|
|
||||||
|
|
||||||
constexpr int kOpsPerTx = 100;
|
|
||||||
|
|
||||||
constexpr int kPrefixLen = 0;
|
|
||||||
|
|
||||||
constexpr int kMvccWindow = 100000;
|
|
||||||
|
|
||||||
std::span<const uint8_t> makeKey(Arena &arena, int index) {
|
|
||||||
|
|
||||||
auto result =
|
|
||||||
std::span<uint8_t>{new (arena) uint8_t[4 + kPrefixLen], 4 + kPrefixLen};
|
|
||||||
index = __builtin_bswap32(index);
|
|
||||||
memset(result.data(), 0, kPrefixLen);
|
|
||||||
memcpy(result.data() + kPrefixLen, &index, 4);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <class ConflictSet_> void benchConflictSet(const std::string &name) {
|
|
||||||
ankerl::nanobench::Bench bench;
|
ankerl::nanobench::Bench bench;
|
||||||
ConflictSet_ cs{0};
|
ConflictSet cs{0};
|
||||||
|
|
||||||
bench.batch(kOpsPerTx);
|
bench.batch(kOpsPerTx);
|
||||||
bench.minEpochIterations(2000);
|
|
||||||
|
|
||||||
int64_t version = 0;
|
int64_t version = 0;
|
||||||
|
|
||||||
@@ -678,21 +76,13 @@ template <class ConflictSet_> void benchConflictSet(const std::string &name) {
|
|||||||
w.begin.len = r.begin.len;
|
w.begin.len = r.begin.len;
|
||||||
w.end.p = r.end.p;
|
w.end.p = r.end.p;
|
||||||
w.end.len = 0;
|
w.end.len = 0;
|
||||||
w.writeVersion = version + 1;
|
|
||||||
writes.push_back(w);
|
writes.push_back(w);
|
||||||
}
|
}
|
||||||
cs.addWrites(writes.data(), writes.size());
|
cs.addWrites(writes.data(), writes.size(), version + 1);
|
||||||
++version;
|
++version;
|
||||||
}
|
}
|
||||||
|
|
||||||
// I don't know why std::less didn't work /shrug
|
auto points = set<TrivialSpan, std::less<>>(arena);
|
||||||
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);
|
|
||||||
|
|
||||||
while (points.size() < kOpsPerTx * 2 + 1) {
|
while (points.size() < kOpsPerTx * 2 + 1) {
|
||||||
// TODO don't use rand?
|
// TODO don't use rand?
|
||||||
@@ -712,11 +102,10 @@ template <class ConflictSet_> void benchConflictSet(const std::string &name) {
|
|||||||
w.begin.len = begin.size();
|
w.begin.len = begin.size();
|
||||||
w.end.p = end.data();
|
w.end.p = end.data();
|
||||||
w.end.len = end.size();
|
w.end.len = end.size();
|
||||||
w.writeVersion = version + 1;
|
|
||||||
writes.push_back(w);
|
writes.push_back(w);
|
||||||
}
|
}
|
||||||
|
cs.addWrites(writes.data(), kOpsPerTx, version + 1);
|
||||||
++version;
|
++version;
|
||||||
cs.addWrites(writes.data(), kOpsPerTx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -732,7 +121,7 @@ template <class ConflictSet_> void benchConflictSet(const std::string &name) {
|
|||||||
|
|
||||||
auto *results = new (arena) ConflictSet::Result[kOpsPerTx];
|
auto *results = new (arena) ConflictSet::Result[kOpsPerTx];
|
||||||
|
|
||||||
bench.run(name + " (point reads)",
|
bench.run("point reads",
|
||||||
[&]() { cs.check(reads.data(), results, kOpsPerTx); });
|
[&]() { cs.check(reads.data(), results, kOpsPerTx); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -748,7 +137,7 @@ template <class ConflictSet_> void benchConflictSet(const std::string &name) {
|
|||||||
|
|
||||||
auto *results = new (arena) ConflictSet::Result[kOpsPerTx];
|
auto *results = new (arena) ConflictSet::Result[kOpsPerTx];
|
||||||
|
|
||||||
bench.run(name + " (prefix reads)",
|
bench.run("prefix reads",
|
||||||
[&]() { cs.check(reads.data(), results, kOpsPerTx); });
|
[&]() { cs.check(reads.data(), results, kOpsPerTx); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -769,7 +158,7 @@ template <class ConflictSet_> void benchConflictSet(const std::string &name) {
|
|||||||
|
|
||||||
auto *results = new (arena) ConflictSet::Result[kOpsPerTx];
|
auto *results = new (arena) ConflictSet::Result[kOpsPerTx];
|
||||||
|
|
||||||
bench.run(name + " (range reads)",
|
bench.run("range reads",
|
||||||
[&]() { cs.check(reads.data(), results, kOpsPerTx); });
|
[&]() { cs.check(reads.data(), results, kOpsPerTx); });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -787,13 +176,14 @@ template <class ConflictSet_> void benchConflictSet(const std::string &name) {
|
|||||||
++iter;
|
++iter;
|
||||||
}
|
}
|
||||||
|
|
||||||
bench.run(name + " (point writes)", [&]() {
|
while (version < kMvccWindow) {
|
||||||
auto v = ++version;
|
auto v = ++version;
|
||||||
for (auto &w : writes) {
|
cs.addWrites(writes.data(), 1, v);
|
||||||
w.writeVersion = v;
|
}
|
||||||
}
|
|
||||||
cs.addWrites(writes.data(), writes.size());
|
bench.run("point writes", [&]() {
|
||||||
cs.setOldestVersion(std::max<int64_t>(version - kMvccWindow, 0));
|
auto v = ++version;
|
||||||
|
cs.addWrites(writes.data(), writes.size(), v);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -811,13 +201,9 @@ template <class ConflictSet_> void benchConflictSet(const std::string &name) {
|
|||||||
++iter;
|
++iter;
|
||||||
}
|
}
|
||||||
|
|
||||||
bench.run(name + " (prefix writes)", [&]() {
|
bench.run("prefix writes", [&]() {
|
||||||
auto v = ++version;
|
auto v = ++version;
|
||||||
for (auto &w : writes) {
|
cs.addWrites(writes.data(), writes.size(), v);
|
||||||
w.writeVersion = v;
|
|
||||||
}
|
|
||||||
cs.addWrites(writes.data(), writes.size());
|
|
||||||
cs.setOldestVersion(std::max<int64_t>(version - kMvccWindow, 0));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -835,45 +221,150 @@ template <class ConflictSet_> void benchConflictSet(const std::string &name) {
|
|||||||
writes.push_back(w);
|
writes.push_back(w);
|
||||||
}
|
}
|
||||||
|
|
||||||
bench.run(name + " (range writes)", [&]() {
|
bench.run("range writes", [&]() {
|
||||||
auto v = ++version;
|
auto v = ++version;
|
||||||
for (auto &w : writes) {
|
cs.addWrites(writes.data(), writes.size(), v);
|
||||||
w.writeVersion = v;
|
});
|
||||||
}
|
}
|
||||||
cs.addWrites(writes.data(), writes.size());
|
|
||||||
cs.setOldestVersion(std::max<int64_t>(version - kMvccWindow, 0));
|
bench.batch(1);
|
||||||
|
|
||||||
|
bench.warmup(10000);
|
||||||
|
|
||||||
|
{
|
||||||
|
bench.run("monotonic increasing point writes", [&]() {
|
||||||
|
auto v = ++version;
|
||||||
|
ConflictSet::WriteRange w;
|
||||||
|
|
||||||
|
uint8_t b[9];
|
||||||
|
b[8] = 0;
|
||||||
|
auto x = __builtin_bswap64(version);
|
||||||
|
memcpy(b, &x, 8);
|
||||||
|
|
||||||
|
w.begin.p = b;
|
||||||
|
w.begin.len = 8;
|
||||||
|
w.end.len = 0;
|
||||||
|
w.end.p = b;
|
||||||
|
cs.addWrites(&w, 1, v);
|
||||||
|
cs.setOldestVersion(version - kMvccWindow);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(void) {
|
constexpr int kKeyLenForWorstCase = 50;
|
||||||
benchConflictSet<SkipListConflictSet>("skip list");
|
|
||||||
benchConflictSet<ConflictSet>("radix tree");
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
|
void benchWorstCaseForRadixRangeRead() {
|
||||||
// TestDriver<SkipListConflictSet> driver{data, size};
|
ankerl::nanobench::Bench bench;
|
||||||
|
|
||||||
// for (;;) {
|
std::unique_ptr<ConflictSet> cs[256];
|
||||||
// bool done = driver.next();
|
for (int i = 0; i < 256; ++i) {
|
||||||
// if (!driver.ok) {
|
cs[i] =
|
||||||
// // debugPrintDot(stdout, driver.cs.root);
|
std::make_unique<ConflictSet>(worstCaseConflictSetForRadixRangeRead(i));
|
||||||
// // fflush(stdout);
|
}
|
||||||
// abort();
|
|
||||||
// }
|
|
||||||
// #if DEBUG_VERBOSE && !defined(NDEBUG)
|
|
||||||
// fprintf(stderr, "Check correctness\n");
|
|
||||||
// #endif
|
|
||||||
// // bool success = checkCorrectness(driver.cs.root);
|
|
||||||
// // if (!success) {
|
|
||||||
// // debugPrintDot(stdout, driver.cs.root);
|
|
||||||
// // fflush(stdout);
|
|
||||||
// // abort();
|
|
||||||
// // }
|
|
||||||
// if (done) {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return 0;
|
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();
|
||||||
|
}
|
||||||
|
423
CMakeLists.txt
423
CMakeLists.txt
@@ -1,12 +1,22 @@
|
|||||||
cmake_minimum_required(VERSION 3.18)
|
cmake_minimum_required(VERSION 3.18)
|
||||||
project(
|
project(
|
||||||
conflict_set
|
conflict-set
|
||||||
VERSION 0.0.1
|
VERSION 0.0.14
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
|
"A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys."
|
||||||
|
HOMEPAGE_URL "https://git.weaselab.dev/weaselab/conflict-set"
|
||||||
LANGUAGES C CXX)
|
LANGUAGES C CXX)
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
|
||||||
|
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)
|
||||||
|
include(CheckIncludeFileCXX)
|
||||||
|
include(CheckCXXSourceCompiles)
|
||||||
|
|
||||||
set(DEFAULT_BUILD_TYPE "Release")
|
set(DEFAULT_BUILD_TYPE "Release")
|
||||||
|
|
||||||
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
||||||
@@ -21,16 +31,72 @@ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
|
|||||||
"MinSizeRel" "RelWithDebInfo")
|
"MinSizeRel" "RelWithDebInfo")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_compile_options(-fdata-sections -ffunction-sections)
|
add_compile_options(
|
||||||
|
-Werror=switch-enum -Wswitch-enum -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()
|
||||||
|
list(APPEND CMAKE_REQUIRED_LINK_OPTIONS ${full_relro_flags})
|
||||||
|
check_cxx_source_compiles("int main(){}" HAS_FULL_RELRO FAIL_REGEX "warning:")
|
||||||
|
if(HAS_FULL_RELRO)
|
||||||
|
add_link_options(${full_relro_flags})
|
||||||
|
endif()
|
||||||
|
cmake_pop_check_state()
|
||||||
|
|
||||||
|
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
|
||||||
|
"warning:")
|
||||||
|
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
|
# This is encouraged according to
|
||||||
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
# https://valgrind.org/docs/manual/manual-core-adv.html#manual-core-adv.clientreq
|
||||||
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/third_party/valgrind)
|
include_directories(SYSTEM ${CMAKE_CURRENT_SOURCE_DIR}/third_party/valgrind)
|
||||||
|
|
||||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
add_compile_options($<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
||||||
add_compile_options(-Wno-maybe-uninitialized
|
|
||||||
$<$<COMPILE_LANGUAGE:CXX>:-Wno-invalid-offsetof>)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
add_link_options(-Wl,-dead_strip)
|
add_link_options(-Wl,-dead_strip)
|
||||||
@@ -38,66 +104,130 @@ else()
|
|||||||
add_link_options(-Wl,--gc-sections)
|
add_link_options(-Wl,--gc-sections)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(CheckIncludeFileCXX)
|
if(NOT USE_SIMD_FALLBACK)
|
||||||
include(CMakePushCheckState)
|
cmake_push_check_state()
|
||||||
|
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
||||||
|
check_include_file_cxx("immintrin.h" HAS_AVX)
|
||||||
|
if(HAS_AVX)
|
||||||
|
add_compile_options(-mavx)
|
||||||
|
add_compile_definitions(HAS_AVX)
|
||||||
|
endif()
|
||||||
|
cmake_pop_check_state()
|
||||||
|
|
||||||
cmake_push_check_state()
|
check_include_file_cxx("arm_neon.h" HAS_ARM_NEON)
|
||||||
list(APPEND CMAKE_REQUIRED_FLAGS -mavx)
|
if(HAS_ARM_NEON)
|
||||||
check_include_file_cxx("immintrin.h" HAS_AVX)
|
add_compile_definitions(HAS_ARM_NEON)
|
||||||
if(HAS_AVX)
|
endif()
|
||||||
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()
|
||||||
|
|
||||||
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
|
set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "")
|
||||||
|
|
||||||
add_library(${PROJECT_NAME}_object OBJECT ConflictSet.cpp)
|
add_library(${PROJECT_NAME}-object OBJECT ConflictSet.cpp)
|
||||||
target_compile_options(${PROJECT_NAME}_object PRIVATE -fPIC -fno-exceptions
|
target_compile_options(${PROJECT_NAME}-object PRIVATE -fno-exceptions
|
||||||
-fvisibility=hidden)
|
-fvisibility=hidden)
|
||||||
target_include_directories(${PROJECT_NAME}_object
|
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)
|
||||||
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
set_target_properties(
|
||||||
|
${PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
|
||||||
|
"${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)
|
set_target_properties(${PROJECT_NAME} PROPERTIES LINKER_LANGUAGE C)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(HAS_VERSION_SCRIPT)
|
||||||
|
target_link_options(
|
||||||
|
${PROJECT_NAME} PRIVATE
|
||||||
|
LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/linker.map)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
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(NOT APPLE)
|
if(NOT APPLE)
|
||||||
target_link_options(${PROJECT_NAME} PRIVATE
|
|
||||||
LINKER:--version-script=${CMAKE_SOURCE_DIR}/linker.map)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_library(${PROJECT_NAME}_static STATIC
|
|
||||||
$<TARGET_OBJECTS:${PROJECT_NAME}_object>)
|
|
||||||
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
|
||||||
set_target_properties(${PROJECT_NAME}_static PROPERTIES LINKER_LANGUAGE C)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(NOT APPLE AND CMAKE_OBJCOPY)
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
TARGET conflict_set_static
|
TARGET ${PROJECT_NAME}-static
|
||||||
POST_BUILD
|
POST_BUILD
|
||||||
COMMAND
|
COMMAND
|
||||||
${CMAKE_OBJCOPY} --keep-global-symbols=${CMAKE_SOURCE_DIR}/symbols.txt
|
${CMAKE_OBJCOPY}
|
||||||
$<TARGET_FILE:${PROJECT_NAME}_static>)
|
--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()
|
endif()
|
||||||
|
|
||||||
set(TEST_FLAGS -Wall -Wextra -Wpedantic -Wunreachable-code -UNDEBUG)
|
|
||||||
|
|
||||||
include(CTest)
|
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_CURRENT_SOURCE_DIR}/corpus/*)
|
||||||
|
|
||||||
|
# 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_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)
|
||||||
|
|
||||||
|
# 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})
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# ad hoc testing
|
||||||
add_executable(conflict_set_main ConflictSet.cpp)
|
add_executable(conflict_set_main ConflictSet.cpp)
|
||||||
target_include_directories(conflict_set_main
|
target_include_directories(conflict_set_main
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
target_compile_definitions(conflict_set_main PRIVATE ENABLE_MAIN)
|
target_compile_definitions(conflict_set_main PRIVATE ENABLE_MAIN)
|
||||||
|
target_link_libraries(conflict_set_main PRIVATE nanobench)
|
||||||
|
|
||||||
if(NOT APPLE)
|
if(NOT APPLE)
|
||||||
# libfuzzer target, to generate/manage corpus
|
# libfuzzer target, to generate/manage corpus
|
||||||
@@ -120,12 +250,13 @@ if(BUILD_TESTING)
|
|||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# corpus tests
|
# whitebox tests
|
||||||
|
|
||||||
file(GLOB CORPUS_TESTS ${CMAKE_SOURCE_DIR}/corpus/*)
|
|
||||||
|
|
||||||
add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
add_executable(fuzz_driver ConflictSet.cpp FuzzTestDriver.cpp)
|
||||||
target_compile_options(fuzz_driver PRIVATE ${TEST_FLAGS})
|
target_compile_options(fuzz_driver PRIVATE ${TEST_FLAGS})
|
||||||
|
if(NOT CMAKE_CROSSCOMPILING)
|
||||||
|
target_compile_options(fuzz_driver PRIVATE -fsanitize=address,undefined)
|
||||||
|
target_link_options(fuzz_driver PRIVATE -fsanitize=address,undefined)
|
||||||
|
endif()
|
||||||
target_compile_definitions(fuzz_driver PRIVATE ENABLE_FUZZ)
|
target_compile_definitions(fuzz_driver PRIVATE ENABLE_FUZZ)
|
||||||
target_include_directories(fuzz_driver
|
target_include_directories(fuzz_driver
|
||||||
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
@@ -134,66 +265,195 @@ if(BUILD_TESTING)
|
|||||||
add_test(NAME conflict_set_fuzz_${hash} COMMAND fuzz_driver ${TEST})
|
add_test(NAME conflict_set_fuzz_${hash} COMMAND fuzz_driver ${TEST})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
# tsan tests
|
||||||
|
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)
|
||||||
|
target_compile_definitions(tsan_driver PRIVATE ENABLE_FUZZ THREAD_TEST)
|
||||||
|
target_include_directories(tsan_driver
|
||||||
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
foreach(TEST ${CORPUS_TESTS})
|
||||||
|
get_filename_component(hash ${TEST} NAME)
|
||||||
|
add_test(NAME conflict_set_tsan_${hash} COMMAND tsan_driver ${TEST})
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# blackbox tests
|
||||||
add_executable(driver TestDriver.cpp)
|
add_executable(driver TestDriver.cpp)
|
||||||
target_compile_options(driver PRIVATE ${TEST_FLAGS})
|
target_compile_options(driver PRIVATE ${TEST_FLAGS})
|
||||||
target_link_libraries(driver PRIVATE ${PROJECT_NAME})
|
target_link_libraries(driver PRIVATE ${PROJECT_NAME})
|
||||||
|
|
||||||
find_program(VALGRIND_EXE valgrind)
|
|
||||||
if(VALGRIND_EXE)
|
|
||||||
add_test(NAME conflict_set_blackbox_valgrind
|
|
||||||
COMMAND ${VALGRIND_EXE} --error-exitcode=99 --
|
|
||||||
$<TARGET_FILE:driver> ${CORPUS_TESTS})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
foreach(TEST ${CORPUS_TESTS})
|
foreach(TEST ${CORPUS_TESTS})
|
||||||
get_filename_component(hash ${TEST} NAME)
|
get_filename_component(hash ${TEST} NAME)
|
||||||
add_test(NAME conflict_set_blackbox_${hash} COMMAND driver ${TEST})
|
add_test(NAME conflict_set_blackbox_${hash} COMMAND driver ${TEST})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
|
find_program(VALGRIND_EXE valgrind)
|
||||||
|
if(VALGRIND_EXE AND NOT CMAKE_CROSSCOMPILING)
|
||||||
|
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
|
# api smoke tests
|
||||||
|
|
||||||
# c90
|
# c90
|
||||||
add_executable(conflict_set_c_api_test conflict_set_c_api_test.c)
|
add_executable(conflict_set_c_api_test conflict_set_c_api_test.c)
|
||||||
target_compile_options(conflict_set_c_api_test PRIVATE ${TEST_FLAGS})
|
target_compile_options(conflict_set_c_api_test PRIVATE ${TEST_FLAGS})
|
||||||
target_link_libraries(conflict_set_c_api_test PRIVATE ${PROJECT_NAME})
|
target_link_libraries(conflict_set_c_api_test PRIVATE ${PROJECT_NAME})
|
||||||
set_property(TARGET conflict_set_c_api_test PROPERTY C_STANDARD 90)
|
set_target_properties(conflict_set_c_api_test PROPERTIES C_STANDARD 90)
|
||||||
set_property(TARGET conflict_set_c_api_test PROPERTY C_STANDARD_REQUIRED ON)
|
set_target_properties(conflict_set_c_api_test PROPERTIES C_STANDARD_REQUIRED
|
||||||
|
ON)
|
||||||
add_test(NAME conflict_set_c_api_test COMMAND conflict_set_c_api_test)
|
add_test(NAME conflict_set_c_api_test COMMAND conflict_set_c_api_test)
|
||||||
|
|
||||||
# c++98
|
# c++98
|
||||||
add_executable(conflict_set_cxx_api_test conflict_set_cxx_api_test.cpp)
|
add_executable(conflict_set_cxx_api_test conflict_set_cxx_api_test.cpp)
|
||||||
target_compile_options(conflict_set_cxx_api_test PRIVATE ${TEST_FLAGS})
|
target_compile_options(conflict_set_cxx_api_test PRIVATE ${TEST_FLAGS})
|
||||||
target_link_libraries(conflict_set_cxx_api_test PRIVATE ${PROJECT_NAME})
|
target_link_libraries(conflict_set_cxx_api_test
|
||||||
set_property(TARGET conflict_set_cxx_api_test PROPERTY CXX_STANDARD 98)
|
PRIVATE ${PROJECT_NAME}-static)
|
||||||
set_property(TARGET conflict_set_cxx_api_test PROPERTY CXX_STANDARD_REQUIRED
|
set_target_properties(conflict_set_cxx_api_test PROPERTIES CXX_STANDARD 98)
|
||||||
ON)
|
set_target_properties(conflict_set_cxx_api_test
|
||||||
|
PROPERTIES CXX_STANDARD_REQUIRED ON)
|
||||||
add_test(NAME conflict_set_cxx_api_test COMMAND conflict_set_cxx_api_test)
|
add_test(NAME conflict_set_cxx_api_test COMMAND conflict_set_cxx_api_test)
|
||||||
|
|
||||||
if(NOT APPLE AND NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
# symbol visibility tests
|
||||||
|
if(NOT CMAKE_BUILD_TYPE STREQUAL Debug)
|
||||||
|
if(APPLE)
|
||||||
|
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_CURRENT_SOURCE_DIR}/symbol-exports.txt)
|
||||||
|
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||||
|
set(symbol_imports
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/aarch64-symbol-imports.txt)
|
||||||
|
else()
|
||||||
|
set(symbol_imports ${CMAKE_CURRENT_SOURCE_DIR}/symbol-imports.txt)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
add_test(
|
add_test(
|
||||||
NAME conflict_set_shared_symbols
|
NAME conflict_set_shared_symbols
|
||||||
COMMAND ${CMAKE_SOURCE_DIR}/test_symbols.sh
|
COMMAND
|
||||||
$<TARGET_FILE:${PROJECT_NAME}> ${CMAKE_SOURCE_DIR}/symbols.txt)
|
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
|
||||||
|
$<TARGET_FILE:${PROJECT_NAME}> ${symbol_exports} ${symbol_imports})
|
||||||
add_test(
|
add_test(
|
||||||
NAME conflict_set_static_symbols
|
NAME conflict_set_static_symbols
|
||||||
COMMAND
|
COMMAND
|
||||||
${CMAKE_SOURCE_DIR}/test_symbols.sh
|
${CMAKE_CURRENT_SOURCE_DIR}/test_symbols.sh
|
||||||
$<TARGET_FILE:${PROJECT_NAME}_static> ${CMAKE_SOURCE_DIR}/symbols.txt)
|
$<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()
|
endif()
|
||||||
|
|
||||||
# bench
|
# bench
|
||||||
|
|
||||||
add_executable(conflict_set_bench Bench.cpp)
|
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)
|
||||||
# target_compile_options(conflict_set_bench PRIVATE
|
set_target_properties(conflict_set_bench PROPERTIES SKIP_BUILD_RPATH ON)
|
||||||
# "-fsanitize=address,undefined,fuzzer")
|
add_executable(real_data_bench RealDataBench.cpp)
|
||||||
# target_link_options(conflict_set_bench PRIVATE
|
target_link_libraries(real_data_bench PRIVATE ${PROJECT_NAME})
|
||||||
# "-fsanitize=address,undefined,fuzzer")
|
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()
|
endif()
|
||||||
|
|
||||||
# packaging
|
# packaging
|
||||||
|
|
||||||
set(CPACK_PACKAGE_CONTACT andrew@weaselab.dev)
|
set(CPACK_PACKAGE_CONTACT andrew@weaselab.dev)
|
||||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME all)
|
||||||
|
|
||||||
|
set(CPACK_PACKAGE_VENDOR "Weaselab")
|
||||||
|
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
||||||
|
set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
|
||||||
|
|
||||||
|
# rpm
|
||||||
|
set(CPACK_RPM_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
|
||||||
|
set(CPACK_RPM_SPEC_INSTALL_POST "/bin/true") # avoid stripping
|
||||||
|
set(CPACK_RPM_PACKAGE_LICENSE "Apache 2.0")
|
||||||
|
set(CPACK_RPM_FILE_NAME RPM-DEFAULT)
|
||||||
|
|
||||||
|
# deb
|
||||||
|
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
|
||||||
|
# see *-imports.txt - dependency versions need to be synced with symbol versions
|
||||||
|
if(CMAKE_SYSTEM_PROCESSOR STREQUAL aarch64)
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.17)")
|
||||||
|
else()
|
||||||
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.14)")
|
||||||
|
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_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_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)
|
include(CPack)
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
@@ -204,7 +464,7 @@ target_include_directories(
|
|||||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
|
||||||
|
|
||||||
target_include_directories(
|
target_include_directories(
|
||||||
${PROJECT_NAME}_static
|
${PROJECT_NAME}-static
|
||||||
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}>)
|
||||||
|
|
||||||
@@ -213,13 +473,16 @@ set_target_properties(
|
|||||||
SOVERSION ${PROJECT_VERSION_MAJOR})
|
SOVERSION ${PROJECT_VERSION_MAJOR})
|
||||||
|
|
||||||
install(
|
install(
|
||||||
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_static
|
TARGETS ${PROJECT_NAME} ${PROJECT_NAME}-static
|
||||||
EXPORT ConflictSetConfig
|
EXPORT ${PROJECT_NAME}Config
|
||||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
|
||||||
install(DIRECTORY include/
|
install(DIRECTORY include/
|
||||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
|
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME})
|
||||||
|
|
||||||
install(EXPORT ConflictSetConfig
|
install(EXPORT ${PROJECT_NAME}Config
|
||||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/ConflictSet/cmake)
|
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/cmake)
|
||||||
|
|
||||||
|
cpack_add_component(all)
|
||||||
|
7333
ConflictSet.cpp
7333
ConflictSet.cpp
File diff suppressed because it is too large
Load Diff
70
Dockerfile
70
Dockerfile
@@ -8,83 +8,51 @@ RUN chmod -R 777 /tmp
|
|||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get upgrade -y
|
RUN apt-get upgrade -y
|
||||||
RUN TZ=America/Los_Angeles DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
RUN TZ=America/Los_Angeles DEBIAN_FRONTEND=noninteractive apt-get install -y \
|
||||||
|
binutils-aarch64-linux-gnu \
|
||||||
build-essential \
|
build-essential \
|
||||||
ccache \
|
ccache \
|
||||||
clang \
|
|
||||||
cmake \
|
cmake \
|
||||||
curl \
|
curl \
|
||||||
doxygen \
|
devscripts \
|
||||||
file \
|
g++-aarch64-linux-gnu \
|
||||||
gcovr \
|
gcovr \
|
||||||
git \
|
git \
|
||||||
gperf \
|
gnupg \
|
||||||
graphviz \
|
|
||||||
libc6-dbg \
|
libc6-dbg \
|
||||||
|
lsb-release \
|
||||||
|
mold \
|
||||||
ninja-build \
|
ninja-build \
|
||||||
pre-commit \
|
pre-commit \
|
||||||
python3-requests \
|
python3-requests \
|
||||||
|
qemu-user \
|
||||||
|
rpm \
|
||||||
|
software-properties-common \
|
||||||
|
texlive-full \
|
||||||
|
wget \
|
||||||
zstd
|
zstd
|
||||||
|
|
||||||
# Install recent valgrind from source
|
# Install recent valgrind from source
|
||||||
RUN curl -Ls https://sourceware.org/pub/valgrind/valgrind-3.20.0.tar.bz2 -o valgrind.tar.bz2 && \
|
RUN curl -Ls https://sourceware.org/pub/valgrind/valgrind-3.22.0.tar.bz2 -o valgrind.tar.bz2 && \
|
||||||
echo "8536c031dbe078d342f121fa881a9ecd205cb5a78e639005ad570011bdb9f3c6 valgrind.tar.bz2" > valgrind-sha.txt && \
|
echo "c811db5add2c5f729944caf47c4e7a65dcaabb9461e472b578765dd7bf6d2d4c valgrind.tar.bz2" > valgrind-sha.txt && \
|
||||||
sha256sum --quiet -c valgrind-sha.txt && \
|
sha256sum --quiet -c valgrind-sha.txt && \
|
||||||
mkdir valgrind && \
|
mkdir valgrind && \
|
||||||
tar --strip-components 1 --no-same-owner --no-same-permissions --directory valgrind -xjf valgrind.tar.bz2 && \
|
tar --strip-components 1 --no-same-owner --no-same-permissions --directory valgrind -xjf valgrind.tar.bz2 && \
|
||||||
cd valgrind && \
|
cd valgrind && \
|
||||||
./configure --enable-only64bit --enable-lto && \
|
./configure --enable-only64bit --enable-lto && \
|
||||||
make && \
|
make -j`nproc` && \
|
||||||
make install && \
|
make install && \
|
||||||
cd .. && \
|
cd .. && \
|
||||||
rm -rf /tmp/*
|
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
|
# Set after building valgrind, which doesn't build with clang for some reason
|
||||||
ENV CC=clang
|
ENV CC=clang
|
||||||
ENV CXX=clang++
|
ENV CXX=clang++
|
||||||
|
|
||||||
# Install recent flatbuffers from source
|
|
||||||
RUN curl -Ls https://github.com/google/flatbuffers/archive/refs/tags/v23.3.3.tar.gz -o flatbuffers.tar.gz && \
|
|
||||||
echo "8aff985da30aaab37edf8e5b02fda33ed4cbdd962699a8e2af98fdef306f4e4d flatbuffers.tar.gz" > flatbuffers-sha.txt && \
|
|
||||||
sha256sum --quiet -c flatbuffers-sha.txt && \
|
|
||||||
mkdir flatbuffers && \
|
|
||||||
tar --strip-components 1 --no-same-owner --no-same-permissions --directory flatbuffers -xf flatbuffers.tar.gz && \
|
|
||||||
cd flatbuffers && \
|
|
||||||
cmake -S. -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && \
|
|
||||||
ninja -C build install && \
|
|
||||||
rm -rf /tmp/*
|
|
||||||
|
|
||||||
# Build msan-instrumented libc++ (llvmorg-16.0.0)
|
|
||||||
RUN curl -Ls https://github.com/llvm/llvm-project/releases/download/llvmorg-16.0.0/llvm-project-16.0.0.src.tar.xz -o llvm-project.tar.gz && \
|
|
||||||
echo "9a56d906a2c81f16f06efc493a646d497c53c2f4f28f0cb1f3c8da7f74350254 llvm-project.tar.gz" > llvm-project-sha.txt && \
|
|
||||||
sha256sum --quiet -c llvm-project-sha.txt && \
|
|
||||||
mkdir llvm-project && \
|
|
||||||
tar --strip-components 1 --no-same-owner --no-same-permissions --directory llvm-project -xf llvm-project.tar.gz && \
|
|
||||||
cmake -Sllvm-project/runtimes -B build -G Ninja \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
|
||||||
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \
|
|
||||||
-DCMAKE_C_COMPILER=clang \
|
|
||||||
-DCMAKE_CXX_COMPILER=clang++ \
|
|
||||||
-DCMAKE_INSTALL_PREFIX=/opt/llvm-msan \
|
|
||||||
-DLLVM_USE_SANITIZER=MemoryWithOrigins && \
|
|
||||||
ninja -C build cxx cxxabi && \
|
|
||||||
ninja -C build install-cxx install-cxxabi && \
|
|
||||||
rm -rf /tmp/*
|
|
||||||
|
|
||||||
# Install bloaty from source
|
|
||||||
RUN curl -Ls https://github.com/google/bloaty/releases/download/v1.1/bloaty-1.1.tar.bz2 -o bloaty.tar.bz2 && \
|
|
||||||
echo "a308d8369d5812aba45982e55e7c3db2ea4780b7496a5455792fb3dcba9abd6f bloaty.tar.bz2" > bloaty-sha.txt && \
|
|
||||||
sha256sum --quiet -c bloaty-sha.txt && \
|
|
||||||
mkdir bloaty && \
|
|
||||||
tar --strip-components 1 --no-same-owner --no-same-permissions --directory bloaty -xf bloaty.tar.bz2 && \
|
|
||||||
cd bloaty && \
|
|
||||||
cmake -S. -B build -G Ninja && \
|
|
||||||
ninja -C build install && \
|
|
||||||
rm -rf /tmp/*
|
|
||||||
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get upgrade -y
|
|
||||||
RUN TZ=America/Los_Angeles DEBIAN_FRONTEND=noninteractive apt-get install -y texlive-full
|
|
||||||
|
|
||||||
# Try to have all the pre-commit hooks we'll need already initialized
|
# Try to have all the pre-commit hooks we'll need already initialized
|
||||||
COPY .pre-commit-config.yaml /tmp/
|
COPY .pre-commit-config.yaml /tmp/
|
||||||
RUN git init && pre-commit install-hooks
|
RUN git init && pre-commit install-hooks
|
||||||
|
@@ -2,15 +2,25 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
for (int i = 1; i < argc; ++i) {
|
auto doTest = [&]() {
|
||||||
std::ifstream t(argv[i], std::ios::binary);
|
for (int i = 1; i < argc; ++i) {
|
||||||
std::stringstream buffer;
|
std::ifstream t(argv[i], std::ios::binary);
|
||||||
buffer << t.rdbuf();
|
std::stringstream buffer;
|
||||||
auto str = buffer.str();
|
buffer << t.rdbuf();
|
||||||
LLVMFuzzerTestOneInput((const uint8_t *)str.data(), str.size());
|
auto str = buffer.str();
|
||||||
}
|
LLVMFuzzerTestOneInput((const uint8_t *)str.data(), str.size());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#ifdef THREAD_TEST
|
||||||
|
std::thread thread2{doTest};
|
||||||
|
#endif
|
||||||
|
doTest();
|
||||||
|
#ifdef THREAD_TEST
|
||||||
|
thread2.join();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
161
HashTable.cpp
Normal file
161
HashTable.cpp
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
#include "ConflictSet.h"
|
||||||
|
#include "Internal.h"
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
// This implementation isn't correct for range queries :). It's just intended as
|
||||||
|
// a reference for performance comparison with point queries.
|
||||||
|
|
||||||
|
// struct is from "https://www.cppstories.com/2021/heterogeneous-access-cpp20/"
|
||||||
|
struct string_hash {
|
||||||
|
using is_transparent = void;
|
||||||
|
[[nodiscard]] size_t operator()(std::string_view txt) const {
|
||||||
|
return std::hash<std::string_view>{}(txt);
|
||||||
|
}
|
||||||
|
[[nodiscard]] size_t operator()(const std::string &txt) const {
|
||||||
|
return std::hash<std::string>{}(txt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct __attribute__((visibility("hidden"))) ConflictSet::Impl {
|
||||||
|
Impl(int64_t oldestVersion) : oldestVersion(oldestVersion) {}
|
||||||
|
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||||
|
int count) const {
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
auto key =
|
||||||
|
std::string_view((const char *)reads[i].begin.p, reads[i].begin.len);
|
||||||
|
auto version = reads[i].readVersion;
|
||||||
|
if (version < oldestVersion) {
|
||||||
|
results[i] = TooOld;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto iter = map.find(key);
|
||||||
|
results[i] =
|
||||||
|
iter == map.end() || iter->second <= version ? Commit : Conflict;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||||
|
int64_t writeVersion) {
|
||||||
|
for (int i = 0; i < count; ++i) {
|
||||||
|
auto &max = map[std::string((const char *)writes[i].begin.p,
|
||||||
|
writes[i].begin.len)];
|
||||||
|
assert(writeVersion >= max);
|
||||||
|
max = writeVersion;
|
||||||
|
keyUpdates += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOldestVersion(int64_t oldestVersion) {
|
||||||
|
if (oldestVersion <= this->oldestVersion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->oldestVersion = oldestVersion;
|
||||||
|
if (keyUpdates < 100) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto iter = map.find(removalKey);
|
||||||
|
while (keyUpdates > 0) {
|
||||||
|
if (iter == map.end()) {
|
||||||
|
iter = map.begin();
|
||||||
|
}
|
||||||
|
for (; iter != map.end(); --keyUpdates) {
|
||||||
|
if (iter->second <= oldestVersion) {
|
||||||
|
iter = map.erase(iter);
|
||||||
|
} else {
|
||||||
|
++iter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (iter == map.end()) {
|
||||||
|
removalKey.clear();
|
||||||
|
} else {
|
||||||
|
removalKey = iter->first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
int64_t keyUpdates = 0;
|
||||||
|
int64_t oldestVersion;
|
||||||
|
std::unordered_map<std::string, int64_t, string_hash, std::equal_to<>> map;
|
||||||
|
std::string removalKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
void ConflictSet::check(const ReadRange *reads, Result *results,
|
||||||
|
int count) const {
|
||||||
|
return impl->check(reads, results, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConflictSet::addWrites(const WriteRange *writes, int count,
|
||||||
|
int64_t writeVersion) {
|
||||||
|
return impl->addWrites(writes, count, writeVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConflictSet::setOldestVersion(int64_t oldestVersion) {
|
||||||
|
return impl->setOldestVersion(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}) {}
|
||||||
|
|
||||||
|
ConflictSet::~ConflictSet() {
|
||||||
|
if (impl) {
|
||||||
|
impl->~Impl();
|
||||||
|
safe_free(impl, sizeof(Impl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConflictSet::ConflictSet(ConflictSet &&other) noexcept
|
||||||
|
: impl(std::exchange(other.impl, nullptr)) {}
|
||||||
|
|
||||||
|
ConflictSet &ConflictSet::operator=(ConflictSet &&other) noexcept {
|
||||||
|
impl = std::exchange(other.impl, nullptr);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
using ConflictSet_Result = ConflictSet::Result;
|
||||||
|
using ConflictSet_Key = ConflictSet::Key;
|
||||||
|
using ConflictSet_ReadRange = ConflictSet::ReadRange;
|
||||||
|
using ConflictSet_WriteRange = ConflictSet::WriteRange;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
__attribute__((__visibility__("default"))) void
|
||||||
|
ConflictSet_addWrites(void *cs, const ConflictSet_WriteRange *writes, int count,
|
||||||
|
int64_t writeVersion) {
|
||||||
|
((ConflictSet::Impl *)cs)->addWrites(writes, count, writeVersion);
|
||||||
|
}
|
||||||
|
__attribute__((__visibility__("default"))) void
|
||||||
|
ConflictSet_setOldestVersion(void *cs, int64_t oldestVersion) {
|
||||||
|
((ConflictSet::Impl *)cs)->setOldestVersion(oldestVersion);
|
||||||
|
}
|
||||||
|
__attribute__((__visibility__("default"))) void *
|
||||||
|
ConflictSet_create(int64_t oldestVersion) {
|
||||||
|
return new (safe_malloc(sizeof(ConflictSet::Impl)))
|
||||||
|
ConflictSet::Impl{oldestVersion};
|
||||||
|
}
|
||||||
|
__attribute__((__visibility__("default"))) void ConflictSet_destroy(void *cs) {
|
||||||
|
using Impl = ConflictSet::Impl;
|
||||||
|
((Impl *)cs)->~Impl();
|
||||||
|
safe_free(cs, sizeof(Impl));
|
||||||
|
}
|
||||||
|
__attribute__((__visibility__("default"))) int64_t
|
||||||
|
ConflictSet_getBytes(void *cs) {
|
||||||
|
using Impl = ConflictSet::Impl;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
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;
|
||||||
|
}
|
||||||
|
}
|
531
Internal.h
531
Internal.h
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include "ConflictSet.h"
|
#include "ConflictSet.h"
|
||||||
|
|
||||||
|
using namespace weaselab;
|
||||||
|
|
||||||
#include <bit>
|
#include <bit>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <compare>
|
#include <compare>
|
||||||
@@ -10,21 +12,52 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
|
#include <latch>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <span>
|
#include <span>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <callgrind.h>
|
#include <callgrind.h>
|
||||||
|
|
||||||
#define DEBUG_VERBOSE 0
|
#define DEBUG_VERBOSE 0
|
||||||
|
#define SHOW_MEMORY 0
|
||||||
|
|
||||||
[[nodiscard]] inline auto
|
// std::span is not trivially constructible. We want a span that leaves its
|
||||||
operator<=>(const std::span<const uint8_t> &lhs,
|
// members uninitialized for performance reasons.
|
||||||
const std::span<const uint8_t> &rhs) noexcept {
|
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());
|
int cl = std::min<int>(lhs.size(), rhs.size());
|
||||||
if (cl > 0) {
|
if (cl > 0) {
|
||||||
if (auto c = memcmp(lhs.data(), rhs.data(), cl) <=> 0; c != 0) {
|
if (auto c = memcmp(lhs.data(), rhs.data(), cl) <=> 0; c != 0) {
|
||||||
@@ -34,16 +67,85 @@ operator<=>(const std::span<const uint8_t> &lhs,
|
|||||||
return lhs.size() <=> rhs.size();
|
return lhs.size() <=> rhs.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline auto operator<=>(const TrivialSpan &lhs,
|
||||||
|
const ConflictSet::Key &rhs) noexcept {
|
||||||
|
int cl = std::min<int>(lhs.size(), rhs.len);
|
||||||
|
if (cl > 0) {
|
||||||
|
if (auto c = memcmp(lhs.data(), rhs.p, cl) <=> 0; c != 0) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
// This header contains code that we want to reuse outside of ConflictSet.cpp or
|
||||||
// want to exclude from coverage since it's only testing related.
|
// want to exclude from coverage since it's only testing related.
|
||||||
|
|
||||||
// GCOVR_EXCL_START
|
// GCOVR_EXCL_START
|
||||||
|
|
||||||
|
#if SHOW_MEMORY
|
||||||
|
inline int64_t mallocBytes = 0;
|
||||||
|
inline int64_t peakMallocBytes = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
inline thread_local int64_t mallocBytesDelta = 0;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
constexpr auto kMallocHeaderSize = 16;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// malloc that aborts on OOM and thus always returns a non-null pointer. Must be
|
||||||
|
// paired with `safe_free`.
|
||||||
__attribute__((always_inline)) inline void *safe_malloc(size_t s) {
|
__attribute__((always_inline)) inline void *safe_malloc(size_t s) {
|
||||||
if (void *p = malloc(s)) {
|
mallocBytesDelta += s;
|
||||||
return p;
|
#if SHOW_MEMORY
|
||||||
|
mallocBytes += s;
|
||||||
|
if (mallocBytes > peakMallocBytes) {
|
||||||
|
peakMallocBytes = mallocBytes;
|
||||||
}
|
}
|
||||||
abort();
|
#endif
|
||||||
|
void *p = malloc(s
|
||||||
|
#ifndef NDEBUG
|
||||||
|
+ kMallocHeaderSize
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
if (p == nullptr) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
#ifndef NDEBUG
|
||||||
|
memcpy(p, &s, sizeof(s));
|
||||||
|
(char *&)p += kMallocHeaderSize;
|
||||||
|
#endif
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be paired with `safe_malloc`.
|
||||||
|
//
|
||||||
|
// There's nothing safer about this than free. Only called safe_free for
|
||||||
|
// symmetry with safe_malloc.
|
||||||
|
__attribute__((always_inline)) inline void safe_free(void *p, size_t s) {
|
||||||
|
mallocBytesDelta -= s;
|
||||||
|
#if SHOW_MEMORY
|
||||||
|
mallocBytes -= s;
|
||||||
|
#endif
|
||||||
|
#ifndef NDEBUG
|
||||||
|
(char *&)p -= kMallocHeaderSize;
|
||||||
|
size_t expected;
|
||||||
|
memcpy(&expected, p, sizeof(expected));
|
||||||
|
assert(s == expected);
|
||||||
|
#endif
|
||||||
|
free(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== BEGIN ARENA IMPL ====================
|
// ==================== BEGIN ARENA IMPL ====================
|
||||||
@@ -89,6 +191,7 @@ inline void *operator new[](size_t size, std::align_val_t align,
|
|||||||
|
|
||||||
/// align must be a power of two
|
/// align must be a power of two
|
||||||
template <class T> T *align_up(T *t, size_t align) {
|
template <class T> T *align_up(T *t, size_t align) {
|
||||||
|
assert(std::popcount(align) == 1);
|
||||||
auto unaligned = uintptr_t(t);
|
auto unaligned = uintptr_t(t);
|
||||||
auto aligned = (unaligned + align - 1) & ~(align - 1);
|
auto aligned = (unaligned + align - 1) & ~(align - 1);
|
||||||
return reinterpret_cast<T *>(reinterpret_cast<char *>(t) + aligned -
|
return reinterpret_cast<T *>(reinterpret_cast<char *>(t) + aligned -
|
||||||
@@ -97,6 +200,7 @@ template <class T> T *align_up(T *t, size_t align) {
|
|||||||
|
|
||||||
/// align must be a power of two
|
/// align must be a power of two
|
||||||
constexpr inline int align_up(uint32_t unaligned, uint32_t align) {
|
constexpr inline int align_up(uint32_t unaligned, uint32_t align) {
|
||||||
|
assert(std::popcount(align) == 1);
|
||||||
return (unaligned + align - 1) & ~(align - 1);
|
return (unaligned + align - 1) & ~(align - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,12 +216,10 @@ struct Arena::ArenaImpl {
|
|||||||
uint8_t *begin() { return reinterpret_cast<uint8_t *>(this + 1); }
|
uint8_t *begin() { return reinterpret_cast<uint8_t *>(this + 1); }
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(sizeof(Arena::ArenaImpl) == 16);
|
|
||||||
static_assert(alignof(Arena::ArenaImpl) == 8);
|
|
||||||
|
|
||||||
inline Arena::Arena(int initialSize) : impl(nullptr) {
|
inline Arena::Arena(int initialSize) : impl(nullptr) {
|
||||||
if (initialSize > 0) {
|
if (initialSize > 0) {
|
||||||
auto allocationSize = align_up(initialSize + sizeof(ArenaImpl), 16);
|
auto allocationSize =
|
||||||
|
align_up(initialSize + sizeof(ArenaImpl), alignof(ArenaImpl));
|
||||||
impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
||||||
impl->prev = nullptr;
|
impl->prev = nullptr;
|
||||||
impl->capacity = allocationSize - sizeof(ArenaImpl);
|
impl->capacity = allocationSize - sizeof(ArenaImpl);
|
||||||
@@ -128,7 +230,7 @@ inline Arena::Arena(int initialSize) : impl(nullptr) {
|
|||||||
inline void onDestroy(Arena::ArenaImpl *impl) {
|
inline void onDestroy(Arena::ArenaImpl *impl) {
|
||||||
while (impl) {
|
while (impl) {
|
||||||
auto *prev = impl->prev;
|
auto *prev = impl->prev;
|
||||||
free(impl);
|
safe_free(impl, sizeof(Arena::ArenaImpl) + impl->capacity);
|
||||||
impl = prev;
|
impl = prev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +255,7 @@ inline void *operator new(size_t size, std::align_val_t align, Arena &arena) {
|
|||||||
(arena.impl ? std::max<int>(sizeof(Arena::ArenaImpl),
|
(arena.impl ? std::max<int>(sizeof(Arena::ArenaImpl),
|
||||||
arena.impl->capacity * 2)
|
arena.impl->capacity * 2)
|
||||||
: 0)),
|
: 0)),
|
||||||
16);
|
alignof(Arena::ArenaImpl));
|
||||||
auto *impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
auto *impl = (Arena::ArenaImpl *)safe_malloc(allocationSize);
|
||||||
impl->prev = arena.impl;
|
impl->prev = arena.impl;
|
||||||
impl->capacity = allocationSize - sizeof(Arena::ArenaImpl);
|
impl->capacity = allocationSize - sizeof(Arena::ArenaImpl);
|
||||||
@@ -192,10 +294,75 @@ template <class T> struct ArenaAlloc {
|
|||||||
void deallocate(T *, size_t) noexcept {}
|
void deallocate(T *, size_t) noexcept {}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T> using Vector = std::vector<T, ArenaAlloc<T>>;
|
template <class T> struct Vector {
|
||||||
template <class T> auto vector(Arena &arena) {
|
static_assert(std::is_trivially_destructible_v<T>);
|
||||||
return Vector<T>(ArenaAlloc<T>(&arena));
|
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> using Set = std::set<T, C, ArenaAlloc<T>>;
|
||||||
template <class T, class C = std::less<T>> auto set(Arena &arena) {
|
template <class T, class C = std::less<T>> auto set(Arena &arena) {
|
||||||
return Set<T, C>(ArenaAlloc<T>(&arena));
|
return Set<T, C>(ArenaAlloc<T>(&arena));
|
||||||
@@ -348,42 +515,16 @@ inline uint32_t Arbitrary::bounded(uint32_t s) {
|
|||||||
|
|
||||||
// ==================== END ARBITRARY IMPL ====================
|
// ==================== END ARBITRARY IMPL ====================
|
||||||
|
|
||||||
// ==================== BEGIN UTILITIES IMPL ====================
|
|
||||||
|
|
||||||
// Call Stepwise::step for each element of remaining until it returns true.
|
|
||||||
// Applies a permutation to `remaining` as a side effect.
|
|
||||||
template <class Stepwise> void runInterleaved(std::span<Stepwise> remaining) {
|
|
||||||
while (remaining.size() > 0) {
|
|
||||||
for (int i = 0; i < int(remaining.size());) {
|
|
||||||
bool done = remaining[i].step();
|
|
||||||
if (done) {
|
|
||||||
if (i != int(remaining.size()) - 1) {
|
|
||||||
using std::swap;
|
|
||||||
swap(remaining[i], remaining.back());
|
|
||||||
}
|
|
||||||
remaining = remaining.subspan(0, remaining.size() - 1);
|
|
||||||
} else {
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template <class Stepwise> void runSequential(std::span<Stepwise> remaining) {
|
|
||||||
for (auto &r : remaining) {
|
|
||||||
while (!r.step()) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ReferenceImpl {
|
struct ReferenceImpl {
|
||||||
explicit ReferenceImpl(int64_t oldestVersion) : oldestVersion(oldestVersion) {
|
explicit ReferenceImpl(int64_t oldestVersion)
|
||||||
|
: oldestVersion(oldestVersion), newestVersion(oldestVersion) {
|
||||||
writeVersionMap[""] = oldestVersion;
|
writeVersionMap[""] = oldestVersion;
|
||||||
}
|
}
|
||||||
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
void check(const ConflictSet::ReadRange *reads, ConflictSet::Result *results,
|
||||||
int count) const {
|
int count) const {
|
||||||
for (int i = 0; i < count; ++i) {
|
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;
|
results[i] = ConflictSet::TooOld;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -403,7 +544,10 @@ struct ReferenceImpl {
|
|||||||
: ConflictSet::Commit;
|
: ConflictSet::Commit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void addWrites(const ConflictSet::WriteRange *writes, int count) {
|
void addWrites(const ConflictSet::WriteRange *writes, int count,
|
||||||
|
int64_t writeVersion) {
|
||||||
|
assert(writeVersion >= newestVersion);
|
||||||
|
newestVersion = writeVersion;
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
auto begin =
|
auto begin =
|
||||||
std::string((const char *)writes[i].begin.p, writes[i].begin.len);
|
std::string((const char *)writes[i].begin.p, writes[i].begin.len);
|
||||||
@@ -411,7 +555,6 @@ struct ReferenceImpl {
|
|||||||
writes[i].end.len == 0
|
writes[i].end.len == 0
|
||||||
? begin + std::string("\x00", 1)
|
? begin + std::string("\x00", 1)
|
||||||
: std::string((const char *)writes[i].end.p, writes[i].end.len);
|
: std::string((const char *)writes[i].end.p, writes[i].end.len);
|
||||||
auto writeVersion = writes[i].writeVersion;
|
|
||||||
auto prevVersion = (--writeVersionMap.upper_bound(end))->second;
|
auto prevVersion = (--writeVersionMap.upper_bound(end))->second;
|
||||||
for (auto iter = writeVersionMap.lower_bound(begin),
|
for (auto iter = writeVersionMap.lower_bound(begin),
|
||||||
endIter = writeVersionMap.lower_bound(end);
|
endIter = writeVersionMap.lower_bound(end);
|
||||||
@@ -424,16 +567,21 @@ struct ReferenceImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setOldestVersion(int64_t oldestVersion) {
|
void setOldestVersion(int64_t oldestVersion) {
|
||||||
assert(oldestVersion >= oldestVersion);
|
assert(oldestVersion >= this->oldestVersion);
|
||||||
this->oldestVersion = oldestVersion;
|
this->oldestVersion = oldestVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t oldestVersion;
|
int64_t oldestVersion;
|
||||||
|
int64_t newestVersion;
|
||||||
std::map<std::string, int64_t> writeVersionMap;
|
std::map<std::string, int64_t> writeVersionMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
using Key = ConflictSet::Key;
|
using Key = ConflictSet::Key;
|
||||||
|
|
||||||
|
inline Key operator""_s(const char *str, size_t size) {
|
||||||
|
return {reinterpret_cast<const uint8_t *>(str), int(size)};
|
||||||
|
}
|
||||||
|
|
||||||
[[maybe_unused]] static Key toKey(Arena &arena, int n) {
|
[[maybe_unused]] static Key toKey(Arena &arena, int n) {
|
||||||
uint8_t *buf = new (arena) uint8_t[sizeof(n)];
|
uint8_t *buf = new (arena) uint8_t[sizeof(n)];
|
||||||
memcpy(buf, &n, sizeof(n));
|
memcpy(buf, &n, sizeof(n));
|
||||||
@@ -461,59 +609,70 @@ inline std::string printable(const Key &key) {
|
|||||||
return printable(std::string_view((const char *)key.p, key.len));
|
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()));
|
return printable(std::string_view((const char *)key.data(), key.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline const char *resultToStr(ConflictSet::Result r) {
|
||||||
|
switch (r) {
|
||||||
|
case ConflictSet::Commit:
|
||||||
|
return "commit";
|
||||||
|
case ConflictSet::Conflict:
|
||||||
|
return "conflict";
|
||||||
|
case ConflictSet::TooOld:
|
||||||
|
return "too old";
|
||||||
|
}
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
template <class ConflictSetImpl> struct TestDriver {
|
template <class ConflictSetImpl, bool kEnableAssertions = true>
|
||||||
Arbitrary arbitrary;
|
struct TestDriver {
|
||||||
explicit TestDriver(const uint8_t *data, size_t size)
|
Arbitrary *arbitrary;
|
||||||
: arbitrary({data, size}) {}
|
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 = arbitrary->next();
|
||||||
int64_t oldestVersion = 0;
|
int64_t writeVersion = oldestVersion;
|
||||||
ConflictSetImpl cs{oldestVersion};
|
ConflictSetImpl cs{oldestVersion};
|
||||||
ReferenceImpl refImpl{oldestVersion};
|
ReferenceImpl refImpl{oldestVersion};
|
||||||
|
|
||||||
constexpr static auto kMaxKeyLen = 32;
|
constexpr static auto kMaxKeySuffixLen = 8;
|
||||||
|
|
||||||
bool ok = true;
|
bool ok = true;
|
||||||
|
|
||||||
static const char *resultToStr(ConflictSet::Result r) {
|
const int prefixLen = arbitrary->bounded(512);
|
||||||
switch (r) {
|
const int prefixByte = arbitrary->randT<uint8_t>();
|
||||||
case ConflictSet::Commit:
|
|
||||||
return "commit";
|
|
||||||
case ConflictSet::Conflict:
|
|
||||||
return "conflict";
|
|
||||||
case ConflictSet::TooOld:
|
|
||||||
return "too old";
|
|
||||||
}
|
|
||||||
abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call until it returns true, for "done". Check internal invariants etc
|
// Call until it returns true, for "done". Check internal invariants etc
|
||||||
// between calls to next.
|
// between calls to next.
|
||||||
bool next() {
|
bool next() {
|
||||||
if (!arbitrary.hasEntropy()) {
|
assert(cs.getBytes() >= 0);
|
||||||
|
if (!arbitrary->hasEntropy()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
Arena arena;
|
Arena arena;
|
||||||
{
|
{
|
||||||
int numPointWrites = arbitrary.bounded(100);
|
int numPointWrites = arbitrary->bounded(100);
|
||||||
int numRangeWrites = arbitrary.bounded(100);
|
int numRangeWrites = arbitrary->bounded(100);
|
||||||
int64_t v = ++writeVersion;
|
int64_t v =
|
||||||
|
(writeVersion +=
|
||||||
|
arbitrary->bounded(10) ? arbitrary->bounded(10) : arbitrary->next());
|
||||||
auto *writes =
|
auto *writes =
|
||||||
new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites];
|
new (arena) ConflictSet::WriteRange[numPointWrites + numRangeWrites];
|
||||||
auto keys = set<std::string_view>(arena);
|
auto keys = set<std::string_view>(arena);
|
||||||
while (int(keys.size()) < numPointWrites + numRangeWrites * 2) {
|
while (int(keys.size()) < numPointWrites + numRangeWrites * 2) {
|
||||||
if (!arbitrary.hasEntropy()) {
|
if (!arbitrary->hasEntropy()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
int keyLen = arbitrary.bounded(kMaxKeyLen);
|
int keyLen = prefixLen + arbitrary->bounded(kMaxKeySuffixLen);
|
||||||
auto *begin = new (arena) uint8_t[keyLen];
|
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));
|
keys.insert(std::string_view((const char *)begin, keyLen));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -523,7 +682,7 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
rangesRemaining = numRangeWrites;
|
rangesRemaining = numRangeWrites;
|
||||||
pointsRemaining > 0 || rangesRemaining > 0; ++i) {
|
pointsRemaining > 0 || rangesRemaining > 0; ++i) {
|
||||||
bool pointRead = pointsRemaining > 0 && rangesRemaining > 0
|
bool pointRead = pointsRemaining > 0 && rangesRemaining > 0
|
||||||
? bool(arbitrary.bounded(2))
|
? bool(arbitrary->bounded(2))
|
||||||
: pointsRemaining > 0;
|
: pointsRemaining > 0;
|
||||||
if (pointRead) {
|
if (pointRead) {
|
||||||
assert(pointsRemaining > 0);
|
assert(pointsRemaining > 0);
|
||||||
@@ -542,48 +701,121 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
++iter;
|
++iter;
|
||||||
--rangesRemaining;
|
--rangesRemaining;
|
||||||
}
|
}
|
||||||
writes[i].writeVersion = v;
|
|
||||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
|
||||||
if (writes[i].end.len == 0) {
|
|
||||||
fprintf(stderr, "Write: {%s} -> %d\n",
|
|
||||||
printable(writes[i].begin).c_str(),
|
|
||||||
int(writes[i].writeVersion));
|
|
||||||
} else {
|
|
||||||
fprintf(stderr, "Write: [%s, %s) -> %d\n",
|
|
||||||
printable(writes[i].begin).c_str(),
|
|
||||||
printable(writes[i].end).c_str(),
|
|
||||||
int(writes[i].writeVersion));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
assert(iter == keys.end());
|
assert(iter == keys.end());
|
||||||
assert(i == numPointWrites + numRangeWrites);
|
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;
|
CALLGRIND_START_INSTRUMENTATION;
|
||||||
cs.addWrites(writes, numPointWrites + numRangeWrites);
|
cs.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||||
CALLGRIND_STOP_INSTRUMENTATION;
|
CALLGRIND_STOP_INSTRUMENTATION;
|
||||||
|
|
||||||
refImpl.addWrites(writes, numPointWrites + numRangeWrites);
|
if constexpr (kEnableAssertions) {
|
||||||
|
refImpl.addWrites(writes, numPointWrites + numRangeWrites, v);
|
||||||
|
}
|
||||||
|
|
||||||
oldestVersion = std::max<int64_t>(writeVersion - arbitrary.bounded(10),
|
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||||
oldestVersion);
|
fprintf(stderr, "%p Set oldest version: %" PRId64 "\n", this,
|
||||||
|
oldestVersion);
|
||||||
|
#endif
|
||||||
|
CALLGRIND_START_INSTRUMENTATION;
|
||||||
cs.setOldestVersion(oldestVersion);
|
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 numPointReads = arbitrary->bounded(100);
|
||||||
int numRangeReads = arbitrary.bounded(100);
|
int numRangeReads = arbitrary->bounded(100);
|
||||||
int64_t v = std::max<int64_t>(writeVersion - arbitrary.bounded(10), 0);
|
|
||||||
|
int64_t v = std::max<int64_t>(writeVersion - (arbitrary->bounded(10)
|
||||||
|
? arbitrary->bounded(10)
|
||||||
|
: arbitrary->next()),
|
||||||
|
0);
|
||||||
auto *reads =
|
auto *reads =
|
||||||
new (arena) ConflictSet::ReadRange[numPointReads + numRangeReads];
|
new (arena) ConflictSet::ReadRange[numPointReads + numRangeReads];
|
||||||
auto keys = set<std::string_view>(arena);
|
auto keys = set<std::string_view>(arena);
|
||||||
while (int(keys.size()) < numPointReads + numRangeReads * 2) {
|
while (int(keys.size()) < numPointReads + numRangeReads * 2) {
|
||||||
if (!arbitrary.hasEntropy()) {
|
if (!arbitrary->hasEntropy()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
int keyLen = arbitrary.bounded(kMaxKeyLen);
|
int keyLen = prefixLen + arbitrary->bounded(kMaxKeySuffixLen);
|
||||||
auto *begin = new (arena) uint8_t[keyLen];
|
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));
|
keys.insert(std::string_view((const char *)begin, keyLen));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,7 +824,7 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
for (int pointsRemaining = numPointReads, rangesRemaining = numRangeReads;
|
for (int pointsRemaining = numPointReads, rangesRemaining = numRangeReads;
|
||||||
pointsRemaining > 0 || rangesRemaining > 0; ++i) {
|
pointsRemaining > 0 || rangesRemaining > 0; ++i) {
|
||||||
bool pointRead = pointsRemaining > 0 && rangesRemaining > 0
|
bool pointRead = pointsRemaining > 0 && rangesRemaining > 0
|
||||||
? bool(arbitrary.bounded(2))
|
? bool(arbitrary->bounded(2))
|
||||||
: pointsRemaining > 0;
|
: pointsRemaining > 0;
|
||||||
if (pointRead) {
|
if (pointRead) {
|
||||||
assert(pointsRemaining > 0);
|
assert(pointsRemaining > 0);
|
||||||
@@ -614,12 +846,12 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
reads[i].readVersion = v;
|
reads[i].readVersion = v;
|
||||||
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
#if DEBUG_VERBOSE && !defined(NDEBUG)
|
||||||
if (reads[i].end.len == 0) {
|
if (reads[i].end.len == 0) {
|
||||||
fprintf(stderr, "Read: {%s} @ %d\n",
|
fprintf(stderr, "%p Read: {%s} @ %" PRId64 "\n", this,
|
||||||
printable(reads[i].begin).c_str(), int(reads[i].readVersion));
|
printable(reads[i].begin).c_str(), reads[i].readVersion);
|
||||||
} else {
|
} 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].begin).c_str(),
|
||||||
printable(reads[i].end).c_str(), int(reads[i].readVersion));
|
printable(reads[i].end).c_str(), reads[i].readVersion);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -630,32 +862,85 @@ template <class ConflictSetImpl> struct TestDriver {
|
|||||||
auto *results2 =
|
auto *results2 =
|
||||||
new (arena) ConflictSet::Result[numPointReads + numRangeReads];
|
new (arena) ConflictSet::Result[numPointReads + numRangeReads];
|
||||||
|
|
||||||
|
#ifdef THREAD_TEST
|
||||||
|
auto *results3 =
|
||||||
|
new (arena) ConflictSet::Result[numPointReads + numRangeReads];
|
||||||
|
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
|
||||||
|
|
||||||
CALLGRIND_START_INSTRUMENTATION;
|
CALLGRIND_START_INSTRUMENTATION;
|
||||||
cs.check(reads, results1, numPointReads + numRangeReads);
|
cs.check(reads, results1, numPointReads + numRangeReads);
|
||||||
CALLGRIND_STOP_INSTRUMENTATION;
|
CALLGRIND_STOP_INSTRUMENTATION;
|
||||||
|
|
||||||
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
if constexpr (kEnableAssertions) {
|
||||||
for (int i = 0; i < numPointReads + numRangeReads; ++i) {
|
// Call remaining const methods
|
||||||
if (results1[i] != results2[i]) {
|
cs.getBytes();
|
||||||
if (reads[i].end.len == 0) {
|
ConflictSet::MetricsV1 *m;
|
||||||
fprintf(stderr,
|
int count;
|
||||||
"Expected %s, got %s for read of {%s} at version %" PRId64
|
cs.getMetricsV1(&m, &count);
|
||||||
"\n",
|
for (int i = 0; i < count; ++i) {
|
||||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
m[i].getValue();
|
||||||
printable(reads[i].begin).c_str(), reads[i].readVersion);
|
}
|
||||||
} else {
|
|
||||||
fprintf(
|
refImpl.check(reads, results2, numPointReads + numRangeReads);
|
||||||
stderr,
|
}
|
||||||
"Expected %s, got %s for read of [%s, %s) at version %" PRId64
|
|
||||||
"\n",
|
auto compareResults = [reads, this](ConflictSet::Result *results1,
|
||||||
resultToStr(results2[i]), resultToStr(results1[i]),
|
ConflictSet::Result *results2,
|
||||||
printable(reads[i].begin).c_str(),
|
int count) {
|
||||||
printable(reads[i].end).c_str(), reads[i].readVersion);
|
for (int i = 0; i < count; ++i) {
|
||||||
|
if (results1[i] != results2[i]) {
|
||||||
|
if (reads[i].end.len == 0) {
|
||||||
|
fprintf(
|
||||||
|
stderr,
|
||||||
|
"%p Expected %s, got %s for read of {%s} at version %" PRId64
|
||||||
|
"\n",
|
||||||
|
(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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if constexpr (kEnableAssertions) {
|
||||||
|
if (!compareResults(results1, results2,
|
||||||
|
numPointReads + numRangeReads)) {
|
||||||
ok = false;
|
ok = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef THREAD_TEST
|
||||||
|
thread2.join();
|
||||||
|
if (!compareResults(results3, results2, numPointReads + numRangeReads)) {
|
||||||
|
ok = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
97
Jenkinsfile
vendored
97
Jenkinsfile
vendored
@@ -11,11 +11,11 @@ def CleanBuildAndTest(String cmakeArgs) {
|
|||||||
catchError {
|
catchError {
|
||||||
sh '''
|
sh '''
|
||||||
cd build
|
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
|
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}/'
|
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,6 +36,61 @@ pipeline {
|
|||||||
sh 'pre-commit run --all-files --show-diff-on-failure'
|
sh 'pre-commit run --all-files --show-diff-on-failure'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stage('64 bit versions') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
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') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DUSE_SIMD_FALLBACK=ON")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Release [clang]') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
||||||
|
recordIssues(tools: [clang()])
|
||||||
|
sh '''
|
||||||
|
cd build
|
||||||
|
cpack -G DEB
|
||||||
|
cpack -G RPM
|
||||||
|
'''
|
||||||
|
sh '''
|
||||||
|
cd paper
|
||||||
|
make
|
||||||
|
'''
|
||||||
|
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]') {
|
stage('Release [gcc]') {
|
||||||
agent {
|
agent {
|
||||||
dockerfile {
|
dockerfile {
|
||||||
@@ -44,18 +99,25 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Release")
|
CleanBuildAndTest("-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
||||||
recordIssues(tools: [gcc()])
|
recordIssues(tools: [gcc()])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stage('Release [clang,aarch64]') {
|
||||||
|
agent {
|
||||||
|
dockerfile {
|
||||||
|
args '-v /home/jenkins/ccache:/ccache'
|
||||||
|
reuseNode true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
CleanBuildAndTest("-DCMAKE_TOOLCHAIN_FILE=../aarch64-toolchain.cmake -DCMAKE_CXX_FLAGS=-DNVALGRIND")
|
||||||
sh '''
|
sh '''
|
||||||
cd build
|
cd build
|
||||||
cpack -G DEB
|
cpack -G DEB
|
||||||
|
cpack -G RPM
|
||||||
'''
|
'''
|
||||||
minio bucket: 'jenkins', credentialsId: 'jenkins-minio', excludes: '', host: 'minio.weaselab.dev', includes: 'build/*.deb', targetFolder: '${JOB_NAME}/${BUILD_NUMBER}/${STAGE_NAME}/'
|
minio bucket: 'jenkins', credentialsId: 'jenkins-minio', excludes: '', host: 'minio.weaselab.dev', includes: 'build/*.deb,build/*.rpm', targetFolder: '${JOB_NAME}/${BUILD_NUMBER}/${STAGE_NAME}'
|
||||||
sh '''
|
|
||||||
cd paper
|
|
||||||
make
|
|
||||||
'''
|
|
||||||
minio bucket: 'jenkins', credentialsId: 'jenkins-minio', excludes: '', host: 'minio.weaselab.dev', includes: 'paper/*.pdf', targetFolder: '${JOB_NAME}/${BUILD_NUMBER}/${STAGE_NAME}/'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('Coverage') {
|
stage('Coverage') {
|
||||||
@@ -66,11 +128,18 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
steps {
|
steps {
|
||||||
CleanBuildAndTest("-DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug")
|
script {
|
||||||
sh '''
|
gcov_args = "-f ConflictSet.cpp -f LongestCommonPrefix.h -f Metrics.h --gcov-executable 'llvm-cov gcov' --exclude-noncode-lines"
|
||||||
gcovr --gcov-executable "llvm-cov-15 gcov" --exclude '.*third_party.*' --cobertura > build/coverage.xml
|
}
|
||||||
'''
|
CleanBuildAndTest("-DCMAKE_C_FLAGS=--coverage -DCMAKE_CXX_FLAGS=--coverage -DCMAKE_BUILD_TYPE=Debug -DDISABLE_TSAN=ON")
|
||||||
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
|
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;
|
||||||
|
}
|
105
README.md
105
README.md
@@ -1,70 +1,61 @@
|
|||||||
A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys.
|
A data structure for optimistic concurrency control on ranges of bitwise-lexicographically-ordered keys.
|
||||||
|
|
||||||
Intended to replace FoundationDB's skip list.
|
Intended as an alternative to FoundationDB's skip list.
|
||||||
|
|
||||||
# FoundationDB's benchmark
|
Hardware for all benchmarks is an AMD Ryzen 9 7900 with (2x32GB) 5600MT/s CL28-34-34-89 1.35V RAM.
|
||||||
|
|
||||||
|
Compiler is `Ubuntu clang version 20.0.0 (++20241029082144+7544d3af0e28-1~exp1~20241029082307.506)`.
|
||||||
|
|
||||||
|
# Microbenchmark
|
||||||
|
|
||||||
## Skip list
|
## Skip list
|
||||||
|
|
||||||
```
|
| ns/op | op/s | err% | ins/op | cyc/op | IPC | bra/op | miss% | total | benchmark
|
||||||
New conflict set: 4.189 sec
|
|--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
|
||||||
0.298 Mtransactions/sec
|
| 159.65 | 6,263,576.52 | 1.6% | 2,972.36 | 820.37 | 3.623 | 504.59 | 0.0% | 0.01 | `point reads`
|
||||||
1.194 Mkeys/sec
|
| 156.32 | 6,397,320.65 | 0.7% | 2,913.62 | 806.87 | 3.611 | 490.19 | 0.0% | 0.01 | `prefix reads`
|
||||||
Detect only: 3.990 sec
|
| 229.18 | 4,363,293.65 | 1.2% | 3,541.05 | 1,219.75 | 2.903 | 629.33 | 0.0% | 0.01 | `range reads`
|
||||||
0.313 Mtransactions/sec
|
| 363.37 | 2,752,026.30 | 0.3% | 5,273.63 | 1,951.54 | 2.702 | 851.66 | 1.7% | 0.01 | `point writes`
|
||||||
1.253 Mkeys/sec
|
| 364.99 | 2,739,787.02 | 0.3% | 5,250.92 | 1,958.54 | 2.681 | 839.24 | 1.7% | 0.01 | `prefix writes`
|
||||||
Skiplist only: 2.849 sec
|
| 242.26 | 4,127,796.58 | 2.9% | 3,117.33 | 1,304.41 | 2.390 | 541.07 | 2.8% | 0.02 | `range writes`
|
||||||
0.439 Mtransactions/sec
|
| 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`
|
||||||
1.755 Mkeys/sec
|
| 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`
|
||||||
Performance counters:
|
| 41.71 | 23,976,459.34 | 1.7% | 885.00 | 219.17 | 4.038 | 132.00 | 0.0% | 0.01 | `create and destroy`
|
||||||
Build: 0.0913
|
|
||||||
Add: 0.0998
|
|
||||||
Detect: 3.99
|
|
||||||
D.Sort: 0.808
|
|
||||||
D.Combine: 0.0309
|
|
||||||
D.CheckRead: 1.67
|
|
||||||
D.CheckIntraBatch: 0.0305
|
|
||||||
D.MergeWrite: 1.18
|
|
||||||
D.RemoveBefore: 0.265
|
|
||||||
```
|
|
||||||
|
|
||||||
## Radix tree (this implementation)
|
## Radix tree (this implementation)
|
||||||
|
|
||||||
|
| 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
|
||||||
|
|
||||||
|
Point queries only, best of three runs. Gc ratio is the ratio of time spent doing garbage collection to time spent adding writes or doing garbage collection. Lower is better.
|
||||||
|
|
||||||
|
## skip list
|
||||||
|
|
||||||
```
|
```
|
||||||
New conflict set: 2.965 sec
|
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
|
||||||
0.422 Mtransactions/sec
|
|
||||||
1.686 Mkeys/sec
|
|
||||||
Detect only: 2.761 sec
|
|
||||||
0.453 Mtransactions/sec
|
|
||||||
1.811 Mkeys/sec
|
|
||||||
Skiplist only: 1.580 sec
|
|
||||||
0.791 Mtransactions/sec
|
|
||||||
3.165 Mkeys/sec
|
|
||||||
Performance counters:
|
|
||||||
Build: 0.0902
|
|
||||||
Add: 0.107
|
|
||||||
Detect: 2.76
|
|
||||||
D.Sort: 0.809
|
|
||||||
D.Combine: 0.0309
|
|
||||||
D.CheckRead: 0.658
|
|
||||||
D.CheckIntraBatch: 0.0294
|
|
||||||
D.MergeWrite: 0.921
|
|
||||||
D.RemoveBefore: 0.305
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Our benchmark
|
## radix tree
|
||||||
|
|
||||||
| ns/op | op/s | err% | total | benchmark
|
```
|
||||||
|--------------------:|--------------------:|--------:|----------:|:----------
|
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
|
||||||
| 263.10 | 3,800,889.33 | 1.6% | 0.64 | `skip list (point reads)`
|
```
|
||||||
| 256.36 | 3,900,800.96 | 1.7% | 0.61 | `skip list (prefix reads)`
|
|
||||||
| 526.95 | 1,897,714.85 | 1.6% | 1.23 | `skip list (range reads)`
|
## hash table
|
||||||
| 546.54 | 1,829,676.97 | 0.5% | 1.31 | `skip list (point writes)`
|
|
||||||
| 539.23 | 1,854,511.73 | 0.5% | 1.29 | `skip list (prefix writes)`
|
(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)
|
||||||
| 266.68 | 3,749,799.99 | 0.6% | 0.65 | `skip list (range writes)`
|
|
||||||
| 18.31 | 54,612,706.60 | 0.4% | 0.04 | `radix tree (point reads)`
|
```
|
||||||
| 48.78 | 20,498,870.77 | 0.2% | 0.12 | `radix tree (prefix reads)`
|
Check: 0.84256 seconds, 1935.23 MB/s, Add: 0.697204 seconds, 804.146 MB/s, Gc ratio: 35.4091%
|
||||||
| 347.16 | 2,880,474.86 | 0.9% | 0.83 | `radix tree (range reads)`
|
```
|
||||||
| 34.80 | 28,734,706.67 | 3.6% | 0.09 | `radix tree (point writes)`
|
|
||||||
| 70.34 | 14,216,970.16 | 1.4% | 0.17 | `radix tree (prefix writes)`
|
|
||||||
| 82.41 | 12,134,555.86 | 1.0% | 0.20 | `radix tree (range writes)`
|
|
||||||
|
147
RealDataBench.cpp
Normal file
147
RealDataBench.cpp
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
#include <ConflictSet.h>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <string_view>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace weaselab;
|
||||||
|
|
||||||
|
double now() {
|
||||||
|
return std::chrono::duration_cast<std::chrono::nanoseconds>(
|
||||||
|
std::chrono::steady_clock::now().time_since_epoch())
|
||||||
|
.count() *
|
||||||
|
1e-9;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, const char **argv) {
|
||||||
|
// Use with this dataset https://snap.stanford.edu/data/memetracker9.html
|
||||||
|
// Preprocess the files with `sed -i'' '/^Q/d'`
|
||||||
|
|
||||||
|
double checkTime = 0;
|
||||||
|
double addTime = 0;
|
||||||
|
double gcTime = 0;
|
||||||
|
double checkBytes = 0;
|
||||||
|
double addBytes = 0;
|
||||||
|
|
||||||
|
ConflictSet cs{0};
|
||||||
|
int64_t version = 0;
|
||||||
|
double timer = 0;
|
||||||
|
|
||||||
|
int64_t peakMemory = 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;
|
||||||
|
|
||||||
|
using StringView = std::basic_string_view<uint8_t>;
|
||||||
|
|
||||||
|
StringView write;
|
||||||
|
std::vector<StringView> reads;
|
||||||
|
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.size() > 0 && line[0] == 'P') {
|
||||||
|
write = line.substr(2, line.size());
|
||||||
|
} else if (line.size() > 0 && line[0] == 'L') {
|
||||||
|
reads.push_back(line.substr(2, line.size()));
|
||||||
|
} else if (line.empty()) {
|
||||||
|
{
|
||||||
|
readRanges.resize(reads.size());
|
||||||
|
auto iter = readRanges.begin();
|
||||||
|
for (const auto &read : reads) {
|
||||||
|
iter->begin.p = (const uint8_t *)read.data();
|
||||||
|
iter->begin.len = read.size();
|
||||||
|
checkBytes += read.size();
|
||||||
|
iter->end.len = 0;
|
||||||
|
iter->readVersion = version - 100;
|
||||||
|
++iter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results.resize(readRanges.size());
|
||||||
|
|
||||||
|
timer = now();
|
||||||
|
cs.check(readRanges.data(), results.data(), readRanges.size());
|
||||||
|
checkTime += now() - timer;
|
||||||
|
|
||||||
|
// Add unconditionally so that the load doesn't actually depend on the
|
||||||
|
// conflict rate
|
||||||
|
ConflictSet::WriteRange w;
|
||||||
|
w.begin.p = (const uint8_t *)write.data();
|
||||||
|
w.begin.len = write.size();
|
||||||
|
w.end.len = 0;
|
||||||
|
|
||||||
|
addBytes += write.size();
|
||||||
|
|
||||||
|
timer = now();
|
||||||
|
cs.addWrites(&w, 1, ++version);
|
||||||
|
addTime += now() - timer;
|
||||||
|
|
||||||
|
write = {};
|
||||||
|
reads.clear();
|
||||||
|
|
||||||
|
if (cs.getBytes() > peakMemory) {
|
||||||
|
peakMemory = cs.getBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
timer = now();
|
||||||
|
cs.setOldestVersion(version - 10000);
|
||||||
|
gcTime += now() - timer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
munmap((void *)mapOriginal, sizeOriginal);
|
||||||
|
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,
|
||||||
|
addBytes / addTime * 1e-6, gcTime / (gcTime + addTime) * 1e2,
|
||||||
|
double(peakMemory));
|
||||||
|
}
|
383
ServerBench.cpp
Normal file
383
ServerBench.cpp
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
#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"
|
||||||
|
|
||||||
|
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 kNumKeys = 10;
|
||||||
|
|
||||||
|
void workload(weaselab::ConflictSet *cs) {
|
||||||
|
int64_t version = kWindowSize;
|
||||||
|
for (;; transactions.fetch_add(1, std::memory_order_relaxed)) {
|
||||||
|
std::vector<int64_t> keyIndices;
|
||||||
|
for (int i = 0; i < kNumKeys; ++i) {
|
||||||
|
keyIndices.push_back(rand() % kTotalKeyRange);
|
||||||
|
}
|
||||||
|
std::sort(keyIndices.begin(), keyIndices.end());
|
||||||
|
std::vector<std::string> keys;
|
||||||
|
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 < kNumKeys; ++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());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<weaselab::ConflictSet::ReadRange> reads;
|
||||||
|
std::vector<weaselab::ConflictSet::WriteRange> writes;
|
||||||
|
std::vector<weaselab::ConflictSet::Result> results;
|
||||||
|
for (int i = 0; i < kNumKeys; ++i) {
|
||||||
|
writes.push_back({{(const uint8_t *)keys[i].data(), int(keys[i].size())},
|
||||||
|
{nullptr, 0}});
|
||||||
|
reads.push_back({{(const uint8_t *)keys[i].data(), int(keys[i].size())},
|
||||||
|
{nullptr, 0},
|
||||||
|
version - kWindowSize});
|
||||||
|
}
|
||||||
|
results.resize(reads.size());
|
||||||
|
|
||||||
|
cs->check(reads.data(), results.data(), reads.size());
|
||||||
|
bool ok = true;
|
||||||
|
for (auto result : results) {
|
||||||
|
ok &= result == weaselab::ConflictSet::Commit;
|
||||||
|
}
|
||||||
|
cs->addWrites(writes.data(), ok ? writes.size() : 0, version);
|
||||||
|
cs->setOldestVersion(version - kWindowSize);
|
||||||
|
++version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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]);
|
||||||
|
|
||||||
|
weaselab::ConflictSet cs{0};
|
||||||
|
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
|
||||||
|
|
||||||
|
auto w = std::thread{workload, &cs};
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
struct sockaddr_storage peer_addr = {};
|
||||||
|
socklen_t peer_addr_len = sizeof(peer_addr);
|
||||||
|
const int connfd =
|
||||||
|
accept(listenFd, (struct sockaddr *)&peer_addr, &peer_addr_len);
|
||||||
|
|
||||||
|
std::string body;
|
||||||
|
|
||||||
|
rusage r;
|
||||||
|
getrusage(RUSAGE_SELF, &r);
|
||||||
|
body += "# HELP process_cpu_seconds_total Total user and system CPU time "
|
||||||
|
"spent in seconds.\n# TYPE process_cpu_seconds_total counter\n"
|
||||||
|
"process_cpu_seconds_total ";
|
||||||
|
body += std::to_string(toSeconds(r.ru_utime) + toSeconds(r.ru_stime));
|
||||||
|
body += "\n";
|
||||||
|
body += "# HELP process_resident_memory_bytes Resident memory size in "
|
||||||
|
"bytes.\n# TYPE process_resident_memory_bytes gauge\n"
|
||||||
|
"process_resident_memory_bytes ";
|
||||||
|
body += std::to_string(getCurrentRSS());
|
||||||
|
body += "\n";
|
||||||
|
body += "# HELP transactions_total Total number of transactions\n"
|
||||||
|
"# TYPE transactions_total counter\n"
|
||||||
|
"transactions_total ";
|
||||||
|
body += std::to_string(transactions.load(std::memory_order_relaxed));
|
||||||
|
body += "\n";
|
||||||
|
#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;
|
||||||
|
}
|
1030
SkipList.cpp
Normal file
1030
SkipList.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,18 +3,68 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#ifndef PERF_TEST
|
||||||
|
#define PERF_TEST 0
|
||||||
|
#endif
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
for (int i = 1; i < argc; ++i) {
|
for (int i = 1; i < argc; ++i) {
|
||||||
printf("Running: %s\n", argv[i]);
|
|
||||||
std::ifstream t(argv[i], std::ios::binary);
|
std::ifstream t(argv[i], std::ios::binary);
|
||||||
std::stringstream buffer;
|
std::stringstream buffer;
|
||||||
buffer << t.rdbuf();
|
buffer << t.rdbuf();
|
||||||
auto str = buffer.str();
|
auto str = buffer.str();
|
||||||
TestDriver<ConflictSet> driver{(const uint8_t *)str.data(), str.size()};
|
Arbitrary arbitrary({(const uint8_t *)str.data(), str.size()});
|
||||||
while (!driver.next())
|
TestDriver<ConflictSet, !PERF_TEST> driver1{arbitrary};
|
||||||
;
|
TestDriver<ConflictSet, !PERF_TEST> driver2{arbitrary};
|
||||||
if (!driver.ok) {
|
bool done1 = false;
|
||||||
abort();
|
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("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
aarch64-symbol-imports.txt
Normal file
11
aarch64-symbol-imports.txt
Normal file
@@ -0,0 +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
|
8
aarch64-toolchain.cmake
Normal file
8
aarch64-toolchain.cmake
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
set(CMAKE_SYSTEM_NAME Linux)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||||
|
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")
|
19
apple-symbol-exports.txt
Normal file
19
apple-symbol-exports.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
_ConflictSet_addWrites
|
||||||
|
_ConflictSet_check
|
||||||
|
_ConflictSet_create
|
||||||
|
_ConflictSet_destroy
|
||||||
|
_ConflictSet_getBytes
|
||||||
|
_ConflictSet_setOldestVersion
|
||||||
|
__ZN8weaselab11ConflictSet16setOldestVersionEx
|
||||||
|
__ZN8weaselab11ConflictSet9addWritesEPKNS0_10WriteRangeEix
|
||||||
|
__ZN8weaselab11ConflictSetC1EOS0_
|
||||||
|
__ZN8weaselab11ConflictSetC1Ex
|
||||||
|
__ZN8weaselab11ConflictSetC2EOS0_
|
||||||
|
__ZN8weaselab11ConflictSetC2Ex
|
||||||
|
__ZN8weaselab11ConflictSetD1Ev
|
||||||
|
__ZN8weaselab11ConflictSetD2Ev
|
||||||
|
__ZN8weaselab11ConflictSetaSEOS0_
|
||||||
|
__ZNK8weaselab11ConflictSet12getMetricsV1EPPNS0_9MetricsV1EPi
|
||||||
|
__ZNK8weaselab11ConflictSet5checkEPKNS0_9ReadRangeEPNS0_6ResultEi
|
||||||
|
__ZNK8weaselab11ConflictSet8getBytesEv
|
||||||
|
__ZNK8weaselab11ConflictSet9MetricsV18getValueEv
|
10
apple-symbol-imports.txt
Normal file
10
apple-symbol-imports.txt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
___stack_chk_fail
|
||||||
|
___stack_chk_guard
|
||||||
|
__tlv_bootstrap
|
||||||
|
_abort
|
||||||
|
_bzero
|
||||||
|
_free
|
||||||
|
_malloc
|
||||||
|
_memcpy
|
||||||
|
_memmove
|
||||||
|
dyld_stub_binder
|
138
conflict_set.py
Normal file
138
conflict_set.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import ctypes
|
||||||
|
import enum
|
||||||
|
import os
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
class _Key(ctypes.Structure):
|
||||||
|
_fields_ = [("p", ctypes.POINTER(ctypes.c_ubyte)), ("len", ctypes.c_int)]
|
||||||
|
|
||||||
|
|
||||||
|
class ReadRange(ctypes.Structure):
|
||||||
|
_fields_ = [
|
||||||
|
("begin", _Key),
|
||||||
|
("end", _Key),
|
||||||
|
("readVersion", ctypes.c_int64),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class WriteRange(ctypes.Structure):
|
||||||
|
_fields_ = [("begin", _Key), ("end", _Key)]
|
||||||
|
|
||||||
|
|
||||||
|
class Result(enum.Enum):
|
||||||
|
COMMIT = 0
|
||||||
|
CONFLICT = 1
|
||||||
|
TOO_OLD = 2
|
||||||
|
|
||||||
|
|
||||||
|
def write(begin: bytes, end: Optional[bytes] = None) -> WriteRange:
|
||||||
|
b = (ctypes.c_ubyte * len(begin)).from_buffer(bytearray(begin))
|
||||||
|
|
||||||
|
if end is None:
|
||||||
|
e = (ctypes.c_ubyte * 0)()
|
||||||
|
else:
|
||||||
|
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)).from_buffer(bytearray(begin))
|
||||||
|
if end is None:
|
||||||
|
e = (ctypes.c_ubyte * 0)()
|
||||||
|
else:
|
||||||
|
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,
|
||||||
|
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):
|
||||||
|
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))()
|
||||||
|
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:
|
||||||
|
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:
|
||||||
|
self._lib.ConflictSet_destroy(self.p)
|
||||||
|
self.p = None
|
||||||
|
|
||||||
|
def __exit__(self, exception_type, exception_value, exception_traceback):
|
||||||
|
self.close()
|
@@ -7,17 +7,19 @@ int main(void) {
|
|||||||
ConflictSet_WriteRange w;
|
ConflictSet_WriteRange w;
|
||||||
ConflictSet_Result result;
|
ConflictSet_Result result;
|
||||||
ConflictSet_ReadRange r;
|
ConflictSet_ReadRange r;
|
||||||
|
int64_t bytes;
|
||||||
w.begin.p = (const uint8_t *)"0000";
|
w.begin.p = (const uint8_t *)"0000";
|
||||||
w.begin.len = 4;
|
w.begin.len = 4;
|
||||||
w.end.len = 0;
|
w.end.len = 0;
|
||||||
w.writeVersion = 1;
|
ConflictSet_addWrites(cs, &w, 1, 1);
|
||||||
ConflictSet_addWrites(cs, &w, 1);
|
|
||||||
r.begin.p = (const uint8_t *)"0000";
|
r.begin.p = (const uint8_t *)"0000";
|
||||||
r.begin.len = 4;
|
r.begin.len = 4;
|
||||||
r.end.len = 0;
|
r.end.len = 0;
|
||||||
r.readVersion = 0;
|
r.readVersion = 0;
|
||||||
ConflictSet_check(cs, &r, &result, 1);
|
ConflictSet_check(cs, &r, &result, 1);
|
||||||
assert(result == ConflictSet_Conflict);
|
assert(result == ConflictSet_Conflict);
|
||||||
|
bytes = ConflictSet_getBytes(cs);
|
||||||
|
assert(bytes > 0);
|
||||||
ConflictSet_destroy(cs);
|
ConflictSet_destroy(cs);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
#include "ConflictSet.h"
|
#include "ConflictSet.h"
|
||||||
|
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
using namespace weaselab;
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
ConflictSet cs(0);
|
ConflictSet cs(0);
|
||||||
@@ -8,8 +11,7 @@ int main(void) {
|
|||||||
w.begin.p = (const uint8_t *)"0000";
|
w.begin.p = (const uint8_t *)"0000";
|
||||||
w.begin.len = 4;
|
w.begin.len = 4;
|
||||||
w.end.len = 0;
|
w.end.len = 0;
|
||||||
w.writeVersion = 1;
|
cs.addWrites(&w, 1, 1);
|
||||||
cs.addWrites(&w, 1);
|
|
||||||
ConflictSet::Result result;
|
ConflictSet::Result result;
|
||||||
ConflictSet::ReadRange r;
|
ConflictSet::ReadRange r;
|
||||||
r.begin.p = (const uint8_t *)"0000";
|
r.begin.p = (const uint8_t *)"0000";
|
||||||
@@ -18,4 +20,16 @@ int main(void) {
|
|||||||
r.readVersion = 0;
|
r.readVersion = 0;
|
||||||
cs.check(&r, &result, 1);
|
cs.check(&r, &result, 1);
|
||||||
assert(result == ConflictSet::Conflict);
|
assert(result == ConflictSet::Conflict);
|
||||||
|
int64_t bytes = cs.getBytes();
|
||||||
|
assert(bytes > 0);
|
||||||
|
|
||||||
|
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.
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.
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.
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.
BIN
corpus/026c293f183c14c6f173718d432b73f41e19cba8
Normal file
BIN
corpus/026c293f183c14c6f173718d432b73f41e19cba8
Normal file
Binary file not shown.
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/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.
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.
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.
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.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user