tigerbeetle-node 0.11.12 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +212 -196
- package/dist/bin/aarch64-linux-gnu/client.node +0 -0
- package/dist/bin/aarch64-linux-musl/client.node +0 -0
- package/dist/bin/aarch64-macos/client.node +0 -0
- package/dist/bin/x86_64-linux-gnu/client.node +0 -0
- package/dist/bin/x86_64-linux-musl/client.node +0 -0
- package/dist/bin/x86_64-macos/client.node +0 -0
- package/dist/index.js +33 -1
- package/dist/index.js.map +1 -1
- package/package-lock.json +66 -0
- package/package.json +8 -17
- package/src/index.ts +56 -1
- package/src/node.zig +10 -9
- package/dist/.client.node.sha256 +0 -1
- package/scripts/build_lib.sh +0 -61
- package/scripts/download_node_headers.sh +0 -32
- package/src/tigerbeetle/scripts/benchmark.bat +0 -48
- package/src/tigerbeetle/scripts/benchmark.sh +0 -66
- package/src/tigerbeetle/scripts/confirm_image.sh +0 -44
- package/src/tigerbeetle/scripts/fuzz_loop.sh +0 -15
- package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +0 -7
- package/src/tigerbeetle/scripts/install.bat +0 -7
- package/src/tigerbeetle/scripts/install.sh +0 -21
- package/src/tigerbeetle/scripts/install_zig.bat +0 -113
- package/src/tigerbeetle/scripts/install_zig.sh +0 -90
- package/src/tigerbeetle/scripts/lint.zig +0 -199
- package/src/tigerbeetle/scripts/pre-commit.sh +0 -9
- package/src/tigerbeetle/scripts/scripts/benchmark.bat +0 -48
- package/src/tigerbeetle/scripts/scripts/benchmark.sh +0 -66
- package/src/tigerbeetle/scripts/scripts/confirm_image.sh +0 -44
- package/src/tigerbeetle/scripts/scripts/fuzz_loop.sh +0 -15
- package/src/tigerbeetle/scripts/scripts/fuzz_unique_errors.sh +0 -7
- package/src/tigerbeetle/scripts/scripts/install.bat +0 -7
- package/src/tigerbeetle/scripts/scripts/install.sh +0 -21
- package/src/tigerbeetle/scripts/scripts/install_zig.bat +0 -113
- package/src/tigerbeetle/scripts/scripts/install_zig.sh +0 -90
- package/src/tigerbeetle/scripts/scripts/lint.zig +0 -199
- package/src/tigerbeetle/scripts/scripts/pre-commit.sh +0 -9
- package/src/tigerbeetle/scripts/scripts/shellcheck.sh +0 -5
- package/src/tigerbeetle/scripts/scripts/tests_on_alpine.sh +0 -10
- package/src/tigerbeetle/scripts/scripts/tests_on_ubuntu.sh +0 -14
- package/src/tigerbeetle/scripts/scripts/upgrade_ubuntu_kernel.sh +0 -48
- package/src/tigerbeetle/scripts/scripts/validate_docs.sh +0 -23
- package/src/tigerbeetle/scripts/scripts/vr_state_enumerate +0 -46
- package/src/tigerbeetle/scripts/shellcheck.sh +0 -5
- package/src/tigerbeetle/scripts/tests_on_alpine.sh +0 -10
- package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +0 -14
- package/src/tigerbeetle/scripts/upgrade_ubuntu_kernel.sh +0 -48
- package/src/tigerbeetle/scripts/validate_docs.sh +0 -23
- package/src/tigerbeetle/scripts/vr_state_enumerate +0 -46
- package/src/tigerbeetle/src/benchmark.zig +0 -314
- package/src/tigerbeetle/src/config.zig +0 -234
- package/src/tigerbeetle/src/constants.zig +0 -436
- package/src/tigerbeetle/src/ewah.zig +0 -286
- package/src/tigerbeetle/src/ewah_benchmark.zig +0 -120
- package/src/tigerbeetle/src/ewah_fuzz.zig +0 -130
- package/src/tigerbeetle/src/fifo.zig +0 -120
- package/src/tigerbeetle/src/io/benchmark.zig +0 -213
- package/src/tigerbeetle/src/io/darwin.zig +0 -814
- package/src/tigerbeetle/src/io/linux.zig +0 -1062
- package/src/tigerbeetle/src/io/test.zig +0 -643
- package/src/tigerbeetle/src/io/windows.zig +0 -1183
- package/src/tigerbeetle/src/io.zig +0 -34
- package/src/tigerbeetle/src/iops.zig +0 -107
- package/src/tigerbeetle/src/lsm/README.md +0 -308
- package/src/tigerbeetle/src/lsm/binary_search.zig +0 -341
- package/src/tigerbeetle/src/lsm/bloom_filter.zig +0 -125
- package/src/tigerbeetle/src/lsm/compaction.zig +0 -603
- package/src/tigerbeetle/src/lsm/composite_key.zig +0 -77
- package/src/tigerbeetle/src/lsm/direction.zig +0 -11
- package/src/tigerbeetle/src/lsm/eytzinger.zig +0 -587
- package/src/tigerbeetle/src/lsm/eytzinger_benchmark.zig +0 -330
- package/src/tigerbeetle/src/lsm/forest.zig +0 -204
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +0 -401
- package/src/tigerbeetle/src/lsm/grid.zig +0 -573
- package/src/tigerbeetle/src/lsm/groove.zig +0 -972
- package/src/tigerbeetle/src/lsm/k_way_merge.zig +0 -474
- package/src/tigerbeetle/src/lsm/level_iterator.zig +0 -332
- package/src/tigerbeetle/src/lsm/manifest.zig +0 -617
- package/src/tigerbeetle/src/lsm/manifest_level.zig +0 -877
- package/src/tigerbeetle/src/lsm/manifest_log.zig +0 -789
- package/src/tigerbeetle/src/lsm/manifest_log_fuzz.zig +0 -691
- package/src/tigerbeetle/src/lsm/merge_iterator.zig +0 -106
- package/src/tigerbeetle/src/lsm/node_pool.zig +0 -235
- package/src/tigerbeetle/src/lsm/posted_groove.zig +0 -378
- package/src/tigerbeetle/src/lsm/segmented_array.zig +0 -1328
- package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +0 -148
- package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +0 -9
- package/src/tigerbeetle/src/lsm/set_associative_cache.zig +0 -850
- package/src/tigerbeetle/src/lsm/table.zig +0 -1031
- package/src/tigerbeetle/src/lsm/table_immutable.zig +0 -203
- package/src/tigerbeetle/src/lsm/table_iterator.zig +0 -340
- package/src/tigerbeetle/src/lsm/table_mutable.zig +0 -220
- package/src/tigerbeetle/src/lsm/test.zig +0 -438
- package/src/tigerbeetle/src/lsm/tree.zig +0 -1193
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +0 -474
- package/src/tigerbeetle/src/message_bus.zig +0 -1012
- package/src/tigerbeetle/src/message_pool.zig +0 -156
- package/src/tigerbeetle/src/ring_buffer.zig +0 -399
- package/src/tigerbeetle/src/simulator.zig +0 -569
- package/src/tigerbeetle/src/state_machine/auditor.zig +0 -577
- package/src/tigerbeetle/src/state_machine/workload.zig +0 -883
- package/src/tigerbeetle/src/state_machine.zig +0 -1881
- package/src/tigerbeetle/src/static_allocator.zig +0 -65
- package/src/tigerbeetle/src/stdx.zig +0 -162
- package/src/tigerbeetle/src/storage.zig +0 -393
- package/src/tigerbeetle/src/testing/cluster/message_bus.zig +0 -82
- package/src/tigerbeetle/src/testing/cluster/network.zig +0 -237
- package/src/tigerbeetle/src/testing/cluster/state_checker.zig +0 -169
- package/src/tigerbeetle/src/testing/cluster/storage_checker.zig +0 -202
- package/src/tigerbeetle/src/testing/cluster.zig +0 -443
- package/src/tigerbeetle/src/testing/fuzz.zig +0 -140
- package/src/tigerbeetle/src/testing/hash_log.zig +0 -66
- package/src/tigerbeetle/src/testing/id.zig +0 -99
- package/src/tigerbeetle/src/testing/packet_simulator.zig +0 -364
- package/src/tigerbeetle/src/testing/priority_queue.zig +0 -645
- package/src/tigerbeetle/src/testing/reply_sequence.zig +0 -139
- package/src/tigerbeetle/src/testing/state_machine.zig +0 -249
- package/src/tigerbeetle/src/testing/storage.zig +0 -757
- package/src/tigerbeetle/src/testing/table.zig +0 -247
- package/src/tigerbeetle/src/testing/time.zig +0 -84
- package/src/tigerbeetle/src/tigerbeetle.zig +0 -227
- package/src/tigerbeetle/src/time.zig +0 -112
- package/src/tigerbeetle/src/tracer.zig +0 -529
- package/src/tigerbeetle/src/unit_tests.zig +0 -42
- package/src/tigerbeetle/src/vopr.zig +0 -495
- package/src/tigerbeetle/src/vsr/README.md +0 -209
- package/src/tigerbeetle/src/vsr/client.zig +0 -544
- package/src/tigerbeetle/src/vsr/clock.zig +0 -853
- package/src/tigerbeetle/src/vsr/journal.zig +0 -2413
- package/src/tigerbeetle/src/vsr/journal_format_fuzz.zig +0 -111
- package/src/tigerbeetle/src/vsr/marzullo.zig +0 -309
- package/src/tigerbeetle/src/vsr/replica.zig +0 -6381
- package/src/tigerbeetle/src/vsr/replica_format.zig +0 -219
- package/src/tigerbeetle/src/vsr/superblock.zig +0 -1631
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +0 -256
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +0 -929
- package/src/tigerbeetle/src/vsr/superblock_free_set_fuzz.zig +0 -334
- package/src/tigerbeetle/src/vsr/superblock_fuzz.zig +0 -390
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +0 -615
- package/src/tigerbeetle/src/vsr/superblock_quorums.zig +0 -394
- package/src/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +0 -314
- package/src/tigerbeetle/src/vsr.zig +0 -1352
|
@@ -1,587 +0,0 @@
|
|
|
1
|
-
const std = @import("std");
|
|
2
|
-
const assert = std.debug.assert;
|
|
3
|
-
const math = std.math;
|
|
4
|
-
const mem = std.mem;
|
|
5
|
-
|
|
6
|
-
/// keys_count must be one less than a power of two. This allows us to align the layout
|
|
7
|
-
/// such that great great grandchildren of a node are not unnecessarily split across cache lines.
|
|
8
|
-
pub fn eytzinger(comptime keys_count: u32, comptime values_max: u32) type {
|
|
9
|
-
// This is not strictly necessary, but having less than 8 keys in the
|
|
10
|
-
// Eytzinger layout would make having the layout at all somewhat pointless.
|
|
11
|
-
assert(keys_count >= 3);
|
|
12
|
-
assert(math.isPowerOfTwo(keys_count + 1));
|
|
13
|
-
assert(values_max >= keys_count);
|
|
14
|
-
|
|
15
|
-
return struct {
|
|
16
|
-
const tree: [keys_count]u32 = blk: {
|
|
17
|
-
@setEvalBranchQuota((keys_count + 1) * 4 * math.log2(keys_count));
|
|
18
|
-
// n = 7:
|
|
19
|
-
// sorted values: 0 1 2 3 4 5 6
|
|
20
|
-
//
|
|
21
|
-
// binary search tree:
|
|
22
|
-
// 3
|
|
23
|
-
// 1 5
|
|
24
|
-
// 0 2 4 6
|
|
25
|
-
//
|
|
26
|
-
// Eytzinger layout:
|
|
27
|
-
// 3 1 5 0 2 4 6
|
|
28
|
-
//
|
|
29
|
-
// Our Eytzinger layout construction exactly matches the indexes
|
|
30
|
-
// used during a binary search, unlike common recursive implementations.
|
|
31
|
-
// This gives us more consistent performance and it's easy to verify.
|
|
32
|
-
//
|
|
33
|
-
// n = 21, X = padding:
|
|
34
|
-
// sorted values: 0 1 2 3 ... 20
|
|
35
|
-
//
|
|
36
|
-
// binary search tree:
|
|
37
|
-
// 10
|
|
38
|
-
// 4 15
|
|
39
|
-
// 1 7 12 18
|
|
40
|
-
// 0 2 5 8 11 13 16 19
|
|
41
|
-
// X X X 3 X 6 X 9 X X X 14 X 17 X 20
|
|
42
|
-
//
|
|
43
|
-
// Eytzinger layout:
|
|
44
|
-
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
|
45
|
-
// 10 4 15 1 7 12 1 0 2 5 8 11 13 16 19
|
|
46
|
-
//
|
|
47
|
-
// Note, we only support building a full Eytzinger tree of 2^k-1 keys.
|
|
48
|
-
// The last layer in the binary search tree for n=21 is not included
|
|
49
|
-
// in the Eytzinger layout. If we were to include it, our perfectly
|
|
50
|
-
// balanced construction would require sentinel values for the padding
|
|
51
|
-
// due to negative lookups.
|
|
52
|
-
|
|
53
|
-
var nodes: [keys_count]u32 = undefined;
|
|
54
|
-
var node: u32 = 0;
|
|
55
|
-
while (node < nodes.len) : (node += 1) {
|
|
56
|
-
// Left and right inclusive bounds for the children of this node,
|
|
57
|
-
// as if we were doing a binary search.
|
|
58
|
-
const l = if (left_ancestor(node)) |l| nodes[l] + 1 else 0;
|
|
59
|
-
const r = if (right_ancestor(node)) |r| nodes[r] - 1 else values_max - 1;
|
|
60
|
-
|
|
61
|
-
// The binary search index into source for this node in the Eytzinger layout.
|
|
62
|
-
// This is (r + l) / 2 ... but without overflow bugs.
|
|
63
|
-
nodes[node] = l + (r - l) / 2;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
break :blk nodes;
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
fn left_ancestor(node: u32) ?u32 {
|
|
70
|
-
var n = node;
|
|
71
|
-
while (!is_right_child(n)) {
|
|
72
|
-
n = parent(n) orelse return null;
|
|
73
|
-
}
|
|
74
|
-
return parent(n).?;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
fn right_ancestor(node: u32) ?u32 {
|
|
78
|
-
var n = node;
|
|
79
|
-
while (!is_left_child(n)) {
|
|
80
|
-
n = parent(n) orelse return null;
|
|
81
|
-
}
|
|
82
|
-
return parent(n).?;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
fn parent(node: u32) ?u32 {
|
|
86
|
-
if (node == 0) return null;
|
|
87
|
-
return (node - 1) / 2;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
fn is_right_child(node: u32) bool {
|
|
91
|
-
return node != 0 and node % 2 == 0;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
fn is_left_child(node: u32) bool {
|
|
95
|
-
return node != 0 and node % 2 != 0;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/// Writes the Eytzinger layout to the passed layout buffer.
|
|
99
|
-
/// The values slice must be sorted by key in ascending order.
|
|
100
|
-
pub fn layout_from_keys_or_values(
|
|
101
|
-
comptime Key: type,
|
|
102
|
-
comptime Value: type,
|
|
103
|
-
comptime key_from_value: fn (*const Value) callconv(.Inline) Key,
|
|
104
|
-
/// This sentinel must compare greater than all actual keys.
|
|
105
|
-
comptime sentinel_key: Key,
|
|
106
|
-
values: []const Value,
|
|
107
|
-
layout: *[keys_count + 1]Key,
|
|
108
|
-
) void {
|
|
109
|
-
comptime assert(tree.len + 1 == layout.len);
|
|
110
|
-
assert(values.len > 0);
|
|
111
|
-
assert(values.len <= values_max);
|
|
112
|
-
|
|
113
|
-
// We leave the first slot in layout empty for purposes of alignment.
|
|
114
|
-
// If we did not do this, the level in the tree with 16 great great
|
|
115
|
-
// grand childern would be split across cache lines with one child
|
|
116
|
-
// in the first cache line and the other 15 in the second.
|
|
117
|
-
mem.set(u8, mem.asBytes(&layout[0]), 0);
|
|
118
|
-
// 0 8 4 12 2 6 10 14 1 3 5 7 9 11 13 15
|
|
119
|
-
// ^
|
|
120
|
-
// padding element
|
|
121
|
-
|
|
122
|
-
for (tree) |values_index, i| {
|
|
123
|
-
if (values_index < values.len) {
|
|
124
|
-
layout[i + 1] = key_from_value(&values[values_index]);
|
|
125
|
-
} else {
|
|
126
|
-
layout[i + 1] = sentinel_key;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/// Returns a smaller slice into values where the target key may be found.
|
|
132
|
-
/// If the target key is present in values, the returned slice is guaranteed to contain it.
|
|
133
|
-
/// May return a slice of length one if an exact result is found.
|
|
134
|
-
/// May return a slice of length zero if the key is definitely not in values.
|
|
135
|
-
/// Otherwise, the caller will likely want to perform a binary search on the result.
|
|
136
|
-
/// TODO examine the generated machine code for this function
|
|
137
|
-
pub fn search_values(
|
|
138
|
-
comptime Key: type,
|
|
139
|
-
comptime Value: type,
|
|
140
|
-
comptime compare_keys: fn (Key, Key) callconv(.Inline) math.Order,
|
|
141
|
-
layout: *const [keys_count + 1]Key,
|
|
142
|
-
values: []const Value,
|
|
143
|
-
key: Key,
|
|
144
|
-
) []const Value {
|
|
145
|
-
assert(values.len > 0);
|
|
146
|
-
assert(values.len <= values_max);
|
|
147
|
-
|
|
148
|
-
const keys = layout[1..];
|
|
149
|
-
|
|
150
|
-
// "Array Layouts for Comparison-Based Search"
|
|
151
|
-
//
|
|
152
|
-
// Example using the n=21 tree above:
|
|
153
|
-
//
|
|
154
|
-
// If we search for 12.5 then we would have gone left at 13:
|
|
155
|
-
// i = 25 = 0b0...0011001
|
|
156
|
-
// i + 1 = 0b0...0011010
|
|
157
|
-
//
|
|
158
|
-
// Upper bound:
|
|
159
|
-
// ~(i + 1) = 0b1...1100101
|
|
160
|
-
// ffs(~(i + 1)) = index of least significant set bit + 1 = 0 + 1
|
|
161
|
-
// j = (i + 1) >> ffs(~(i + 1)) = 0b11010 >> 1 = 0b1101 = 13
|
|
162
|
-
// upper = if (j == 0) keys.len else j - 1 = 12
|
|
163
|
-
//
|
|
164
|
-
// Lower bound:
|
|
165
|
-
// ffs(i + 1) = index of least significant set bit + 1 = 1 + 1 = 2
|
|
166
|
-
// k = (i + 1) >> ffs(i + 1) = 0b11010 >> 2 = 0b110 = 6
|
|
167
|
-
// lower = if (k == 0) -1 else k - 1 = 5;
|
|
168
|
-
//
|
|
169
|
-
// Search for 3 in the tree 10 4 15:
|
|
170
|
-
// i = 2 * 1 + 1 = 3
|
|
171
|
-
// i + 1 = 0b100
|
|
172
|
-
//
|
|
173
|
-
// Upper bound:
|
|
174
|
-
// ~(i + 1) = 0b1...11011
|
|
175
|
-
// ffs(~(i + 1)) = 0 + 1 = 1
|
|
176
|
-
// j = (i + 1) >> ffs(~(i + 1)) = 0b100 >> 1 = 0b10 = 2
|
|
177
|
-
// upper = if (j == 0) keys.len else j - 1 = 1
|
|
178
|
-
//
|
|
179
|
-
// Lower bound:
|
|
180
|
-
// ffs(i + 1) = 2 + 1 = 3
|
|
181
|
-
// k = (i + 1) >> ffs(i + 1) = 0b100 >> 3 = 0
|
|
182
|
-
// lower = if (k == 0) -1 else k - 1 = -1;
|
|
183
|
-
|
|
184
|
-
var i: u32 = 0;
|
|
185
|
-
while (i < keys.len) {
|
|
186
|
-
// TODO use @prefetch when available: https://github.com/ziglang/zig/issues/3600
|
|
187
|
-
i = if (compare_keys(key, keys[i]) == .gt) 2 * i + 2 else 2 * i + 1;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// The upper_bound is the smallest key that is greater than or equal to the
|
|
191
|
-
// target. This is due to the greater than comparison in the loop above.
|
|
192
|
-
const upper = @as(u64, i + 1) >> ffs(~(i + 1));
|
|
193
|
-
const upper_bound: ?u32 = if (upper == 0) null else @intCast(u32, upper - 1);
|
|
194
|
-
|
|
195
|
-
// Because of the comparison used in the loop above, the lower bound is a < bound
|
|
196
|
-
// not a <= bound. Therefore in the case of an exact match we must use the upper bound.
|
|
197
|
-
const lower_bound: ?u32 = blk: {
|
|
198
|
-
if (upper_bound) |u| {
|
|
199
|
-
if (compare_keys(key, keys[u]) == .eq) break :blk u;
|
|
200
|
-
}
|
|
201
|
-
const lower = @as(u64, i + 1) >> ffs((i + 1));
|
|
202
|
-
break :blk if (lower == 0) null else @intCast(u32, lower - 1);
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
// We want to exclude the bounding keys to avoid re-checking them, except in the case
|
|
206
|
-
// of an exact match. This condition checks for an inexact match.
|
|
207
|
-
const exclusion = @boolToInt(lower_bound == null or upper_bound == null or
|
|
208
|
-
lower_bound.? != upper_bound.?);
|
|
209
|
-
|
|
210
|
-
// The exclusion alone may result in an upper bound one less than the lower bound.
|
|
211
|
-
// However, we add one to the upper bound to make it exclusive for slicing.
|
|
212
|
-
// This case indicates that key is not present in values.
|
|
213
|
-
const values_lower = if (lower_bound) |l| tree[l] + exclusion else 0;
|
|
214
|
-
// This must be an exclusive upper bound but upper_bound is inclusive. Thus, add 1.
|
|
215
|
-
const values_upper = if (upper_bound) |u| tree[u] + 1 - exclusion else values.len;
|
|
216
|
-
|
|
217
|
-
return values[values_lower..math.min(values.len, values_upper)];
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/// Returns an upper bound index into the corresponding values. The returned index is
|
|
221
|
-
/// less than values_count, or null to indicate that all keys are less than the target key.
|
|
222
|
-
/// TODO examine the generated machine code for this function
|
|
223
|
-
pub fn search_keys(
|
|
224
|
-
comptime Key: type,
|
|
225
|
-
comptime compare_keys: fn (Key, Key) callconv(.Inline) math.Order,
|
|
226
|
-
layout: *const [keys_count + 1]Key,
|
|
227
|
-
values_count: u32,
|
|
228
|
-
key: Key,
|
|
229
|
-
) ?u32 {
|
|
230
|
-
// See search_values() for the explanation and full implementation of the algorithm.
|
|
231
|
-
// This code is duplicated here to avoid unnecessary computation when only searching
|
|
232
|
-
// for an upper bound and to keep search_values() as readable as possible. Using helper
|
|
233
|
-
// functions would fragment the logic.
|
|
234
|
-
const keys = layout[1..];
|
|
235
|
-
var i: u32 = 0;
|
|
236
|
-
while (i < keys.len) {
|
|
237
|
-
// TODO use @prefetch when available: https://github.com/ziglang/zig/issues/3600
|
|
238
|
-
i = if (compare_keys(key, keys[i]) == .gt) 2 * i + 2 else 2 * i + 1;
|
|
239
|
-
}
|
|
240
|
-
const upper = @as(u64, i + 1) >> ffs(~(i + 1));
|
|
241
|
-
|
|
242
|
-
const out_of_bounds = upper == 0 or tree[upper - 1] >= values_count;
|
|
243
|
-
return if (out_of_bounds) null else tree[upper - 1];
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/// Returns one plus the index of the least significant 1-bit of x.
|
|
247
|
-
/// Asserts that x is not 0.
|
|
248
|
-
inline fn ffs(x: u32) u6 {
|
|
249
|
-
// clang __builtin_ffs() output:
|
|
250
|
-
// bsf ecx, edi
|
|
251
|
-
// mov eax, -1
|
|
252
|
-
// cmovne eax, ecx
|
|
253
|
-
// add eax, 1
|
|
254
|
-
// ret
|
|
255
|
-
//
|
|
256
|
-
// zig direct translation:
|
|
257
|
-
// export fn ffs(num: i32) i32 {
|
|
258
|
-
// return if (num == 0) 0 else @ctz(i32, num) + 1;
|
|
259
|
-
// }
|
|
260
|
-
//
|
|
261
|
-
// zig ffs output:
|
|
262
|
-
// tzcnt ecx, edi
|
|
263
|
-
// mov eax, -1
|
|
264
|
-
// cmovae eax, ecx
|
|
265
|
-
// inc eax
|
|
266
|
-
// ret
|
|
267
|
-
//
|
|
268
|
-
// tzcnt is essentially bsf but is defined to return the size
|
|
269
|
-
// of the operand if the operand is 0, whereas the output of
|
|
270
|
-
// bsf is undefined if the operand is 0.
|
|
271
|
-
|
|
272
|
-
// Since x will never be 0 in practice, we can drop the cmove
|
|
273
|
-
// above by not checking for 0.
|
|
274
|
-
// This function is always called with argument ~(i + 1) or (i + 1).
|
|
275
|
-
// Since i is unsigned, (i + 1) is always greater than 0. Therefore
|
|
276
|
-
// we only need to check for the case where ~(i + 1) == 0 which happens
|
|
277
|
-
// exactly when i + 1 is the maximum value for a u32.
|
|
278
|
-
comptime {
|
|
279
|
-
// Max i after the while loop in search_values() terminates.
|
|
280
|
-
const max_i = 2 * (tree.len - 1) + 2;
|
|
281
|
-
assert(max_i + 1 < math.maxInt(u32));
|
|
282
|
-
assert(~(max_i + 1) != 0);
|
|
283
|
-
}
|
|
284
|
-
assert(x != 0);
|
|
285
|
-
|
|
286
|
-
// Since we assert that x is not 0, we know that @ctz() returns
|
|
287
|
-
// a value in the range 0 to 31 inclusive. This means that the return
|
|
288
|
-
// value of @ctz() fits in a u5, but since we add 1 the return type of
|
|
289
|
-
// our function must be a u6.
|
|
290
|
-
comptime assert(31 + 1 <= math.maxInt(u6));
|
|
291
|
-
return @ctz(u32, x) + 1;
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const test_eytzinger = struct {
|
|
297
|
-
const log = false;
|
|
298
|
-
|
|
299
|
-
const Value = extern struct {
|
|
300
|
-
key: u32,
|
|
301
|
-
|
|
302
|
-
inline fn to_key(value: *const Value) u32 {
|
|
303
|
-
return value.key;
|
|
304
|
-
}
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
inline fn compare_keys(a: u32, b: u32) math.Order {
|
|
308
|
-
return math.order(a, b);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
const sentinel_key = math.maxInt(u32);
|
|
312
|
-
|
|
313
|
-
pub const Bounds = struct {
|
|
314
|
-
lower: ?u32,
|
|
315
|
-
upper: ?u32,
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
fn layout_from_keys_or_values(
|
|
319
|
-
comptime keys_count: usize,
|
|
320
|
-
comptime values_max: usize,
|
|
321
|
-
expect: []const u32,
|
|
322
|
-
) !void {
|
|
323
|
-
const e = eytzinger(keys_count, values_max);
|
|
324
|
-
|
|
325
|
-
var values: [values_max]Value = undefined;
|
|
326
|
-
for (values) |*v, i| v.* = .{ .key = @intCast(u32, i) };
|
|
327
|
-
|
|
328
|
-
var layout: [keys_count + 1]u32 = undefined;
|
|
329
|
-
|
|
330
|
-
e.layout_from_keys_or_values(u32, Value, Value.to_key, sentinel_key, &values, &layout);
|
|
331
|
-
|
|
332
|
-
try std.testing.expectEqualSlices(u32, expect, &layout);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
fn search(comptime keys_count: usize, comptime values_max: usize, sentinels_max: usize) !void {
|
|
336
|
-
assert(sentinels_max < values_max);
|
|
337
|
-
const e = eytzinger(keys_count, values_max);
|
|
338
|
-
|
|
339
|
-
var values_full: [values_max]Value = undefined;
|
|
340
|
-
// This 3 * i + 7 ensures that keys don't line up perfectly with indexes
|
|
341
|
-
// which could potentially catch bugs.
|
|
342
|
-
for (values_full) |*v, i| v.* = .{ .key = @intCast(u32, 3 * i + 7) };
|
|
343
|
-
|
|
344
|
-
var layout: [keys_count + 1]u32 = undefined;
|
|
345
|
-
|
|
346
|
-
var sentinels: usize = 0;
|
|
347
|
-
while (sentinels < sentinels_max) : (sentinels += 1) {
|
|
348
|
-
const values = values_full[0 .. values_full.len - sentinels];
|
|
349
|
-
e.layout_from_keys_or_values(u32, Value, Value.to_key, sentinel_key, values, &layout);
|
|
350
|
-
|
|
351
|
-
const keys = layout[1..];
|
|
352
|
-
|
|
353
|
-
if (log) {
|
|
354
|
-
std.debug.print("keys count: {}, values max: {}, sentinels: {}\n", .{
|
|
355
|
-
keys_count,
|
|
356
|
-
values_max,
|
|
357
|
-
sentinels,
|
|
358
|
-
});
|
|
359
|
-
std.debug.print("values: {any}\n", .{values});
|
|
360
|
-
std.debug.print("eytzinger layout: {any}\n", .{e.tree});
|
|
361
|
-
std.debug.print("keys: {any}\n", .{@as([]u32, keys)});
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// This is a regression test for our test code. We added this after we originally failed
|
|
365
|
-
// to test exactly the case this is checking for.
|
|
366
|
-
var at_least_one_target_key_not_in_values = false;
|
|
367
|
-
|
|
368
|
-
var target_key: u32 = 0;
|
|
369
|
-
while (target_key < values[values.len - 1].to_key() + 13) : (target_key += 1) {
|
|
370
|
-
if (log) std.debug.print("target key: {}\n", .{target_key});
|
|
371
|
-
var expect_keys: Bounds = .{
|
|
372
|
-
.lower = null,
|
|
373
|
-
.upper = null,
|
|
374
|
-
};
|
|
375
|
-
for (keys) |key, index| {
|
|
376
|
-
const i = @intCast(u32, index);
|
|
377
|
-
if (key == sentinel_key) continue;
|
|
378
|
-
switch (compare_keys(key, target_key)) {
|
|
379
|
-
.eq => {
|
|
380
|
-
expect_keys.lower = i;
|
|
381
|
-
expect_keys.upper = i;
|
|
382
|
-
break;
|
|
383
|
-
},
|
|
384
|
-
.gt => if (expect_keys.upper == null or
|
|
385
|
-
compare_keys(key, keys[expect_keys.upper.?]) == .lt)
|
|
386
|
-
{
|
|
387
|
-
expect_keys.upper = i;
|
|
388
|
-
},
|
|
389
|
-
.lt => if (expect_keys.lower == null or
|
|
390
|
-
compare_keys(key, keys[expect_keys.lower.?]) == .gt)
|
|
391
|
-
{
|
|
392
|
-
expect_keys.lower = i;
|
|
393
|
-
},
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const expect_upper_bound = if (expect_keys.upper) |u| e.tree[u] else null;
|
|
398
|
-
var actual_upper_bound = e.search_keys(
|
|
399
|
-
u32,
|
|
400
|
-
compare_keys,
|
|
401
|
-
&layout,
|
|
402
|
-
@intCast(u32, values.len),
|
|
403
|
-
target_key,
|
|
404
|
-
);
|
|
405
|
-
try std.testing.expectEqual(expect_upper_bound, actual_upper_bound);
|
|
406
|
-
|
|
407
|
-
var expect_values: Bounds = .{
|
|
408
|
-
.lower = null,
|
|
409
|
-
.upper = null,
|
|
410
|
-
};
|
|
411
|
-
var target_key_found = false;
|
|
412
|
-
for (values) |value, i| {
|
|
413
|
-
if (compare_keys(value.to_key(), target_key) == .eq) target_key_found = true;
|
|
414
|
-
|
|
415
|
-
if (expect_keys.lower) |l| {
|
|
416
|
-
if (compare_keys(value.to_key(), keys[l]) == .eq) {
|
|
417
|
-
expect_values.lower = @intCast(u32, i);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
if (expect_keys.upper) |u| {
|
|
421
|
-
if (compare_keys(value.to_key(), keys[u]) == .eq) {
|
|
422
|
-
expect_values.upper = @intCast(u32, i);
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
if (!target_key_found) at_least_one_target_key_not_in_values = true;
|
|
427
|
-
assert((expect_keys.lower == null) == (expect_values.lower == null));
|
|
428
|
-
assert((expect_keys.upper == null) == (expect_values.upper == null));
|
|
429
|
-
|
|
430
|
-
if (expect_values.lower != null) {
|
|
431
|
-
if (compare_keys(values[expect_values.lower.?].to_key(), target_key) == .lt) {
|
|
432
|
-
expect_values.lower.? += 1;
|
|
433
|
-
} else {
|
|
434
|
-
assert(compare_keys(values[expect_values.lower.?].to_key(), target_key) == .eq);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
if (expect_values.upper != null) {
|
|
438
|
-
var exclusion: u32 = undefined;
|
|
439
|
-
if (compare_keys(values[expect_values.upper.?].to_key(), target_key) == .gt) {
|
|
440
|
-
exclusion = 1;
|
|
441
|
-
} else {
|
|
442
|
-
exclusion = 0;
|
|
443
|
-
assert(compare_keys(values[expect_values.upper.?].to_key(), target_key) == .eq);
|
|
444
|
-
}
|
|
445
|
-
// Convert the inclusive upper bound to an exclusive upper bound for slicing.
|
|
446
|
-
expect_values.upper.? += 1;
|
|
447
|
-
// Now, apply the conditional exclusion. The order is important here to avoid
|
|
448
|
-
// underflow if expect_values.upper is 0.
|
|
449
|
-
expect_values.upper.? -= exclusion;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
const expect_slice_lower = expect_values.lower orelse 0;
|
|
453
|
-
const expect_slice_upper = expect_values.upper orelse values.len;
|
|
454
|
-
const expect_slice = values[expect_slice_lower..expect_slice_upper];
|
|
455
|
-
if (target_key_found and expect_slice.len == 1) {
|
|
456
|
-
assert(compare_keys(expect_slice[0].to_key(), target_key) == .eq);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
const actual_slice = e.search_values(
|
|
460
|
-
u32,
|
|
461
|
-
Value,
|
|
462
|
-
compare_keys,
|
|
463
|
-
&layout,
|
|
464
|
-
values,
|
|
465
|
-
target_key,
|
|
466
|
-
);
|
|
467
|
-
|
|
468
|
-
try std.testing.expectEqual(
|
|
469
|
-
@as([*]const Value, expect_slice.ptr),
|
|
470
|
-
actual_slice.ptr,
|
|
471
|
-
);
|
|
472
|
-
try std.testing.expectEqual(expect_slice.len, actual_slice.len);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
assert(at_least_one_target_key_not_in_values);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
};
|
|
479
|
-
|
|
480
|
-
test "eytzinger: equal key and value count" {
|
|
481
|
-
// zig fmt: off
|
|
482
|
-
try test_eytzinger.layout_from_keys_or_values(3, 3, &[_]u32{ 0,
|
|
483
|
-
1,
|
|
484
|
-
0, 2,
|
|
485
|
-
});
|
|
486
|
-
try test_eytzinger.layout_from_keys_or_values(7, 7, &[_]u32{ 0,
|
|
487
|
-
3,
|
|
488
|
-
1, 5,
|
|
489
|
-
0, 2, 4, 6,
|
|
490
|
-
});
|
|
491
|
-
try test_eytzinger.layout_from_keys_or_values(15, 15, &[_]u32{ 0,
|
|
492
|
-
7,
|
|
493
|
-
3, 11,
|
|
494
|
-
1, 5, 9, 13,
|
|
495
|
-
0, 2, 4, 6, 8, 10, 12, 14,
|
|
496
|
-
});
|
|
497
|
-
// zig fmt: on
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
test "eytzinger: power of two value count > key count" {
|
|
501
|
-
// zig fmt: off
|
|
502
|
-
try test_eytzinger.layout_from_keys_or_values(3, 8, &[_]u32{ 0,
|
|
503
|
-
3,
|
|
504
|
-
1, 5,
|
|
505
|
-
});
|
|
506
|
-
try test_eytzinger.layout_from_keys_or_values(3, 16, &[_]u32{ 0,
|
|
507
|
-
7,
|
|
508
|
-
3, 11,
|
|
509
|
-
});
|
|
510
|
-
try test_eytzinger.layout_from_keys_or_values(7, 16, &[_]u32{ 0,
|
|
511
|
-
7,
|
|
512
|
-
3, 11,
|
|
513
|
-
1, 5, 9, 13,
|
|
514
|
-
});
|
|
515
|
-
try test_eytzinger.layout_from_keys_or_values(15, 32, &[_]u32{ 0,
|
|
516
|
-
15,
|
|
517
|
-
7, 23,
|
|
518
|
-
3, 11, 19, 27,
|
|
519
|
-
1, 5, 9, 13, 17, 21, 25, 29,
|
|
520
|
-
});
|
|
521
|
-
// zig fmt: on
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
test "eytzinger: non power of two value count" {
|
|
525
|
-
// zig fmt: off
|
|
526
|
-
try test_eytzinger.layout_from_keys_or_values(7, 13, &[_]u32{ 0,
|
|
527
|
-
6,
|
|
528
|
-
2, 9,
|
|
529
|
-
0, 4, 7, 11,
|
|
530
|
-
});
|
|
531
|
-
try test_eytzinger.layout_from_keys_or_values(7, 19, &[_]u32{ 0,
|
|
532
|
-
9,
|
|
533
|
-
4, 14,
|
|
534
|
-
1, 6, 11, 16,
|
|
535
|
-
});
|
|
536
|
-
try test_eytzinger.layout_from_keys_or_values(7, 20, &[_]u32{ 0,
|
|
537
|
-
9,
|
|
538
|
-
4, 14,
|
|
539
|
-
1, 6, 11, 17,
|
|
540
|
-
});
|
|
541
|
-
try test_eytzinger.layout_from_keys_or_values(15, 21, &[_]u32{ 0,
|
|
542
|
-
10,
|
|
543
|
-
4, 15,
|
|
544
|
-
1, 7, 12, 18,
|
|
545
|
-
0, 2, 5, 8, 11, 13, 16, 19,
|
|
546
|
-
});
|
|
547
|
-
try test_eytzinger.layout_from_keys_or_values(7, 7919, &[_]u32{ 0,
|
|
548
|
-
3959,
|
|
549
|
-
1979, 5939,
|
|
550
|
-
989, 2969, 4949, 6929,
|
|
551
|
-
});
|
|
552
|
-
try test_eytzinger.layout_from_keys_or_values(7, 7920, &[_]u32{ 0,
|
|
553
|
-
3959,
|
|
554
|
-
1979, 5939,
|
|
555
|
-
989, 2969, 4949, 6929,
|
|
556
|
-
});
|
|
557
|
-
try test_eytzinger.layout_from_keys_or_values(7, 7921, &[_]u32{ 0,
|
|
558
|
-
3960,
|
|
559
|
-
1979, 5940,
|
|
560
|
-
989, 2969, 4950, 6930,
|
|
561
|
-
});
|
|
562
|
-
// zig fmt: on
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
test "eytzinger: handle classic binary search overflow" {
|
|
566
|
-
// Many binary search implementations do mid = (l + r) / 2
|
|
567
|
-
// However, if l + r is outside of the range of the integer type
|
|
568
|
-
// used, this calculation will needlessly overflow.
|
|
569
|
-
// https://en.wikipedia.org/wiki/Binary_search_algorithm#Implementation_issues
|
|
570
|
-
|
|
571
|
-
// We don't use the utilities in test_eytzinger as they place the key/value arrays
|
|
572
|
-
// on the stack, which the OS doesn't like if you have 2^32 values.
|
|
573
|
-
const actual = eytzinger(3, math.maxInt(u32)).tree;
|
|
574
|
-
const expect = [_]u32{ 2_147_483_647, 1_073_741_823, 3_221_225_471 };
|
|
575
|
-
try std.testing.expectEqualSlices(u32, &expect, &actual);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
test "eytzinger: search" {
|
|
579
|
-
comptime var power_of_2: u32 = 4;
|
|
580
|
-
inline while (power_of_2 <= 32) : (power_of_2 <<= 1) {
|
|
581
|
-
comptime var values_count = power_of_2;
|
|
582
|
-
inline while (values_count <= 64) : (values_count += 1) {
|
|
583
|
-
const keys_count = power_of_2 - 1;
|
|
584
|
-
try test_eytzinger.search(keys_count, values_count, values_count - 1);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|