authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-05-07 09:54:30-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2018-05-07 09:54:30-04:00
log78ba3b84850c1e1494797f76d147bc8c87f749e1
tree6cecc99e50a6ea7518dc3100f9adddf137a69e9c
parent7fdbaeca728312d22d877a711b46e157d3b01fe7
parent81007d0a4beed18314e948d3afbcdc7a8cfd73a9
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #992 from zig-lang/segmented-list

Segmented list implementation

5 files changed, 334 insertions(+), 34 deletions(-)

CMakeLists.txt+29-28
...@@ -416,8 +416,8 @@ set(ZIG_CPP_SOURCES...@@ -416,8 +416,8 @@ set(ZIG_CPP_SOURCES
416set(ZIG_STD_FILES416set(ZIG_STD_FILES
417 "array_list.zig"417 "array_list.zig"
418 "atomic/index.zig"418 "atomic/index.zig"
419 "atomic/stack.zig"
420 "atomic/queue.zig"419 "atomic/queue.zig"
420 "atomic/stack.zig"
421 "base64.zig"421 "base64.zig"
422 "buf_map.zig"422 "buf_map.zig"
423 "buf_set.zig"423 "buf_set.zig"
...@@ -427,13 +427,13 @@ set(ZIG_STD_FILES...@@ -427,13 +427,13 @@ set(ZIG_STD_FILES
427 "c/index.zig"427 "c/index.zig"
428 "c/linux.zig"428 "c/linux.zig"
429 "c/windows.zig"429 "c/windows.zig"
430 "crypto/blake2.zig"
431 "crypto/hmac.zig"
430 "crypto/index.zig"432 "crypto/index.zig"
431 "crypto/md5.zig"433 "crypto/md5.zig"
432 "crypto/sha1.zig"434 "crypto/sha1.zig"
433 "crypto/sha2.zig"435 "crypto/sha2.zig"
434 "crypto/sha3.zig"436 "crypto/sha3.zig"
435 "crypto/blake2.zig"
436 "crypto/hmac.zig"
437 "cstr.zig"437 "cstr.zig"
438 "debug/failing_allocator.zig"438 "debug/failing_allocator.zig"
439 "debug/index.zig"439 "debug/index.zig"
...@@ -445,12 +445,12 @@ set(ZIG_STD_FILES...@@ -445,12 +445,12 @@ set(ZIG_STD_FILES
445 "fmt/errol/index.zig"445 "fmt/errol/index.zig"
446 "fmt/errol/lookup.zig"446 "fmt/errol/lookup.zig"
447 "fmt/index.zig"447 "fmt/index.zig"
448 "hash_map.zig"
449 "hash/index.zig"
450 "hash/adler.zig"448 "hash/adler.zig"
451 "hash/crc.zig"449 "hash/crc.zig"
452 "hash/fnv.zig"450 "hash/fnv.zig"
451 "hash/index.zig"
453 "hash/siphash.zig"452 "hash/siphash.zig"
453 "hash_map.zig"
454 "heap.zig"454 "heap.zig"
455 "index.zig"455 "index.zig"
456 "io.zig"456 "io.zig"
...@@ -466,6 +466,28 @@ set(ZIG_STD_FILES...@@ -466,6 +466,28 @@ set(ZIG_STD_FILES
466 "math/atanh.zig"466 "math/atanh.zig"
467 "math/cbrt.zig"467 "math/cbrt.zig"
468 "math/ceil.zig"468 "math/ceil.zig"
469 "math/complex/abs.zig"
470 "math/complex/acos.zig"
471 "math/complex/acosh.zig"
472 "math/complex/arg.zig"
473 "math/complex/asin.zig"
474 "math/complex/asinh.zig"
475 "math/complex/atan.zig"
476 "math/complex/atanh.zig"
477 "math/complex/conj.zig"
478 "math/complex/cos.zig"
479 "math/complex/cosh.zig"
480 "math/complex/exp.zig"
481 "math/complex/index.zig"
482 "math/complex/ldexp.zig"
483 "math/complex/log.zig"
484 "math/complex/pow.zig"
485 "math/complex/proj.zig"
486 "math/complex/sin.zig"
487 "math/complex/sinh.zig"
488 "math/complex/sqrt.zig"
489 "math/complex/tan.zig"
490 "math/complex/tanh.zig"
469 "math/copysign.zig"491 "math/copysign.zig"
470 "math/cos.zig"492 "math/cos.zig"
471 "math/cosh.zig"493 "math/cosh.zig"
...@@ -502,33 +524,12 @@ set(ZIG_STD_FILES...@@ -502,33 +524,12 @@ set(ZIG_STD_FILES
502 "math/tan.zig"524 "math/tan.zig"
503 "math/tanh.zig"525 "math/tanh.zig"
504 "math/trunc.zig"526 "math/trunc.zig"
505 "math/complex/abs.zig"
506 "math/complex/acosh.zig"
507 "math/complex/acos.zig"
508 "math/complex/arg.zig"
509 "math/complex/asinh.zig"
510 "math/complex/asin.zig"
511 "math/complex/atanh.zig"
512 "math/complex/atan.zig"
513 "math/complex/conj.zig"
514 "math/complex/cosh.zig"
515 "math/complex/cos.zig"
516 "math/complex/exp.zig"
517 "math/complex/index.zig"
518 "math/complex/ldexp.zig"
519 "math/complex/log.zig"
520 "math/complex/pow.zig"
521 "math/complex/proj.zig"
522 "math/complex/sinh.zig"
523 "math/complex/sin.zig"
524 "math/complex/sqrt.zig"
525 "math/complex/tanh.zig"
526 "math/complex/tan.zig"
527 "mem.zig"527 "mem.zig"
528 "net.zig"528 "net.zig"
529 "os/child_process.zig"529 "os/child_process.zig"
530 "os/darwin.zig"530 "os/darwin.zig"
531 "os/darwin_errno.zig"531 "os/darwin_errno.zig"
532 "os/epoch.zig"
532 "os/file.zig"533 "os/file.zig"
533 "os/get_user_id.zig"534 "os/get_user_id.zig"
534 "os/index.zig"535 "os/index.zig"
...@@ -538,13 +539,13 @@ set(ZIG_STD_FILES...@@ -538,13 +539,13 @@ set(ZIG_STD_FILES
538 "os/linux/x86_64.zig"539 "os/linux/x86_64.zig"
539 "os/path.zig"540 "os/path.zig"
540 "os/time.zig"541 "os/time.zig"
541 "os/epoch.zig"
542 "os/windows/error.zig"542 "os/windows/error.zig"
543 "os/windows/index.zig"543 "os/windows/index.zig"
544 "os/windows/util.zig"544 "os/windows/util.zig"
545 "os/zen.zig"545 "os/zen.zig"
546 "rand/index.zig"546 "rand/index.zig"
547 "rand/ziggurat.zig"547 "rand/ziggurat.zig"
548 "segmented_list.zig"
548 "sort.zig"549 "sort.zig"
549 "special/bootstrap.zig"550 "special/bootstrap.zig"
550 "special/bootstrap_lib.zig"551 "special/bootstrap_lib.zig"
std/index.zig+2
...@@ -7,6 +7,7 @@ pub const BufferOutStream = @import("buffer.zig").BufferOutStream;...@@ -7,6 +7,7 @@ pub const BufferOutStream = @import("buffer.zig").BufferOutStream;
7pub const HashMap = @import("hash_map.zig").HashMap;7pub const HashMap = @import("hash_map.zig").HashMap;
8pub const LinkedList = @import("linked_list.zig").LinkedList;8pub const LinkedList = @import("linked_list.zig").LinkedList;
9pub const IntrusiveLinkedList = @import("linked_list.zig").IntrusiveLinkedList;9pub const IntrusiveLinkedList = @import("linked_list.zig").IntrusiveLinkedList;
10pub const SegmentedList = @import("segmented_list.zig").SegmentedList;
1011
11pub const atomic = @import("atomic/index.zig");12pub const atomic = @import("atomic/index.zig");
12pub const base64 = @import("base64.zig");13pub const base64 = @import("base64.zig");
...@@ -43,6 +44,7 @@ test "std" {...@@ -43,6 +44,7 @@ test "std" {
43 _ = @import("buffer.zig");44 _ = @import("buffer.zig");
44 _ = @import("hash_map.zig");45 _ = @import("hash_map.zig");
45 _ = @import("linked_list.zig");46 _ = @import("linked_list.zig");
47 _ = @import("segmented_list.zig");
4648
47 _ = @import("base64.zig");49 _ = @import("base64.zig");
48 _ = @import("build.zig");50 _ = @import("build.zig");
std/math/index.zig+26
...@@ -558,6 +558,32 @@ test "math.floorPowerOfTwo" {...@@ -558,6 +558,32 @@ test "math.floorPowerOfTwo" {
558 comptime testFloorPowerOfTwo();558 comptime testFloorPowerOfTwo();
559}559}
560560
561pub fn log2_int(comptime T: type, x: T) Log2Int(T) {
562 assert(x != 0);
563 return Log2Int(T)(T.bit_count - 1 - @clz(x));
564}
565
566pub fn log2_int_ceil(comptime T: type, x: T) Log2Int(T) {
567 assert(x != 0);
568 const log2_val = log2_int(T, x);
569 if (T(1) << log2_val == x)
570 return log2_val;
571 return log2_val + 1;
572}
573
574test "std.math.log2_int_ceil" {
575 assert(log2_int_ceil(u32, 1) == 0);
576 assert(log2_int_ceil(u32, 2) == 1);
577 assert(log2_int_ceil(u32, 3) == 2);
578 assert(log2_int_ceil(u32, 4) == 2);
579 assert(log2_int_ceil(u32, 5) == 3);
580 assert(log2_int_ceil(u32, 6) == 3);
581 assert(log2_int_ceil(u32, 7) == 3);
582 assert(log2_int_ceil(u32, 8) == 3);
583 assert(log2_int_ceil(u32, 9) == 4);
584 assert(log2_int_ceil(u32, 10) == 4);
585}
586
561fn testFloorPowerOfTwo() void {587fn testFloorPowerOfTwo() void {
562 assert(floorPowerOfTwo(u32, 63) == 32);588 assert(floorPowerOfTwo(u32, 63) == 32);
563 assert(floorPowerOfTwo(u32, 64) == 64);589 assert(floorPowerOfTwo(u32, 64) == 64);
std/math/log2.zig+1-6
...@@ -31,17 +31,12 @@ pub fn log2(x: var) @typeOf(x) {...@@ -31,17 +31,12 @@ pub fn log2(x: var) @typeOf(x) {
31 return result;31 return result;
32 },32 },
33 TypeId.Int => {33 TypeId.Int => {
34 return log2_int(T, x);34 return math.log2_int(T, x);
35 },35 },
36 else => @compileError("log2 not implemented for " ++ @typeName(T)),36 else => @compileError("log2 not implemented for " ++ @typeName(T)),
37 }37 }
38}38}
3939
40pub fn log2_int(comptime T: type, x: T) T {
41 assert(x != 0);
42 return T.bit_count - 1 - T(@clz(x));
43}
44
45pub fn log2_32(x_: f32) f32 {40pub fn log2_32(x_: f32) f32 {
46 const ivln2hi: f32 = 1.4428710938e+00;41 const ivln2hi: f32 = 1.4428710938e+00;
47 const ivln2lo: f32 = -1.7605285393e-04;42 const ivln2lo: f32 = -1.7605285393e-04;
std/segmented_list.zig created+276
...@@ -0,0 +1,276 @@
1const std = @import("index.zig");
2const assert = std.debug.assert;
3const Allocator = std.mem.Allocator;
4
5// Imagine that `fn at(self: &Self, index: usize) &T` is a customer asking for a box
6// from a warehouse, based on a flat array, boxes ordered from 0 to N - 1.
7// But the warehouse actually stores boxes in shelves of increasing powers of 2 sizes.
8// So when the customer requests a box index, we have to translate it to shelf index
9// and box index within that shelf. Illustration:
10//
11// customer indexes:
12// shelf 0: 0
13// shelf 1: 1 2
14// shelf 2: 3 4 5 6
15// shelf 3: 7 8 9 10 11 12 13 14
16// shelf 4: 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
17// 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
18// ...
19//
20// warehouse indexes:
21// shelf 0: 0
22// shelf 1: 0 1
23// shelf 2: 0 1 2 3
24// shelf 3: 0 1 2 3 4 5 6 7
25// shelf 4: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
26// 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
27// ...
28//
29// With this arrangement, here are the equations to get the shelf index and
30// box index based on customer box index:
31//
32// shelf_index = floor(log2(customer_index + 1))
33// shelf_count = ceil(log2(box_count + 1))
34// box_index = customer_index + 1 - 2 ** shelf
35// shelf_size = 2 ** shelf_index
36//
37// Now we complicate it a little bit further by adding a preallocated shelf, which must be
38// a power of 2:
39// prealloc=4
40//
41// customer indexes:
42// prealloc: 0 1 2 3
43// shelf 0: 4 5 6 7 8 9 10 11
44// shelf 1: 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
45// 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
46// ...
47//
48// warehouse indexes:
49// prealloc: 0 1 2 3
50// shelf 0: 0 1 2 3 4 5 6 7
51// shelf 1: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
52// 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
53// ...
54//
55// Now the equations are:
56//
57// shelf_index = floor(log2(customer_index + prealloc)) - log2(prealloc) - 1
58// shelf_count = ceil(log2(box_count + prealloc)) - log2(prealloc) - 1
59// box_index = customer_index + prealloc - 2 ** (log2(prealloc) + 1 + shelf)
60// shelf_size = prealloc * 2 ** (shelf_index + 1)
61
62/// This is a stack data structure where pointers to indexes have the same lifetime as the data structure
63/// itself, unlike ArrayList where push() invalidates all existing element pointers.
64/// The tradeoff is that elements are not guaranteed to be contiguous. For that, use ArrayList.
65/// Note however that most elements are contiguous, making this data structure cache-friendly.
66///
67/// Because it never has to copy elements from an old location to a new location, it does not require
68/// its elements to be copyable, and it avoids wasting memory when backed by an ArenaAllocator.
69/// Note that the push() and pop() convenience methods perform a copy, but you can instead use
70/// addOne(), at(), setCapacity(), and shrinkCapacity() to avoid copying items.
71///
72/// This data structure has O(1) push and O(1) pop.
73///
74/// It supports preallocated elements, making it especially well suited when the expected maximum
75/// size is small. `prealloc_item_count` must be 0, or a power of 2.
76pub fn SegmentedList(comptime T: type, comptime prealloc_item_count: usize) type {
77 return struct {
78 const Self = this;
79 const prealloc_exp = blk: {
80 // we don't use the prealloc_exp constant when prealloc_item_count is 0.
81 assert(prealloc_item_count != 0);
82
83 const value = std.math.log2_int(usize, prealloc_item_count);
84 assert((1 << value) == prealloc_item_count); // prealloc_item_count must be a power of 2
85 break :blk @typeOf(1)(value);
86 };
87 const ShelfIndex = std.math.Log2Int(usize);
88
89 allocator: &Allocator,
90 len: usize,
91 prealloc_segment: [prealloc_item_count]T,
92 dynamic_segments: []&T,
93
94 /// Deinitialize with `deinit`
95 pub fn init(allocator: &Allocator) Self {
96 return Self {
97 .allocator = allocator,
98 .len = 0,
99 .prealloc_segment = undefined,
100 .dynamic_segments = []&T{},
101 };
102 }
103
104 pub fn deinit(self: &Self) void {
105 self.freeShelves(ShelfIndex(self.dynamic_segments.len), 0);
106 self.allocator.free(self.dynamic_segments);
107 *self = undefined;
108 }
109
110 pub fn at(self: &Self, i: usize) &T {
111 assert(i < self.len);
112 return self.uncheckedAt(i);
113 }
114
115 pub fn count(self: &const Self) usize {
116 return self.len;
117 }
118
119 pub fn push(self: &Self, item: &const T) !void {
120 const new_item_ptr = try self.addOne();
121 *new_item_ptr = *item;
122 }
123
124 pub fn pushMany(self: &Self, items: []const T) !void {
125 for (items) |item| {
126 try self.push(item);
127 }
128 }
129
130 pub fn pop(self: &Self) ?T {
131 if (self.len == 0)
132 return null;
133
134 const index = self.len - 1;
135 const result = *self.uncheckedAt(index);
136 self.len = index;
137 return result;
138 }
139
140 pub fn addOne(self: &Self) !&T {
141 const new_length = self.len + 1;
142 try self.setCapacity(new_length);
143 const result = self.uncheckedAt(self.len);
144 self.len = new_length;
145 return result;
146 }
147
148 pub fn setCapacity(self: &Self, new_capacity: usize) !void {
149 if (new_capacity <= prealloc_item_count) {
150 const len = ShelfIndex(self.dynamic_segments.len);
151 if (len == 0) return;
152 self.freeShelves(len, 0);
153 self.allocator.free(self.dynamic_segments);
154 self.dynamic_segments = []&T{};
155 return;
156 }
157
158 const new_cap_shelf_count = shelfCount(new_capacity);
159 const old_shelf_count = ShelfIndex(self.dynamic_segments.len);
160 if (new_cap_shelf_count > old_shelf_count) {
161 self.dynamic_segments = try self.allocator.realloc(&T, self.dynamic_segments, new_cap_shelf_count);
162 var i = old_shelf_count;
163 errdefer {
164 self.freeShelves(i, old_shelf_count);
165 self.dynamic_segments = self.allocator.shrink(&T, self.dynamic_segments, old_shelf_count);
166 }
167 while (i < new_cap_shelf_count) : (i += 1) {
168 self.dynamic_segments[i] = (try self.allocator.alloc(T, shelfSize(i))).ptr;
169 }
170 return;
171 }
172 if (new_cap_shelf_count == old_shelf_count) {
173 return;
174 }
175 self.freeShelves(old_shelf_count, new_cap_shelf_count);
176 self.dynamic_segments = self.allocator.shrink(&T, self.dynamic_segments, new_cap_shelf_count);
177 }
178
179 pub fn shrinkCapacity(self: &Self, new_capacity: usize) void {
180 assert(new_capacity <= prealloc_item_count or shelfCount(new_capacity) <= self.dynamic_segments.len);
181 self.setCapacity(new_capacity) catch unreachable;
182 }
183
184 pub fn uncheckedAt(self: &Self, index: usize) &T {
185 if (index < prealloc_item_count) {
186 return &self.prealloc_segment[index];
187 }
188 const shelf_index = shelfIndex(index);
189 const box_index = boxIndex(index, shelf_index);
190 return &self.dynamic_segments[shelf_index][box_index];
191 }
192
193 fn shelfCount(box_count: usize) ShelfIndex {
194 if (prealloc_item_count == 0) {
195 return std.math.log2_int_ceil(usize, box_count + 1);
196 }
197 return std.math.log2_int_ceil(usize, box_count + prealloc_item_count) - prealloc_exp - 1;
198 }
199
200 fn shelfSize(shelf_index: ShelfIndex) usize {
201 if (prealloc_item_count == 0) {
202 return usize(1) << shelf_index;
203 }
204 return usize(1) << (shelf_index + (prealloc_exp + 1));
205 }
206
207 fn shelfIndex(list_index: usize) ShelfIndex {
208 if (prealloc_item_count == 0) {
209 return std.math.log2_int(usize, list_index + 1);
210 }
211 return std.math.log2_int(usize, list_index + prealloc_item_count) - prealloc_exp - 1;
212 }
213
214 fn boxIndex(list_index: usize, shelf_index: ShelfIndex) usize {
215 if (prealloc_item_count == 0) {
216 return (list_index + 1) - (usize(1) << shelf_index);
217 }
218 return list_index + prealloc_item_count - (usize(1) << ((prealloc_exp + 1) + shelf_index));
219 }
220
221 fn freeShelves(self: &Self, from_count: ShelfIndex, to_count: ShelfIndex) void {
222 var i = from_count;
223 while (i != to_count) {
224 i -= 1;
225 self.allocator.free(self.dynamic_segments[i][0..shelfSize(i)]);
226 }
227 }
228
229 };
230}
231
232test "std.SegmentedList" {
233 var da = std.heap.DirectAllocator.init();
234 defer da.deinit();
235 var a = &da.allocator;
236
237 try testSegmentedList(0, a);
238 try testSegmentedList(1, a);
239 try testSegmentedList(2, a);
240 try testSegmentedList(4, a);
241 try testSegmentedList(8, a);
242 try testSegmentedList(16, a);
243}
244
245fn testSegmentedList(comptime prealloc: usize, allocator: &Allocator) !void {
246 var list = SegmentedList(i32, prealloc).init(allocator);
247 defer list.deinit();
248
249 {var i: usize = 0; while (i < 100) : (i += 1) {
250 try list.push(i32(i + 1));
251 assert(list.len == i + 1);
252 }}
253
254 {var i: usize = 0; while (i < 100) : (i += 1) {
255 assert(*list.at(i) == i32(i + 1));
256 }}
257
258 assert(??list.pop() == 100);
259 assert(list.len == 99);
260
261 try list.pushMany([]i32 { 1, 2, 3 });
262 assert(list.len == 102);
263 assert(??list.pop() == 3);
264 assert(??list.pop() == 2);
265 assert(??list.pop() == 1);
266 assert(list.len == 99);
267
268 try list.pushMany([]const i32 {});
269 assert(list.len == 99);
270
271 var i: i32 = 99;
272 while (list.pop()) |item| : (i -= 1) {
273 assert(item == i);
274 list.shrinkCapacity(list.len);
275 }
276}