authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-04-18 07:49:23-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-04-20 05:31:24-07:00
log711bf55eaa643c3d05640bebbf3e4315477b8ed8
tree15c5417840b6326cdf1bfb66ccc42975abcc8edb
parent1a1b5ee264d8b2219c34d53cc9602692e6d2ba24

std: bring back SegmentedList

I want to use it for the self-hosted compiler.

2 files changed, 465 insertions(+), 0 deletions(-)

lib/std/segmented_list.zig created+464
...@@ -0,0 +1,464 @@
1const std = @import("std.zig");
2const assert = std.debug.assert;
3const testing = std.testing;
4const Allocator = std.mem.Allocator;
5
6// Imagine that `fn at(self: *Self, index: usize) &T` is a customer asking for a box
7// from a warehouse, based on a flat array, boxes ordered from 0 to N - 1.
8// But the warehouse actually stores boxes in shelves of increasing powers of 2 sizes.
9// So when the customer requests a box index, we have to translate it to shelf index
10// and box index within that shelf. Illustration:
11//
12// customer indexes:
13// shelf 0: 0
14// shelf 1: 1 2
15// shelf 2: 3 4 5 6
16// shelf 3: 7 8 9 10 11 12 13 14
17// shelf 4: 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
18// shelf 5: 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
19// ...
20//
21// warehouse indexes:
22// shelf 0: 0
23// shelf 1: 0 1
24// shelf 2: 0 1 2 3
25// shelf 3: 0 1 2 3 4 5 6 7
26// shelf 4: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
27// shelf 5: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
28// ...
29//
30// With this arrangement, here are the equations to get the shelf index and
31// box index based on customer box index:
32//
33// shelf_index = floor(log2(customer_index + 1))
34// shelf_count = ceil(log2(box_count + 1))
35// box_index = customer_index + 1 - 2 ** shelf
36// shelf_size = 2 ** shelf_index
37//
38// Now we complicate it a little bit further by adding a preallocated shelf, which must be
39// a power of 2:
40// prealloc=4
41//
42// customer indexes:
43// prealloc: 0 1 2 3
44// shelf 0: 4 5 6 7 8 9 10 11
45// shelf 1: 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
46// shelf 2: 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
47// ...
48//
49// warehouse indexes:
50// prealloc: 0 1 2 3
51// shelf 0: 0 1 2 3 4 5 6 7
52// shelf 1: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
53// shelf 2: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
54// ...
55//
56// Now the equations are:
57//
58// shelf_index = floor(log2(customer_index + prealloc)) - log2(prealloc) - 1
59// shelf_count = ceil(log2(box_count + prealloc)) - log2(prealloc) - 1
60// box_index = customer_index + prealloc - 2 ** (log2(prealloc) + 1 + shelf)
61// shelf_size = prealloc * 2 ** (shelf_index + 1)
62
63/// This is a stack data structure where pointers to indexes have the same lifetime as the data structure
64/// itself, unlike ArrayList where push() invalidates all existing element pointers.
65/// The tradeoff is that elements are not guaranteed to be contiguous. For that, use ArrayList.
66/// Note however that most elements are contiguous, making this data structure cache-friendly.
67///
68/// Because it never has to copy elements from an old location to a new location, it does not require
69/// its elements to be copyable, and it avoids wasting memory when backed by an ArenaAllocator.
70/// Note that the push() and pop() convenience methods perform a copy, but you can instead use
71/// addOne(), at(), setCapacity(), and shrinkCapacity() to avoid copying items.
72///
73/// This data structure has O(1) push and O(1) pop.
74///
75/// It supports preallocated elements, making it especially well suited when the expected maximum
76/// size is small. `prealloc_item_count` must be 0, or a power of 2.
77pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type {
78 return struct {
79 const Self = @This();
80 const ShelfIndex = std.math.Log2Int(usize);
81
82 const prealloc_exp: ShelfIndex = blk: {
83 // we don't use the prealloc_exp constant when prealloc_item_count is 0
84 // but lazy-init may still be triggered by other code so supply a value
85 if (prealloc_item_count == 0) {
86 break :blk 0;
87 } else {
88 assert(std.math.isPowerOfTwo(prealloc_item_count));
89 const value = std.math.log2_int(usize, prealloc_item_count);
90 break :blk value;
91 }
92 };
93
94 prealloc_segment: [prealloc_item_count]T,
95 dynamic_segments: [][*]T,
96 allocator: Allocator,
97 len: usize,
98
99 pub const prealloc_count = prealloc_item_count;
100
101 fn AtType(comptime SelfType: type) type {
102 if (@typeInfo(SelfType).Pointer.is_const) {
103 return *const T;
104 } else {
105 return *T;
106 }
107 }
108
109 /// Deinitialize with `deinit`
110 pub fn init(allocator: Allocator) Self {
111 return Self{
112 .allocator = allocator,
113 .len = 0,
114 .prealloc_segment = undefined,
115 .dynamic_segments = &[_][*]T{},
116 };
117 }
118
119 pub fn deinit(self: *Self) void {
120 self.freeShelves(@intCast(ShelfIndex, self.dynamic_segments.len), 0);
121 self.allocator.free(self.dynamic_segments);
122 self.* = undefined;
123 }
124
125 pub fn at(self: anytype, i: usize) AtType(@TypeOf(self)) {
126 assert(i < self.len);
127 return self.uncheckedAt(i);
128 }
129
130 pub fn count(self: Self) usize {
131 return self.len;
132 }
133
134 pub fn push(self: *Self, item: T) !void {
135 const new_item_ptr = try self.addOne();
136 new_item_ptr.* = item;
137 }
138
139 pub fn pushMany(self: *Self, items: []const T) !void {
140 for (items) |item| {
141 try self.push(item);
142 }
143 }
144
145 pub fn pop(self: *Self) ?T {
146 if (self.len == 0) return null;
147
148 const index = self.len - 1;
149 const result = uncheckedAt(self, index).*;
150 self.len = index;
151 return result;
152 }
153
154 pub fn addOne(self: *Self) !*T {
155 const new_length = self.len + 1;
156 try self.growCapacity(new_length);
157 const result = uncheckedAt(self, self.len);
158 self.len = new_length;
159 return result;
160 }
161
162 /// Grows or shrinks capacity to match usage.
163 pub fn setCapacity(self: *Self, new_capacity: usize) !void {
164 if (prealloc_item_count != 0) {
165 if (new_capacity <= @as(usize, 1) << (prealloc_exp + @intCast(ShelfIndex, self.dynamic_segments.len))) {
166 return self.shrinkCapacity(new_capacity);
167 }
168 }
169 return self.growCapacity(new_capacity);
170 }
171
172 /// Only grows capacity, or retains current capacity
173 pub fn growCapacity(self: *Self, new_capacity: usize) !void {
174 const new_cap_shelf_count = shelfCount(new_capacity);
175 const old_shelf_count = @intCast(ShelfIndex, self.dynamic_segments.len);
176 if (new_cap_shelf_count > old_shelf_count) {
177 self.dynamic_segments = try self.allocator.realloc(self.dynamic_segments, new_cap_shelf_count);
178 var i = old_shelf_count;
179 errdefer {
180 self.freeShelves(i, old_shelf_count);
181 self.dynamic_segments = self.allocator.shrink(self.dynamic_segments, old_shelf_count);
182 }
183 while (i < new_cap_shelf_count) : (i += 1) {
184 self.dynamic_segments[i] = (try self.allocator.alloc(T, shelfSize(i))).ptr;
185 }
186 }
187 }
188
189 /// Only shrinks capacity or retains current capacity
190 pub fn shrinkCapacity(self: *Self, new_capacity: usize) void {
191 if (new_capacity <= prealloc_item_count) {
192 const len = @intCast(ShelfIndex, self.dynamic_segments.len);
193 self.freeShelves(len, 0);
194 self.allocator.free(self.dynamic_segments);
195 self.dynamic_segments = &[_][*]T{};
196 return;
197 }
198
199 const new_cap_shelf_count = shelfCount(new_capacity);
200 const old_shelf_count = @intCast(ShelfIndex, self.dynamic_segments.len);
201 assert(new_cap_shelf_count <= old_shelf_count);
202 if (new_cap_shelf_count == old_shelf_count) {
203 return;
204 }
205
206 self.freeShelves(old_shelf_count, new_cap_shelf_count);
207 self.dynamic_segments = self.allocator.shrink(self.dynamic_segments, new_cap_shelf_count);
208 }
209
210 pub fn shrink(self: *Self, new_len: usize) void {
211 assert(new_len <= self.len);
212 // TODO take advantage of the new realloc semantics
213 self.len = new_len;
214 }
215
216 pub fn writeToSlice(self: *Self, dest: []T, start: usize) void {
217 const end = start + dest.len;
218 assert(end <= self.len);
219
220 var i = start;
221 if (end <= prealloc_item_count) {
222 std.mem.copy(T, dest[i - start ..], self.prealloc_segment[i..end]);
223 return;
224 } else if (i < prealloc_item_count) {
225 std.mem.copy(T, dest[i - start ..], self.prealloc_segment[i..]);
226 i = prealloc_item_count;
227 }
228
229 while (i < end) {
230 const shelf_index = shelfIndex(i);
231 const copy_start = boxIndex(i, shelf_index);
232 const copy_end = std.math.min(shelfSize(shelf_index), copy_start + end - i);
233
234 std.mem.copy(
235 T,
236 dest[i - start ..],
237 self.dynamic_segments[shelf_index][copy_start..copy_end],
238 );
239
240 i += (copy_end - copy_start);
241 }
242 }
243
244 pub fn uncheckedAt(self: anytype, index: usize) AtType(@TypeOf(self)) {
245 if (index < prealloc_item_count) {
246 return &self.prealloc_segment[index];
247 }
248 const shelf_index = shelfIndex(index);
249 const box_index = boxIndex(index, shelf_index);
250 return &self.dynamic_segments[shelf_index][box_index];
251 }
252
253 fn shelfCount(box_count: usize) ShelfIndex {
254 if (prealloc_item_count == 0) {
255 return log2_int_ceil(usize, box_count + 1);
256 }
257 return log2_int_ceil(usize, box_count + prealloc_item_count) - prealloc_exp - 1;
258 }
259
260 fn shelfSize(shelf_index: ShelfIndex) usize {
261 if (prealloc_item_count == 0) {
262 return @as(usize, 1) << shelf_index;
263 }
264 return @as(usize, 1) << (shelf_index + (prealloc_exp + 1));
265 }
266
267 fn shelfIndex(list_index: usize) ShelfIndex {
268 if (prealloc_item_count == 0) {
269 return std.math.log2_int(usize, list_index + 1);
270 }
271 return std.math.log2_int(usize, list_index + prealloc_item_count) - prealloc_exp - 1;
272 }
273
274 fn boxIndex(list_index: usize, shelf_index: ShelfIndex) usize {
275 if (prealloc_item_count == 0) {
276 return (list_index + 1) - (@as(usize, 1) << shelf_index);
277 }
278 return list_index + prealloc_item_count - (@as(usize, 1) << ((prealloc_exp + 1) + shelf_index));
279 }
280
281 fn freeShelves(self: *Self, from_count: ShelfIndex, to_count: ShelfIndex) void {
282 var i = from_count;
283 while (i != to_count) {
284 i -= 1;
285 self.allocator.free(self.dynamic_segments[i][0..shelfSize(i)]);
286 }
287 }
288
289 pub const Iterator = struct {
290 list: *Self,
291 index: usize,
292 box_index: usize,
293 shelf_index: ShelfIndex,
294 shelf_size: usize,
295
296 pub fn next(it: *Iterator) ?*T {
297 if (it.index >= it.list.len) return null;
298 if (it.index < prealloc_item_count) {
299 const ptr = &it.list.prealloc_segment[it.index];
300 it.index += 1;
301 if (it.index == prealloc_item_count) {
302 it.box_index = 0;
303 it.shelf_index = 0;
304 it.shelf_size = prealloc_item_count * 2;
305 }
306 return ptr;
307 }
308
309 const ptr = &it.list.dynamic_segments[it.shelf_index][it.box_index];
310 it.index += 1;
311 it.box_index += 1;
312 if (it.box_index == it.shelf_size) {
313 it.shelf_index += 1;
314 it.box_index = 0;
315 it.shelf_size *= 2;
316 }
317 return ptr;
318 }
319
320 pub fn prev(it: *Iterator) ?*T {
321 if (it.index == 0) return null;
322
323 it.index -= 1;
324 if (it.index < prealloc_item_count) return &it.list.prealloc_segment[it.index];
325
326 if (it.box_index == 0) {
327 it.shelf_index -= 1;
328 it.shelf_size /= 2;
329 it.box_index = it.shelf_size - 1;
330 } else {
331 it.box_index -= 1;
332 }
333
334 return &it.list.dynamic_segments[it.shelf_index][it.box_index];
335 }
336
337 pub fn peek(it: *Iterator) ?*T {
338 if (it.index >= it.list.len)
339 return null;
340 if (it.index < prealloc_item_count)
341 return &it.list.prealloc_segment[it.index];
342
343 return &it.list.dynamic_segments[it.shelf_index][it.box_index];
344 }
345
346 pub fn set(it: *Iterator, index: usize) void {
347 it.index = index;
348 if (index < prealloc_item_count) return;
349 it.shelf_index = shelfIndex(index);
350 it.box_index = boxIndex(index, it.shelf_index);
351 it.shelf_size = shelfSize(it.shelf_index);
352 }
353 };
354
355 pub fn iterator(self: *Self, start_index: usize) Iterator {
356 var it = Iterator{
357 .list = self,
358 .index = undefined,
359 .shelf_index = undefined,
360 .box_index = undefined,
361 .shelf_size = undefined,
362 };
363 it.set(start_index);
364 return it;
365 }
366 };
367}
368
369test "basic usage" {
370 const a = std.testing.allocator;
371
372 try testSegmentedList(0, a);
373 try testSegmentedList(1, a);
374 try testSegmentedList(2, a);
375 try testSegmentedList(4, a);
376 try testSegmentedList(8, a);
377 try testSegmentedList(16, a);
378}
379
380fn testSegmentedList(comptime prealloc: usize, allocator: Allocator) !void {
381 var list = SegmentedList(i32, prealloc).init(allocator);
382 defer list.deinit();
383
384 {
385 var i: usize = 0;
386 while (i < 100) : (i += 1) {
387 try list.push(@intCast(i32, i + 1));
388 try testing.expect(list.len == i + 1);
389 }
390 }
391
392 {
393 var i: usize = 0;
394 while (i < 100) : (i += 1) {
395 try testing.expect(list.at(i).* == @intCast(i32, i + 1));
396 }
397 }
398
399 {
400 var it = list.iterator(0);
401 var x: i32 = 0;
402 while (it.next()) |item| {
403 x += 1;
404 try testing.expect(item.* == x);
405 }
406 try testing.expect(x == 100);
407 while (it.prev()) |item| : (x -= 1) {
408 try testing.expect(item.* == x);
409 }
410 try testing.expect(x == 0);
411 }
412
413 try testing.expect(list.pop().? == 100);
414 try testing.expect(list.len == 99);
415
416 try list.pushMany(&[_]i32{ 1, 2, 3 });
417 try testing.expect(list.len == 102);
418 try testing.expect(list.pop().? == 3);
419 try testing.expect(list.pop().? == 2);
420 try testing.expect(list.pop().? == 1);
421 try testing.expect(list.len == 99);
422
423 try list.pushMany(&[_]i32{});
424 try testing.expect(list.len == 99);
425
426 {
427 var i: i32 = 99;
428 while (list.pop()) |item| : (i -= 1) {
429 try testing.expect(item == i);
430 list.shrinkCapacity(list.len);
431 }
432 }
433
434 {
435 var control: [100]i32 = undefined;
436 var dest: [100]i32 = undefined;
437
438 var i: i32 = 0;
439 while (i < 100) : (i += 1) {
440 try list.push(i + 1);
441 control[@intCast(usize, i)] = i + 1;
442 }
443
444 std.mem.set(i32, dest[0..], 0);
445 list.writeToSlice(dest[0..], 0);
446 try testing.expect(std.mem.eql(i32, control[0..], dest[0..]));
447
448 std.mem.set(i32, dest[0..], 0);
449 list.writeToSlice(dest[50..], 50);
450 try testing.expect(std.mem.eql(i32, control[50..], dest[50..]));
451 }
452
453 try list.setCapacity(0);
454}
455
456/// TODO look into why this std.math function was changed in
457/// fc9430f56798a53f9393a697f4ccd6bf9981b970.
458fn log2_int_ceil(comptime T: type, x: T) std.math.Log2Int(T) {
459 assert(x != 0);
460 const log2_val = std.math.log2_int(T, x);
461 if (@as(T, 1) << log2_val == x)
462 return log2_val;
463 return log2_val + 1;
464}
lib/std/std.zig+1
...@@ -29,6 +29,7 @@ pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceE...@@ -29,6 +29,7 @@ pub const PackedIntSliceEndian = @import("packed_int_array.zig").PackedIntSliceE
29pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;29pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
30pub const PriorityDequeue = @import("priority_dequeue.zig").PriorityDequeue;30pub const PriorityDequeue = @import("priority_dequeue.zig").PriorityDequeue;
31pub const Progress = @import("Progress.zig");31pub const Progress = @import("Progress.zig");
32pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
32pub const SemanticVersion = @import("SemanticVersion.zig");33pub const SemanticVersion = @import("SemanticVersion.zig");
33pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;34pub const SinglyLinkedList = @import("linked_list.zig").SinglyLinkedList;
34pub const StaticBitSet = bit_set.StaticBitSet;35pub const StaticBitSet = bit_set.StaticBitSet;