tigerbeetle-node 0.10.0 → 0.11.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 +302 -101
- package/dist/index.d.ts +70 -72
- package/dist/index.js +70 -72
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/scripts/download_node_headers.sh +14 -7
- package/src/index.ts +6 -10
- package/src/node.zig +6 -3
- package/src/tigerbeetle/scripts/benchmark.sh +4 -4
- package/src/tigerbeetle/scripts/confirm_image.sh +44 -0
- package/src/tigerbeetle/scripts/fuzz_loop.sh +15 -0
- package/src/tigerbeetle/scripts/fuzz_unique_errors.sh +7 -0
- package/src/tigerbeetle/scripts/install.sh +19 -4
- package/src/tigerbeetle/scripts/install_zig.bat +5 -1
- package/src/tigerbeetle/scripts/install_zig.sh +24 -14
- package/src/tigerbeetle/scripts/pre-commit.sh +9 -0
- package/src/tigerbeetle/scripts/shellcheck.sh +5 -0
- package/src/tigerbeetle/scripts/tests_on_alpine.sh +10 -0
- package/src/tigerbeetle/scripts/tests_on_ubuntu.sh +14 -0
- package/src/tigerbeetle/src/benchmark.zig +4 -2
- package/src/tigerbeetle/src/benchmark_array_search.zig +3 -3
- package/src/tigerbeetle/src/c/tb_client/thread.zig +8 -9
- package/src/tigerbeetle/src/c/tb_client.h +100 -80
- package/src/tigerbeetle/src/c/tb_client.zig +4 -1
- package/src/tigerbeetle/src/cli.zig +1 -1
- package/src/tigerbeetle/src/config.zig +48 -16
- package/src/tigerbeetle/src/demo.zig +3 -1
- package/src/tigerbeetle/src/eytzinger_benchmark.zig +3 -3
- package/src/tigerbeetle/src/io/linux.zig +1 -1
- package/src/tigerbeetle/src/lsm/README.md +214 -0
- package/src/tigerbeetle/src/lsm/binary_search.zig +137 -10
- package/src/tigerbeetle/src/lsm/bloom_filter.zig +43 -0
- package/src/tigerbeetle/src/lsm/compaction.zig +352 -398
- package/src/tigerbeetle/src/lsm/composite_key.zig +2 -0
- package/src/tigerbeetle/src/lsm/eytzinger.zig +1 -1
- package/src/tigerbeetle/src/lsm/forest.zig +21 -447
- package/src/tigerbeetle/src/lsm/forest_fuzz.zig +412 -0
- package/src/tigerbeetle/src/lsm/grid.zig +145 -69
- package/src/tigerbeetle/src/lsm/groove.zig +196 -133
- package/src/tigerbeetle/src/lsm/k_way_merge.zig +40 -18
- package/src/tigerbeetle/src/lsm/level_iterator.zig +28 -9
- package/src/tigerbeetle/src/lsm/manifest.zig +81 -181
- package/src/tigerbeetle/src/lsm/manifest_level.zig +210 -454
- package/src/tigerbeetle/src/lsm/manifest_log.zig +77 -28
- package/src/tigerbeetle/src/lsm/posted_groove.zig +64 -76
- package/src/tigerbeetle/src/lsm/segmented_array.zig +561 -241
- package/src/tigerbeetle/src/lsm/segmented_array_benchmark.zig +148 -0
- package/src/tigerbeetle/src/lsm/segmented_array_fuzz.zig +9 -0
- package/src/tigerbeetle/src/lsm/set_associative_cache.zig +62 -12
- package/src/tigerbeetle/src/lsm/table.zig +83 -48
- package/src/tigerbeetle/src/lsm/table_immutable.zig +30 -23
- package/src/tigerbeetle/src/lsm/table_iterator.zig +25 -14
- package/src/tigerbeetle/src/lsm/table_mutable.zig +63 -12
- package/src/tigerbeetle/src/lsm/test.zig +49 -55
- package/src/tigerbeetle/src/lsm/tree.zig +407 -402
- package/src/tigerbeetle/src/lsm/tree_fuzz.zig +457 -0
- package/src/tigerbeetle/src/main.zig +28 -6
- package/src/tigerbeetle/src/message_bus.zig +2 -2
- package/src/tigerbeetle/src/message_pool.zig +14 -17
- package/src/tigerbeetle/src/simulator.zig +145 -112
- package/src/tigerbeetle/src/state_machine.zig +338 -228
- package/src/tigerbeetle/src/static_allocator.zig +65 -0
- package/src/tigerbeetle/src/storage.zig +3 -7
- package/src/tigerbeetle/src/test/accounting/auditor.zig +577 -0
- package/src/tigerbeetle/src/test/accounting/workload.zig +819 -0
- package/src/tigerbeetle/src/test/cluster.zig +18 -48
- package/src/tigerbeetle/src/test/conductor.zig +365 -0
- package/src/tigerbeetle/src/test/fuzz.zig +121 -0
- package/src/tigerbeetle/src/test/id.zig +89 -0
- package/src/tigerbeetle/src/test/priority_queue.zig +645 -0
- package/src/tigerbeetle/src/test/state_checker.zig +93 -69
- package/src/tigerbeetle/src/test/state_machine.zig +11 -35
- package/src/tigerbeetle/src/test/storage.zig +29 -8
- package/src/tigerbeetle/src/tigerbeetle.zig +14 -16
- package/src/tigerbeetle/src/unit_tests.zig +7 -0
- package/src/tigerbeetle/src/vopr.zig +494 -0
- package/src/tigerbeetle/src/vopr_hub/README.md +58 -0
- package/src/tigerbeetle/src/vopr_hub/SETUP.md +199 -0
- package/src/tigerbeetle/src/vopr_hub/go.mod +3 -0
- package/src/tigerbeetle/src/vopr_hub/main.go +1022 -0
- package/src/tigerbeetle/src/vopr_hub/scheduler/go.mod +3 -0
- package/src/tigerbeetle/src/vopr_hub/scheduler/main.go +403 -0
- package/src/tigerbeetle/src/vsr/client.zig +13 -0
- package/src/tigerbeetle/src/vsr/journal.zig +16 -13
- package/src/tigerbeetle/src/vsr/replica.zig +924 -491
- package/src/tigerbeetle/src/vsr/superblock.zig +55 -37
- package/src/tigerbeetle/src/vsr/superblock_client_table.zig +7 -10
- package/src/tigerbeetle/src/vsr/superblock_free_set.zig +2 -2
- package/src/tigerbeetle/src/vsr/superblock_manifest.zig +18 -3
- package/src/tigerbeetle/src/vsr.zig +75 -55
- package/src/tigerbeetle/scripts/vopr.bat +0 -48
- package/src/tigerbeetle/scripts/vopr.sh +0 -33
|
@@ -3,31 +3,72 @@ const std = @import("std");
|
|
|
3
3
|
const assert = std.debug.assert;
|
|
4
4
|
const math = std.math;
|
|
5
5
|
const mem = std.mem;
|
|
6
|
-
const meta = std.meta;
|
|
7
6
|
|
|
8
7
|
const div_ceil = @import("../util.zig").div_ceil;
|
|
9
|
-
|
|
8
|
+
const binary_search_values_raw = @import("binary_search.zig").binary_search_values_raw;
|
|
9
|
+
const binary_search_keys = @import("binary_search.zig").binary_search_keys;
|
|
10
10
|
const Direction = @import("direction.zig").Direction;
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
/// A "segmented array" is an array with efficient (amortized) random-insert/remove operations.
|
|
13
|
+
/// Also known as an "unrolled linked list": https://en.wikipedia.org/wiki/Unrolled_linked_list
|
|
14
|
+
///
|
|
15
|
+
/// The structure consists of an array list of "nodes". Each node is a non-empty array of T.
|
|
16
|
+
/// When a node fills, it is split into two adjacent, partially-full nodes.
|
|
17
|
+
/// When a node empties, it is joined with a nearby node.
|
|
18
|
+
///
|
|
19
|
+
/// An absolute index is offset from the start of the segmented array.
|
|
20
|
+
/// A relative index is offset from the start of a node.
|
|
21
|
+
pub fn SegmentedArray(
|
|
22
|
+
comptime T: type,
|
|
23
|
+
comptime NodePool: type,
|
|
24
|
+
comptime element_count_max: u32,
|
|
25
|
+
comptime options: Options,
|
|
26
|
+
) type {
|
|
27
|
+
return SegmentedArrayType(T, NodePool, element_count_max, null, {}, {}, options);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub fn SortedSegmentedArray(
|
|
31
|
+
comptime T: type,
|
|
32
|
+
comptime NodePool: type,
|
|
33
|
+
comptime element_count_max: u32,
|
|
34
|
+
comptime Key: type,
|
|
35
|
+
comptime key_from_value: fn (*const T) callconv(.Inline) Key,
|
|
36
|
+
comptime compare_keys: fn (Key, Key) callconv(.Inline) math.Order,
|
|
37
|
+
comptime options: Options,
|
|
38
|
+
) type {
|
|
39
|
+
return SegmentedArrayType(T, NodePool, element_count_max, Key, key_from_value, compare_keys, options);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
pub const Options = struct {
|
|
43
|
+
/// Assert all invariants before/after every public function.
|
|
44
|
+
/// Very expensive - only enable for debugging/fuzzing.
|
|
45
|
+
verify: bool = false,
|
|
15
46
|
};
|
|
16
47
|
|
|
17
|
-
|
|
48
|
+
fn SegmentedArrayType(
|
|
18
49
|
comptime T: type,
|
|
19
50
|
comptime NodePool: type,
|
|
20
51
|
comptime element_count_max: u32,
|
|
52
|
+
// Set when the SegmentedArray is ordered:
|
|
53
|
+
comptime Key: ?type,
|
|
54
|
+
comptime key_from_value: if (Key) |K| (fn (*const T) callconv(.Inline) K) else void,
|
|
55
|
+
comptime compare_keys: if (Key) |K| (fn (K, K) callconv(.Inline) math.Order) else void,
|
|
56
|
+
comptime options: Options,
|
|
21
57
|
) type {
|
|
22
58
|
return struct {
|
|
23
59
|
const Self = @This();
|
|
24
60
|
|
|
61
|
+
pub const Cursor = struct {
|
|
62
|
+
node: u32,
|
|
63
|
+
relative_index: u32,
|
|
64
|
+
};
|
|
65
|
+
|
|
25
66
|
// We can't use @divExact() here as we store TableInfo structs of various sizes in this
|
|
26
67
|
// data structure. This means that there may be padding at the end of the node.
|
|
27
68
|
pub const node_capacity = blk: {
|
|
28
|
-
const max = NodePool.node_size
|
|
69
|
+
const max = @divFloor(NodePool.node_size, @sizeOf(T));
|
|
29
70
|
|
|
30
|
-
// We require that the node is evenly divisible by 2 to simplify our code
|
|
71
|
+
// We require that the node capacity is evenly divisible by 2 to simplify our code
|
|
31
72
|
// that splits/joins nodes at the midpoint.
|
|
32
73
|
const capacity = if (max % 2 == 0) max else max - 1;
|
|
33
74
|
|
|
@@ -39,9 +80,15 @@ pub fn SegmentedArray(
|
|
|
39
80
|
comptime {
|
|
40
81
|
// If this assert fails, we should be using a non-segmented array instead!
|
|
41
82
|
assert(element_count_max > node_capacity);
|
|
83
|
+
|
|
84
|
+
// We use u32 for indexes and counts.
|
|
85
|
+
assert(element_count_max <= std.math.maxInt(u32));
|
|
86
|
+
|
|
87
|
+
// The buffers returned from the node_pool must be able to store T with correct alignment.
|
|
88
|
+
assert(NodePool.node_alignment >= @alignOf(T));
|
|
42
89
|
}
|
|
43
90
|
|
|
44
|
-
pub const
|
|
91
|
+
pub const node_count_max_naive = blk: {
|
|
45
92
|
// If a node fills up it is divided into two new nodes. Therefore,
|
|
46
93
|
// the worst possible space overhead is when all nodes are half full.
|
|
47
94
|
// This uses flooring division, we want to examine the worst case here.
|
|
@@ -49,8 +96,23 @@ pub fn SegmentedArray(
|
|
|
49
96
|
break :blk div_ceil(element_count_max, elements_per_node_min);
|
|
50
97
|
};
|
|
51
98
|
|
|
99
|
+
// We can't always actually reach node_count_max_naive in all configurations.
|
|
100
|
+
// If we're at node_count_max_naive-1 nodes, in order to split one more node we need:
|
|
101
|
+
pub const node_count_max = if (element_count_max >=
|
|
102
|
+
// * The node that we split must be full.
|
|
103
|
+
node_capacity +
|
|
104
|
+
// * The last node must have at least one element.
|
|
105
|
+
1 +
|
|
106
|
+
// * All other nodes must be at least half-full.
|
|
107
|
+
((node_count_max_naive -| 3) * @divExact(node_capacity, 2)) +
|
|
108
|
+
// * And then we insert one more element into the full node.
|
|
109
|
+
1)
|
|
110
|
+
node_count_max_naive
|
|
111
|
+
else
|
|
112
|
+
node_count_max_naive - 1;
|
|
113
|
+
|
|
52
114
|
node_count: u32 = 0,
|
|
53
|
-
/// This is the segmented array
|
|
115
|
+
/// This is the segmented array. The first node_count pointers are non-null.
|
|
54
116
|
/// The rest are null. We only use optional pointers here to get safety checks.
|
|
55
117
|
nodes: *[node_count_max]?*[node_capacity]T,
|
|
56
118
|
/// Since nodes in a segmented array are usually not full, computing the absolute index
|
|
@@ -72,13 +134,19 @@ pub fn SegmentedArray(
|
|
|
72
134
|
mem.set(?*[node_capacity]T, nodes, null);
|
|
73
135
|
indexes[0] = 0;
|
|
74
136
|
|
|
75
|
-
|
|
137
|
+
const array = Self{
|
|
76
138
|
.nodes = nodes,
|
|
77
139
|
.indexes = indexes,
|
|
78
140
|
};
|
|
141
|
+
|
|
142
|
+
if (options.verify) array.verify();
|
|
143
|
+
|
|
144
|
+
return array;
|
|
79
145
|
}
|
|
80
146
|
|
|
81
147
|
pub fn deinit(array: Self, allocator: mem.Allocator, node_pool: ?*NodePool) void {
|
|
148
|
+
if (options.verify) array.verify();
|
|
149
|
+
|
|
82
150
|
for (array.nodes[0..array.node_count]) |node| {
|
|
83
151
|
node_pool.?.release(
|
|
84
152
|
@ptrCast(NodePool.Node, @alignCast(NodePool.node_alignment, node.?)),
|
|
@@ -88,7 +156,78 @@ pub fn SegmentedArray(
|
|
|
88
156
|
allocator.free(array.indexes);
|
|
89
157
|
}
|
|
90
158
|
|
|
91
|
-
pub fn
|
|
159
|
+
pub fn verify(array: Self) void {
|
|
160
|
+
assert(array.node_count <= node_count_max);
|
|
161
|
+
for (array.nodes) |node, node_index| {
|
|
162
|
+
if (node_index < array.node_count) {
|
|
163
|
+
// The first node_count pointers are non-null.
|
|
164
|
+
assert(node != null);
|
|
165
|
+
} else {
|
|
166
|
+
// The rest are non-null.
|
|
167
|
+
assert(node == null);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (array.nodes[0..array.node_count]) |_, node_index| {
|
|
171
|
+
const c = array.count(@intCast(u32, node_index));
|
|
172
|
+
// Every node is at most full.
|
|
173
|
+
assert(c <= node_capacity);
|
|
174
|
+
// Every node is at least half-full, except the last.
|
|
175
|
+
if (node_index < array.node_count - 1) {
|
|
176
|
+
assert(c >= @divTrunc(node_capacity, 2));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (Key) |K| {
|
|
180
|
+
// If Key is not null then the elements must be sorted by key_from_value (but not necessarily unique).
|
|
181
|
+
var key_prior_or_null: ?K = null;
|
|
182
|
+
for (array.nodes[0..array.node_count]) |_, node_index| {
|
|
183
|
+
for (array.node_elements(@intCast(u32, node_index))) |*value| {
|
|
184
|
+
const key = key_from_value(value);
|
|
185
|
+
if (key_prior_or_null) |key_prior| {
|
|
186
|
+
assert(compare_keys(key_prior, key) != .gt);
|
|
187
|
+
}
|
|
188
|
+
key_prior_or_null = key;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
pub usingnamespace if (Key) |_| struct {
|
|
195
|
+
/// Returns the absolute index of the element being inserted.
|
|
196
|
+
pub fn insert_element(
|
|
197
|
+
array: *Self,
|
|
198
|
+
node_pool: *NodePool,
|
|
199
|
+
element: T,
|
|
200
|
+
) u32 {
|
|
201
|
+
if (options.verify) array.verify();
|
|
202
|
+
|
|
203
|
+
const cursor = array.search(key_from_value(&element));
|
|
204
|
+
const absolute_index = array.absolute_index_for_cursor(cursor);
|
|
205
|
+
array.insert_elements_at_absolute_index(node_pool, absolute_index, &[_]T{element});
|
|
206
|
+
|
|
207
|
+
if (options.verify) array.verify();
|
|
208
|
+
|
|
209
|
+
return absolute_index;
|
|
210
|
+
}
|
|
211
|
+
} else struct {
|
|
212
|
+
pub fn insert_elements(
|
|
213
|
+
array: *Self,
|
|
214
|
+
node_pool: *NodePool,
|
|
215
|
+
absolute_index: u32,
|
|
216
|
+
elements: []const T,
|
|
217
|
+
) void {
|
|
218
|
+
if (options.verify) array.verify();
|
|
219
|
+
|
|
220
|
+
array.insert_elements_at_absolute_index(
|
|
221
|
+
node_pool,
|
|
222
|
+
absolute_index,
|
|
223
|
+
elements,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
if (options.verify) array.verify();
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
fn insert_elements_at_absolute_index(
|
|
92
231
|
array: *Self,
|
|
93
232
|
node_pool: *NodePool,
|
|
94
233
|
absolute_index: u32,
|
|
@@ -136,6 +275,7 @@ pub fn SegmentedArray(
|
|
|
136
275
|
|
|
137
276
|
const a = cursor.node;
|
|
138
277
|
const a_pointer = array.nodes[a].?;
|
|
278
|
+
assert(cursor.relative_index <= array.count(a));
|
|
139
279
|
|
|
140
280
|
const total = array.count(a) + @intCast(u32, elements.len);
|
|
141
281
|
if (total <= node_capacity) {
|
|
@@ -164,86 +304,99 @@ pub fn SegmentedArray(
|
|
|
164
304
|
// The 5th case can be seen as a special case of the 4th.
|
|
165
305
|
//
|
|
166
306
|
// elements: [yyyyyy], relative_index: 0
|
|
167
|
-
// [xxxxx_]
|
|
168
|
-
// [
|
|
307
|
+
// [xxxxx_][______]
|
|
308
|
+
// [______][xxxxx_] // after first copy_backwards
|
|
309
|
+
// [______][xxxxx_] // skip mem.copyBackwards (a_half >= relative_index)
|
|
310
|
+
// [yyyyyy][xxxxx_] // after second copy_backwards
|
|
169
311
|
//
|
|
170
312
|
// elements: [yy], relative_index: 1
|
|
171
|
-
// [xxxxx_]
|
|
172
|
-
// [
|
|
313
|
+
// [xxxxx_][______]
|
|
314
|
+
// [x__x__][xxx___] // after first copy_backwards
|
|
315
|
+
// [x__x__][xxx___] // skip mem.copyBackwards (a_half >= relative_index)
|
|
316
|
+
// [xyyx__][xxx___] // after second copy_backwards
|
|
173
317
|
//
|
|
174
318
|
// elements: [yy], relative_index: 2
|
|
175
|
-
// [xxx_]
|
|
176
|
-
// [
|
|
319
|
+
// [xxx_][____]
|
|
320
|
+
// [xx__][_x__] // after first copy_backwards
|
|
321
|
+
// [xx__][_x__] // skip mem.copyBackwards (a_half >= relative_index)
|
|
322
|
+
// [xxy_][yx__] // after second copy_backwards
|
|
177
323
|
//
|
|
178
324
|
// elements: [yy], relative_index: 5
|
|
179
|
-
// [xxxxxx]
|
|
180
|
-
// [
|
|
325
|
+
// [xxxxxx][______]
|
|
326
|
+
// [xxxxx_][___x__] // after first copy_backwards
|
|
327
|
+
// [xxxx__][x__x__] // after mem.copyBackwards (a_half < relative_index)
|
|
328
|
+
// [xxxx__][xyyx__] // after second copy_backwards
|
|
181
329
|
//
|
|
182
330
|
// elements: [yyyyy_], relative_index: 5
|
|
183
|
-
// [xxxxx_]
|
|
184
|
-
// [xxxxx_][
|
|
185
|
-
|
|
186
|
-
|
|
331
|
+
// [xxxxx_][______]
|
|
332
|
+
// [xxxxx_][______] // after first copy_backwards
|
|
333
|
+
// [xxxxx_][______] // skip mem.copyBackwards (a_half >= relative_index)
|
|
334
|
+
// [xxxxx_][yyyyy_] // after second copy_backwards
|
|
335
|
+
|
|
336
|
+
const a_half_pointer = a_pointer[0..a_half];
|
|
337
|
+
const b_half_pointer = b_pointer[0..b_half];
|
|
338
|
+
|
|
339
|
+
// Move part of `a` forwards to make space for elements.
|
|
340
|
+
copy_backwards(
|
|
341
|
+
a_half_pointer,
|
|
342
|
+
b_half_pointer,
|
|
343
|
+
cursor.relative_index + elements.len,
|
|
344
|
+
a_pointer[cursor.relative_index..array.count(a)],
|
|
345
|
+
);
|
|
187
346
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const elements_a = elements[0 .. a_half - existing_a_head.len - existing_a_tail.len];
|
|
197
|
-
const elements_b = elements[elements_a.len..];
|
|
198
|
-
|
|
199
|
-
assert(array.count(a) == existing_a_head.len + existing_b_head.len +
|
|
200
|
-
existing_a_tail.len + existing_b_tail.len);
|
|
201
|
-
assert(elements.len == elements_a.len + elements_b.len);
|
|
202
|
-
assert(a_half == existing_a_head.len + elements_a.len + existing_a_tail.len);
|
|
203
|
-
assert(b_half == existing_b_head.len + elements_b.len + existing_b_tail.len);
|
|
204
|
-
assert(total == a_half + b_half);
|
|
205
|
-
|
|
206
|
-
if (existing_a_tail.len > 0) assert(existing_b_head.len == 0 and elements_b.len == 0);
|
|
207
|
-
if (existing_b_head.len > 0) assert(elements_a.len == 0 and existing_a_tail.len == 0);
|
|
208
|
-
if (elements_a.len > 0) assert(existing_b_head.len == 0);
|
|
209
|
-
if (elements_b.len > 0) assert(existing_a_tail.len == 0);
|
|
210
|
-
|
|
211
|
-
assert(existing_a_head.ptr == a_pointer);
|
|
212
|
-
assert(existing_a_head.ptr + existing_a_head.len == existing_a_tail.ptr);
|
|
213
|
-
assert(existing_a_head.ptr + existing_a_head.len == existing_b_head.ptr);
|
|
214
|
-
if (existing_b_head.len > 0) {
|
|
215
|
-
assert(existing_b_head.ptr + existing_b_head.len == existing_b_tail.ptr);
|
|
216
|
-
} else {
|
|
217
|
-
assert(existing_a_tail.ptr + existing_a_tail.len == existing_b_tail.ptr);
|
|
347
|
+
if (a_half < cursor.relative_index) {
|
|
348
|
+
// Move the part of `a` that is past the half-way point into `b`.
|
|
349
|
+
mem.copyBackwards(
|
|
350
|
+
T,
|
|
351
|
+
b_half_pointer,
|
|
352
|
+
a_pointer[a_half..cursor.relative_index],
|
|
353
|
+
);
|
|
218
354
|
}
|
|
219
355
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
a_pointer[existing_a_head.len + elements_a.len ..],
|
|
227
|
-
existing_a_tail,
|
|
356
|
+
// Move `elements` into `a` and/or `b`.
|
|
357
|
+
copy_backwards(
|
|
358
|
+
a_half_pointer,
|
|
359
|
+
b_half_pointer,
|
|
360
|
+
cursor.relative_index,
|
|
361
|
+
elements,
|
|
228
362
|
);
|
|
229
|
-
mem.copy(T, a_pointer[existing_a_head.len..], elements_a);
|
|
230
363
|
|
|
231
364
|
array.indexes[b] = array.indexes[a] + a_half;
|
|
232
365
|
array.increment_indexes_after(b, @intCast(u32, elements.len));
|
|
233
366
|
}
|
|
234
367
|
|
|
368
|
+
/// Behaves like mem.copyBackwards, but as if `a` and `b` were a single contiguous slice.
|
|
369
|
+
/// `target` is the destination index within the concatenation of `a` and `b`.
|
|
370
|
+
fn copy_backwards(
|
|
371
|
+
a: []T,
|
|
372
|
+
b: []T,
|
|
373
|
+
target: usize,
|
|
374
|
+
source: []const T,
|
|
375
|
+
) void {
|
|
376
|
+
assert(target + source.len <= a.len + b.len);
|
|
377
|
+
const target_a = a[@minimum(target, a.len)..@minimum(target + source.len, a.len)];
|
|
378
|
+
const target_b = b[target -| a.len..(target + source.len) -| a.len];
|
|
379
|
+
assert(target_a.len + target_b.len == source.len);
|
|
380
|
+
const source_a = source[0..target_a.len];
|
|
381
|
+
const source_b = source[target_a.len..];
|
|
382
|
+
if (target_b.ptr != source_b.ptr) {
|
|
383
|
+
mem.copyBackwards(T, target_b, source_b);
|
|
384
|
+
}
|
|
385
|
+
if (target_a.ptr != source_a.ptr) {
|
|
386
|
+
mem.copyBackwards(T, target_a, source_a);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
235
390
|
/// Insert an empty node at index `node`.
|
|
236
391
|
fn insert_empty_node_at(array: *Self, node_pool: *NodePool, node: u32) void {
|
|
237
|
-
assert(array.node_count + 1 < node_count_max);
|
|
238
|
-
|
|
239
392
|
assert(node <= array.node_count);
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
393
|
+
assert(array.node_count + 1 <= node_count_max);
|
|
394
|
+
|
|
395
|
+
mem.copyBackwards(
|
|
396
|
+
?*[node_capacity]T,
|
|
397
|
+
array.nodes[node + 1 .. array.node_count + 1],
|
|
398
|
+
array.nodes[node..array.node_count],
|
|
399
|
+
);
|
|
247
400
|
mem.copyBackwards(
|
|
248
401
|
u32,
|
|
249
402
|
array.indexes[node + 1 .. array.node_count + 2],
|
|
@@ -251,7 +404,13 @@ pub fn SegmentedArray(
|
|
|
251
404
|
);
|
|
252
405
|
|
|
253
406
|
array.node_count += 1;
|
|
254
|
-
|
|
407
|
+
const node_pointer = node_pool.acquire();
|
|
408
|
+
comptime {
|
|
409
|
+
// @ptrCast does not check that the size or alignment agree
|
|
410
|
+
assert(std.meta.alignment(@TypeOf(node_pointer)) >= @alignOf(T));
|
|
411
|
+
assert(@sizeOf(@TypeOf(node_pointer.*)) >= @sizeOf([node_capacity]T));
|
|
412
|
+
}
|
|
413
|
+
array.nodes[node] = @ptrCast(*[node_capacity]T, node_pointer);
|
|
255
414
|
assert(array.indexes[node] == array.indexes[node + 1]);
|
|
256
415
|
}
|
|
257
416
|
|
|
@@ -261,6 +420,8 @@ pub fn SegmentedArray(
|
|
|
261
420
|
absolute_index: u32,
|
|
262
421
|
remove_count: u32,
|
|
263
422
|
) void {
|
|
423
|
+
if (options.verify) array.verify();
|
|
424
|
+
|
|
264
425
|
assert(array.node_count > 0);
|
|
265
426
|
assert(remove_count > 0);
|
|
266
427
|
assert(absolute_index + remove_count <= element_count_max);
|
|
@@ -274,6 +435,8 @@ pub fn SegmentedArray(
|
|
|
274
435
|
array.remove_elements_batch(node_pool, absolute_index, batch);
|
|
275
436
|
i -= batch;
|
|
276
437
|
}
|
|
438
|
+
|
|
439
|
+
if (options.verify) array.verify();
|
|
277
440
|
}
|
|
278
441
|
|
|
279
442
|
fn remove_elements_batch(
|
|
@@ -351,7 +514,11 @@ pub fn SegmentedArray(
|
|
|
351
514
|
array.decrement_indexes_after(b, remove_count);
|
|
352
515
|
|
|
353
516
|
array.remove_empty_node_at(node_pool, b);
|
|
354
|
-
|
|
517
|
+
|
|
518
|
+
// Either:
|
|
519
|
+
// * `b` was the last node so now `a` is the last node
|
|
520
|
+
// * both `a` and `b` were at least half-full so now `a` is at least half-full
|
|
521
|
+
assert(b == array.node_count or array.count(a) >= half);
|
|
355
522
|
}
|
|
356
523
|
}
|
|
357
524
|
|
|
@@ -442,14 +609,11 @@ pub fn SegmentedArray(
|
|
|
442
609
|
@ptrCast(NodePool.Node, @alignCast(NodePool.node_alignment, array.nodes[node].?)),
|
|
443
610
|
);
|
|
444
611
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
array.nodes[node + 1 .. array.node_count],
|
|
451
|
-
);
|
|
452
|
-
}
|
|
612
|
+
mem.copy(
|
|
613
|
+
?*[node_capacity]T,
|
|
614
|
+
array.nodes[node .. array.node_count - 1],
|
|
615
|
+
array.nodes[node + 1 .. array.node_count],
|
|
616
|
+
);
|
|
453
617
|
mem.copy(
|
|
454
618
|
u32,
|
|
455
619
|
array.indexes[node..array.node_count],
|
|
@@ -535,27 +699,31 @@ pub fn SegmentedArray(
|
|
|
535
699
|
assert(absolute_index < element_count_max);
|
|
536
700
|
assert(absolute_index <= array.len());
|
|
537
701
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
}
|
|
544
|
-
assert(node < array.node_count);
|
|
545
|
-
|
|
546
|
-
const relative_index = absolute_index - array.indexes[node];
|
|
702
|
+
const result = binary_search_keys(u32, struct {
|
|
703
|
+
inline fn compare(a: u32, b: u32) math.Order {
|
|
704
|
+
return math.order(a, b);
|
|
705
|
+
}
|
|
706
|
+
}.compare, array.indexes[0..array.node_count], absolute_index, .{});
|
|
547
707
|
|
|
548
|
-
if (
|
|
549
|
-
|
|
550
|
-
|
|
708
|
+
if (result.exact) {
|
|
709
|
+
return .{
|
|
710
|
+
.node = result.index,
|
|
711
|
+
.relative_index = 0,
|
|
712
|
+
};
|
|
551
713
|
} else {
|
|
552
|
-
|
|
714
|
+
const node = result.index - 1;
|
|
715
|
+
const relative_index = absolute_index - array.indexes[node];
|
|
716
|
+
if (node == array.node_count - 1) {
|
|
717
|
+
// Insertion may target the index one past the end of the array.
|
|
718
|
+
assert(relative_index <= array.count(node));
|
|
719
|
+
} else {
|
|
720
|
+
assert(relative_index < array.count(node));
|
|
721
|
+
}
|
|
722
|
+
return .{
|
|
723
|
+
.node = node,
|
|
724
|
+
.relative_index = relative_index,
|
|
725
|
+
};
|
|
553
726
|
}
|
|
554
|
-
|
|
555
|
-
return .{
|
|
556
|
-
.node = node,
|
|
557
|
-
.relative_index = relative_index,
|
|
558
|
-
};
|
|
559
727
|
}
|
|
560
728
|
|
|
561
729
|
pub const Iterator = struct {
|
|
@@ -607,19 +775,15 @@ pub fn SegmentedArray(
|
|
|
607
775
|
}
|
|
608
776
|
};
|
|
609
777
|
|
|
610
|
-
pub fn
|
|
778
|
+
pub fn iterator_from_cursor(
|
|
611
779
|
array: *const Self,
|
|
612
|
-
///
|
|
613
|
-
|
|
614
|
-
/// The start node allows us to skip over nodes as an optimization.
|
|
615
|
-
/// If ascending start from the first element of the start node and ascend.
|
|
616
|
-
/// If descending start from the last element of the start node and descend.
|
|
617
|
-
start_node: u32,
|
|
780
|
+
/// First element of iteration.
|
|
781
|
+
cursor: Cursor,
|
|
618
782
|
direction: Direction,
|
|
619
783
|
) Iterator {
|
|
620
784
|
if (array.node_count == 0) {
|
|
621
|
-
assert(
|
|
622
|
-
assert(
|
|
785
|
+
assert(cursor.node == 0);
|
|
786
|
+
assert(cursor.relative_index == 0);
|
|
623
787
|
|
|
624
788
|
return Iterator{
|
|
625
789
|
.array = array,
|
|
@@ -627,58 +791,111 @@ pub fn SegmentedArray(
|
|
|
627
791
|
.cursor = .{ .node = 0, .relative_index = 0 },
|
|
628
792
|
.done = true,
|
|
629
793
|
};
|
|
794
|
+
} else {
|
|
795
|
+
assert(cursor.node < array.node_count);
|
|
796
|
+
assert(cursor.relative_index < array.count(cursor.node));
|
|
797
|
+
|
|
798
|
+
return .{
|
|
799
|
+
.array = array,
|
|
800
|
+
.direction = direction,
|
|
801
|
+
.cursor = cursor,
|
|
802
|
+
};
|
|
630
803
|
}
|
|
804
|
+
}
|
|
631
805
|
|
|
632
|
-
|
|
806
|
+
pub fn iterator_from_index(
|
|
807
|
+
array: *const Self,
|
|
808
|
+
/// First element of iteration.
|
|
809
|
+
absolute_index: u32,
|
|
810
|
+
direction: Direction,
|
|
811
|
+
) Iterator {
|
|
633
812
|
assert(absolute_index < element_count_max);
|
|
634
|
-
assert(absolute_index < array.indexes[array.node_count]);
|
|
635
813
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
assert(absolute_index >= array.indexes[start_node]);
|
|
814
|
+
if (array.node_count == 0) {
|
|
815
|
+
assert(absolute_index == 0);
|
|
639
816
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
{
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
817
|
+
return Iterator{
|
|
818
|
+
.array = array,
|
|
819
|
+
.direction = direction,
|
|
820
|
+
.cursor = .{ .node = 0, .relative_index = 0 },
|
|
821
|
+
.done = true,
|
|
822
|
+
};
|
|
823
|
+
} else {
|
|
824
|
+
assert(absolute_index < array.len());
|
|
647
825
|
|
|
648
|
-
|
|
649
|
-
|
|
826
|
+
return Iterator{
|
|
827
|
+
.array = array,
|
|
828
|
+
.direction = direction,
|
|
829
|
+
.cursor = array.cursor_for_absolute_index(absolute_index),
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
}
|
|
650
833
|
|
|
834
|
+
pub usingnamespace if (Key) |K| struct {
|
|
835
|
+
/// Returns a cursor to the index of the key either exactly equal to the target key or,
|
|
836
|
+
/// if there is no exact match, the next greatest key.
|
|
837
|
+
pub fn search(
|
|
838
|
+
array: *const Self,
|
|
839
|
+
key: K,
|
|
840
|
+
) Cursor {
|
|
841
|
+
if (array.node_count == 0) {
|
|
651
842
|
return .{
|
|
652
|
-
.
|
|
653
|
-
.
|
|
654
|
-
.cursor = .{
|
|
655
|
-
.node = node,
|
|
656
|
-
.relative_index = relative_index,
|
|
657
|
-
},
|
|
843
|
+
.node = 0,
|
|
844
|
+
.relative_index = 0,
|
|
658
845
|
};
|
|
659
|
-
}
|
|
660
|
-
.descending => {
|
|
661
|
-
assert(absolute_index < array.indexes[start_node + 1]);
|
|
846
|
+
}
|
|
662
847
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
848
|
+
var offset: usize = 0;
|
|
849
|
+
var length: usize = array.node_count;
|
|
850
|
+
while (length > 1) {
|
|
851
|
+
const half = length / 2;
|
|
852
|
+
const mid = offset + half;
|
|
667
853
|
|
|
668
|
-
const
|
|
669
|
-
|
|
854
|
+
const node = &array.nodes[mid].?[0];
|
|
855
|
+
// This trick seems to be what's needed to get llvm to emit branchless code for this,
|
|
856
|
+
// a ternary-style if expression was generated as a jump here for whatever reason.
|
|
857
|
+
const next_offsets = [_]usize{ offset, mid };
|
|
858
|
+
offset = next_offsets[@boolToInt(compare_keys(key_from_value(node), key) == .lt)];
|
|
670
859
|
|
|
860
|
+
length -= half;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Unlike a normal binary search, don't increment the offset when "key" is higher
|
|
864
|
+
// than the element — "round down" to the previous node.
|
|
865
|
+
// This guarantees that the node result is never "== node_count".
|
|
866
|
+
//
|
|
867
|
+
// (If there are two adjacent nodes starting with keys A and C, and we search B,
|
|
868
|
+
// we want to pick the A node.)
|
|
869
|
+
const node = @intCast(u32, offset);
|
|
870
|
+
assert(node < array.node_count);
|
|
871
|
+
|
|
872
|
+
const relative_index = binary_search_values_raw(
|
|
873
|
+
K,
|
|
874
|
+
T,
|
|
875
|
+
key_from_value,
|
|
876
|
+
compare_keys,
|
|
877
|
+
array.node_elements(node),
|
|
878
|
+
key,
|
|
879
|
+
.{},
|
|
880
|
+
);
|
|
881
|
+
|
|
882
|
+
// Follow the same rule as absolute_index_for_cursor:
|
|
883
|
+
// only return relative_index==array.count() at the last node.
|
|
884
|
+
if (node + 1 < array.node_count and
|
|
885
|
+
relative_index == array.count(node))
|
|
886
|
+
{
|
|
671
887
|
return .{
|
|
672
|
-
.
|
|
673
|
-
.
|
|
674
|
-
.cursor = .{
|
|
675
|
-
.node = node,
|
|
676
|
-
.relative_index = relative_index,
|
|
677
|
-
},
|
|
888
|
+
.node = node + 1,
|
|
889
|
+
.relative_index = 0,
|
|
678
890
|
};
|
|
679
|
-
}
|
|
891
|
+
} else {
|
|
892
|
+
return .{
|
|
893
|
+
.node = node,
|
|
894
|
+
.relative_index = relative_index,
|
|
895
|
+
};
|
|
896
|
+
}
|
|
680
897
|
}
|
|
681
|
-
}
|
|
898
|
+
} else struct {};
|
|
682
899
|
};
|
|
683
900
|
}
|
|
684
901
|
|
|
@@ -686,19 +903,26 @@ fn TestContext(
|
|
|
686
903
|
comptime T: type,
|
|
687
904
|
comptime node_size: u32,
|
|
688
905
|
comptime element_count_max: u32,
|
|
906
|
+
comptime Key: type,
|
|
907
|
+
comptime key_from_value: fn (*const T) callconv(.Inline) Key,
|
|
908
|
+
comptime compare_keys: fn (Key, Key) callconv(.Inline) math.Order,
|
|
909
|
+
comptime element_order: enum { sorted, unsorted },
|
|
910
|
+
comptime options: Options,
|
|
689
911
|
) type {
|
|
690
912
|
return struct {
|
|
691
913
|
const Self = @This();
|
|
692
914
|
|
|
693
915
|
const testing = std.testing;
|
|
694
|
-
|
|
695
916
|
const log = false;
|
|
696
917
|
|
|
697
918
|
const NodePool = @import("node_pool.zig").NodePool;
|
|
698
919
|
|
|
699
920
|
// Test overaligned nodes to catch compile errors for missing @alignCast()
|
|
700
921
|
const TestPool = NodePool(node_size, 2 * @alignOf(T));
|
|
701
|
-
const TestArray =
|
|
922
|
+
const TestArray = switch (element_order) {
|
|
923
|
+
.sorted => SortedSegmentedArray(T, TestPool, element_count_max, Key, key_from_value, compare_keys, options),
|
|
924
|
+
.unsorted => SegmentedArray(T, TestPool, element_count_max, options),
|
|
925
|
+
};
|
|
702
926
|
|
|
703
927
|
random: std.rand.Random,
|
|
704
928
|
|
|
@@ -761,6 +985,27 @@ fn TestContext(
|
|
|
761
985
|
}
|
|
762
986
|
|
|
763
987
|
try context.remove_all();
|
|
988
|
+
|
|
989
|
+
if (element_order == .unsorted) {
|
|
990
|
+
// Insert at the beginning of the array until the array is full.
|
|
991
|
+
while (context.array.len() < element_count_max) {
|
|
992
|
+
try context.insert_before_first();
|
|
993
|
+
}
|
|
994
|
+
assert(context.array.node_count >= TestArray.node_count_max - 1);
|
|
995
|
+
|
|
996
|
+
// Remove all-but-one elements from the last node and insert them into the first node.
|
|
997
|
+
const element_count_last = context.array.count(context.array.node_count - 1);
|
|
998
|
+
var element_index: usize = 0;
|
|
999
|
+
while (element_index < element_count_last - 1) : (element_index += 1) {
|
|
1000
|
+
try context.remove_last();
|
|
1001
|
+
try context.insert_before_first();
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// We should now have maxed out our node count.
|
|
1005
|
+
assert(context.array.node_count == TestArray.node_count_max);
|
|
1006
|
+
|
|
1007
|
+
try context.remove_all();
|
|
1008
|
+
}
|
|
764
1009
|
}
|
|
765
1010
|
|
|
766
1011
|
fn insert(context: *Self) !void {
|
|
@@ -770,19 +1015,29 @@ fn TestContext(
|
|
|
770
1015
|
if (count_free == 0) return;
|
|
771
1016
|
|
|
772
1017
|
var buffer: [TestArray.node_capacity * 3]T = undefined;
|
|
773
|
-
|
|
774
1018
|
const count_max = @minimum(count_free, TestArray.node_capacity * 3);
|
|
775
1019
|
const count = context.random.uintAtMostBiased(u32, count_max - 1) + 1;
|
|
776
1020
|
context.random.bytes(mem.sliceAsBytes(buffer[0..count]));
|
|
777
1021
|
|
|
778
1022
|
assert(context.reference.items.len <= element_count_max);
|
|
779
|
-
const index = context.random.uintAtMostBiased(u32, reference_len);
|
|
780
|
-
|
|
781
|
-
context.array.insert_elements(&context.pool, index, buffer[0..count]);
|
|
782
1023
|
|
|
783
|
-
|
|
784
|
-
|
|
1024
|
+
switch (element_order) {
|
|
1025
|
+
.unsorted => {
|
|
1026
|
+
const index = context.random.uintAtMostBiased(u32, reference_len);
|
|
785
1027
|
|
|
1028
|
+
context.array.insert_elements(&context.pool, index, buffer[0..count]);
|
|
1029
|
+
// TODO the standard library could use an AssumeCapacity variant of this.
|
|
1030
|
+
context.reference.insertSlice(index, buffer[0..count]) catch unreachable;
|
|
1031
|
+
},
|
|
1032
|
+
.sorted => {
|
|
1033
|
+
for (buffer[0..count]) |value| {
|
|
1034
|
+
const index_actual = context.array.insert_element(&context.pool, value);
|
|
1035
|
+
const index_expect = context.reference_index(key_from_value(&value));
|
|
1036
|
+
context.reference.insert(index_expect, value) catch unreachable;
|
|
1037
|
+
try std.testing.expectEqual(index_expect, index_actual);
|
|
1038
|
+
}
|
|
1039
|
+
},
|
|
1040
|
+
}
|
|
786
1041
|
context.inserts += count;
|
|
787
1042
|
|
|
788
1043
|
try context.verify();
|
|
@@ -807,6 +1062,35 @@ fn TestContext(
|
|
|
807
1062
|
try context.verify();
|
|
808
1063
|
}
|
|
809
1064
|
|
|
1065
|
+
fn insert_before_first(context: *Self) !void {
|
|
1066
|
+
assert(element_order == .unsorted);
|
|
1067
|
+
|
|
1068
|
+
const insert_index = context.array.absolute_index_for_cursor(context.array.first());
|
|
1069
|
+
|
|
1070
|
+
var element: T = undefined;
|
|
1071
|
+
context.random.bytes(mem.asBytes(&element));
|
|
1072
|
+
|
|
1073
|
+
context.array.insert_elements(&context.pool, insert_index, &.{element});
|
|
1074
|
+
context.reference.insert(insert_index, element) catch unreachable;
|
|
1075
|
+
|
|
1076
|
+
context.inserts += 1;
|
|
1077
|
+
|
|
1078
|
+
try context.verify();
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
fn remove_last(context: *Self) !void {
|
|
1082
|
+
assert(element_order == .unsorted);
|
|
1083
|
+
|
|
1084
|
+
const remove_index = context.array.absolute_index_for_cursor(context.array.last());
|
|
1085
|
+
|
|
1086
|
+
context.array.remove_elements(&context.pool, remove_index, 1);
|
|
1087
|
+
context.reference.replaceRange(remove_index, 1, &[0]T{}) catch unreachable;
|
|
1088
|
+
|
|
1089
|
+
context.removes += 1;
|
|
1090
|
+
|
|
1091
|
+
try context.verify();
|
|
1092
|
+
}
|
|
1093
|
+
|
|
810
1094
|
fn remove_all(context: *Self) !void {
|
|
811
1095
|
while (context.reference.items.len > 0) try context.remove();
|
|
812
1096
|
|
|
@@ -830,7 +1114,7 @@ fn TestContext(
|
|
|
830
1114
|
for (context.reference.items) |i| std.debug.print("{}, ", .{i});
|
|
831
1115
|
|
|
832
1116
|
std.debug.print("\nactual: ", .{});
|
|
833
|
-
var it = context.array.
|
|
1117
|
+
var it = context.array.iterator_from_index(0, .ascending);
|
|
834
1118
|
while (it.next()) |i| std.debug.print("{}, ", .{i.*});
|
|
835
1119
|
std.debug.print("\n", .{});
|
|
836
1120
|
}
|
|
@@ -838,7 +1122,7 @@ fn TestContext(
|
|
|
838
1122
|
try testing.expectEqual(context.reference.items.len, context.array.len());
|
|
839
1123
|
|
|
840
1124
|
{
|
|
841
|
-
var it = context.array.
|
|
1125
|
+
var it = context.array.iterator_from_index(0, .ascending);
|
|
842
1126
|
|
|
843
1127
|
for (context.reference.items) |expect| {
|
|
844
1128
|
const actual = it.next() orelse return error.TestUnexpectedResult;
|
|
@@ -848,9 +1132,8 @@ fn TestContext(
|
|
|
848
1132
|
}
|
|
849
1133
|
|
|
850
1134
|
{
|
|
851
|
-
var it = context.array.
|
|
1135
|
+
var it = context.array.iterator_from_index(
|
|
852
1136
|
@intCast(u32, context.reference.items.len) -| 1,
|
|
853
|
-
context.array.last().node,
|
|
854
1137
|
.descending,
|
|
855
1138
|
);
|
|
856
1139
|
|
|
@@ -865,7 +1148,26 @@ fn TestContext(
|
|
|
865
1148
|
try testing.expectEqual(@as(?*const T, null), it.next());
|
|
866
1149
|
}
|
|
867
1150
|
|
|
868
|
-
|
|
1151
|
+
{
|
|
1152
|
+
for (context.reference.items) |_, i| {
|
|
1153
|
+
try testing.expect(std.meta.eql(
|
|
1154
|
+
i,
|
|
1155
|
+
context.array.absolute_index_for_cursor(
|
|
1156
|
+
context.array.cursor_for_absolute_index(@intCast(u32, i)),
|
|
1157
|
+
),
|
|
1158
|
+
));
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
if (element_order == .sorted) {
|
|
1163
|
+
for (context.reference.items) |*expect, i| {
|
|
1164
|
+
if (i == 0) continue;
|
|
1165
|
+
try testing.expect(compare_keys(
|
|
1166
|
+
key_from_value(&context.reference.items[i - 1]),
|
|
1167
|
+
key_from_value(expect),
|
|
1168
|
+
) != .gt);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
869
1171
|
|
|
870
1172
|
if (context.array.len() == 0) {
|
|
871
1173
|
try testing.expectEqual(@as(u32, 0), context.array.node_count);
|
|
@@ -875,45 +1177,6 @@ fn TestContext(
|
|
|
875
1177
|
try testing.expectEqual(@as(?*[TestArray.node_capacity]T, null), node);
|
|
876
1178
|
}
|
|
877
1179
|
|
|
878
|
-
if (context.reference.items.len > 0) {
|
|
879
|
-
const reference_len = @intCast(u32, context.reference.items.len);
|
|
880
|
-
const index = context.random.uintLessThanBiased(u32, reference_len);
|
|
881
|
-
const cursor = context.array.cursor_for_absolute_index(index);
|
|
882
|
-
|
|
883
|
-
{
|
|
884
|
-
const start_node = context.random.uintAtMostBiased(u32, cursor.node);
|
|
885
|
-
|
|
886
|
-
var it = context.array.iterator(index, start_node, .ascending);
|
|
887
|
-
|
|
888
|
-
for (context.reference.items[index..]) |expect| {
|
|
889
|
-
const actual = it.next() orelse return error.TestUnexpectedResult;
|
|
890
|
-
try testing.expectEqual(expect, actual.*);
|
|
891
|
-
}
|
|
892
|
-
try testing.expectEqual(@as(?*const T, null), it.next());
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
{
|
|
896
|
-
const start_node = cursor.node + context.random.uintAtMostBiased(
|
|
897
|
-
u32,
|
|
898
|
-
context.array.node_count - 1 - cursor.node,
|
|
899
|
-
);
|
|
900
|
-
assert(start_node >= cursor.node);
|
|
901
|
-
assert(start_node < context.array.node_count);
|
|
902
|
-
|
|
903
|
-
var it = context.array.iterator(index, start_node, .descending);
|
|
904
|
-
|
|
905
|
-
var i = index + 1;
|
|
906
|
-
while (i > 0) {
|
|
907
|
-
i -= 1;
|
|
908
|
-
|
|
909
|
-
const expect = context.reference.items[i];
|
|
910
|
-
const actual = it.next() orelse return error.TestUnexpectedResult;
|
|
911
|
-
try testing.expectEqual(expect, actual.*);
|
|
912
|
-
}
|
|
913
|
-
try testing.expectEqual(@as(?*const T, null), it.next());
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
|
|
917
1180
|
{
|
|
918
1181
|
var i: u32 = 0;
|
|
919
1182
|
while (i < context.array.node_count -| 1) : (i += 1) {
|
|
@@ -921,38 +1184,83 @@ fn TestContext(
|
|
|
921
1184
|
@divExact(TestArray.node_capacity, 2));
|
|
922
1185
|
}
|
|
923
1186
|
}
|
|
1187
|
+
if (element_order == .sorted) try context.verify_search();
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
fn verify_search(context: *Self) !void {
|
|
1191
|
+
var queries: [20]Key = undefined;
|
|
1192
|
+
context.random.bytes(mem.sliceAsBytes(&queries));
|
|
1193
|
+
|
|
1194
|
+
// Test min/max exceptional values on different SegmentedArray shapes.
|
|
1195
|
+
queries[0] = 0;
|
|
1196
|
+
queries[1] = math.maxInt(Key);
|
|
1197
|
+
|
|
1198
|
+
for (queries) |query| {
|
|
1199
|
+
try testing.expectEqual(
|
|
1200
|
+
context.reference_index(query),
|
|
1201
|
+
context.array.absolute_index_for_cursor(context.array.search(query)),
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
fn reference_index(context: *const Self, key: Key) u32 {
|
|
1207
|
+
return binary_search_values_raw(
|
|
1208
|
+
Key,
|
|
1209
|
+
T,
|
|
1210
|
+
key_from_value,
|
|
1211
|
+
compare_keys,
|
|
1212
|
+
context.reference.items,
|
|
1213
|
+
key,
|
|
1214
|
+
.{},
|
|
1215
|
+
);
|
|
924
1216
|
}
|
|
925
1217
|
};
|
|
926
1218
|
}
|
|
927
1219
|
|
|
928
|
-
|
|
929
|
-
const seed = 42;
|
|
930
|
-
|
|
1220
|
+
pub fn run_tests(seed: u64, comptime options: Options) !void {
|
|
931
1221
|
var prng = std.rand.DefaultPrng.init(seed);
|
|
932
1222
|
const random = prng.random();
|
|
933
1223
|
|
|
934
|
-
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1224
|
+
const Key = @import("composite_key.zig").CompositeKey(u64);
|
|
1225
|
+
const TableType = @import("table.zig").TableType;
|
|
1226
|
+
const TableInfoType = @import("manifest.zig").TableInfoType;
|
|
1227
|
+
const TableInfo = TableInfoType(TableType(
|
|
1228
|
+
Key,
|
|
1229
|
+
Key.Value,
|
|
1230
|
+
Key.compare_keys,
|
|
1231
|
+
Key.key_from_value,
|
|
1232
|
+
Key.sentinel_key,
|
|
1233
|
+
Key.tombstone,
|
|
1234
|
+
Key.tombstone_from_key,
|
|
1235
|
+
));
|
|
1236
|
+
|
|
1237
|
+
const CompareInt = struct {
|
|
1238
|
+
inline fn compare_keys(a: u32, b: u32) std.math.Order {
|
|
1239
|
+
return std.math.order(a, b);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
inline fn key_from_value(value: *const u32) u32 {
|
|
1243
|
+
return value.*;
|
|
1244
|
+
}
|
|
1245
|
+
};
|
|
939
1246
|
|
|
940
|
-
|
|
1247
|
+
const CompareTable = struct {
|
|
1248
|
+
inline fn compare_keys(a: u64, b: u64) std.math.Order {
|
|
1249
|
+
return std.math.order(a, b);
|
|
1250
|
+
}
|
|
941
1251
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
/// snapshot tick on deletion.
|
|
946
|
-
snapshot_max: u64,
|
|
947
|
-
flags: u64 = 0,
|
|
1252
|
+
inline fn key_from_value(value: *const TableInfo) u64 {
|
|
1253
|
+
return value.address;
|
|
1254
|
+
}
|
|
948
1255
|
};
|
|
949
1256
|
|
|
950
1257
|
comptime {
|
|
951
1258
|
assert(@sizeOf(TableInfo) == 48 + @sizeOf(u128) * 2);
|
|
952
1259
|
assert(@alignOf(TableInfo) == 16);
|
|
1260
|
+
assert(@bitSizeOf(TableInfo) == @sizeOf(TableInfo) * 8);
|
|
953
1261
|
}
|
|
954
1262
|
|
|
955
|
-
const
|
|
1263
|
+
const TestOptions = struct {
|
|
956
1264
|
element_type: type,
|
|
957
1265
|
node_size: u32,
|
|
958
1266
|
element_count_max: u32,
|
|
@@ -964,35 +1272,47 @@ test "SegmentedArray" {
|
|
|
964
1272
|
// We want to explore not just the bottom boundary but also the surrounding area
|
|
965
1273
|
// as it may also have interesting edge cases.
|
|
966
1274
|
inline for (.{
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
}) |
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1275
|
+
TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 3 },
|
|
1276
|
+
TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 4 },
|
|
1277
|
+
TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 5 },
|
|
1278
|
+
TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 6 },
|
|
1279
|
+
TestOptions{ .element_type = u32, .node_size = 8, .element_count_max = 1024 },
|
|
1280
|
+
TestOptions{ .element_type = u32, .node_size = 16, .element_count_max = 1024 },
|
|
1281
|
+
TestOptions{ .element_type = u32, .node_size = 32, .element_count_max = 1024 },
|
|
1282
|
+
TestOptions{ .element_type = u32, .node_size = 64, .element_count_max = 1024 },
|
|
1283
|
+
TestOptions{ .element_type = TableInfo, .node_size = 256, .element_count_max = 3 },
|
|
1284
|
+
TestOptions{ .element_type = TableInfo, .node_size = 256, .element_count_max = 4 },
|
|
1285
|
+
TestOptions{ .element_type = TableInfo, .node_size = 256, .element_count_max = 1024 },
|
|
1286
|
+
TestOptions{ .element_type = TableInfo, .node_size = 512, .element_count_max = 1024 },
|
|
1287
|
+
TestOptions{ .element_type = TableInfo, .node_size = 1024, .element_count_max = 1024 },
|
|
1288
|
+
}) |test_options| {
|
|
1289
|
+
inline for (.{ .sorted, .unsorted }) |order| {
|
|
1290
|
+
const Context = TestContext(
|
|
1291
|
+
test_options.element_type,
|
|
1292
|
+
test_options.node_size,
|
|
1293
|
+
test_options.element_count_max,
|
|
1294
|
+
if (test_options.element_type == u32) u32 else u64,
|
|
1295
|
+
if (test_options.element_type == u32) CompareInt.key_from_value else CompareTable.key_from_value,
|
|
1296
|
+
if (test_options.element_type == u32) CompareInt.compare_keys else CompareTable.compare_keys,
|
|
1297
|
+
order,
|
|
1298
|
+
options,
|
|
1299
|
+
);
|
|
1300
|
+
|
|
1301
|
+
var context = try Context.init(random);
|
|
1302
|
+
defer context.deinit();
|
|
1303
|
+
|
|
1304
|
+
try context.run();
|
|
1305
|
+
|
|
1306
|
+
if (test_options.node_size % @sizeOf(test_options.element_type) != 0) tested_padding = true;
|
|
1307
|
+
if (Context.TestArray.node_capacity == 2) tested_node_capacity_min = true;
|
|
1308
|
+
}
|
|
994
1309
|
}
|
|
995
1310
|
|
|
996
1311
|
assert(tested_padding);
|
|
997
1312
|
assert(tested_node_capacity_min);
|
|
998
1313
|
}
|
|
1314
|
+
|
|
1315
|
+
test "SegmentedArray" {
|
|
1316
|
+
const seed = 42;
|
|
1317
|
+
try run_tests(seed, .{ .verify = true });
|
|
1318
|
+
}
|