Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: fast deinit feature. #15

Merged
merged 4 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
/zig-out
/.vscode
/.zig-cache
*.code-workspace
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,19 @@ To use this library, you need at least Zig 0.13.x.

## API
```zig
// create tree type:
pub const Options = struct {
countChildren: bool = false,
};
pub fn TreeWithOptions(comptime K: type, comptime V: type, comptime Cmp: fn (a: K, b: K) math.Order, comptime options: Options) type
pub fn Tree(comptime K: type, comptime V: type, comptime Cmp: fn (a: K, b: K) math.Order) type

// init/deinit:
pub const InitOptions = struct {
allowFastDeinit: enum { always, auto, never } = .never,
};
pub fn init(a: std.mem.Allocator) Self
pub fn initWithOptions(a: std.mem.Allocator, io: InitOptions) Self
pub fn deinit()

// insert:
Expand Down
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.{
.name = "zigavl",
.version = "0.6.1",
.version = "0.7.0",
.dependencies = .{},
.paths = .{""},
}
102 changes: 101 additions & 1 deletion src/avl.zig
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,25 @@ fn locationCache(comptime K: type, comptime V: type, comptime Tags: type) type {
};
}

const allocAddrs = blk: {
var aa: std.heap.ArenaAllocator = undefined;
const aaa = aa.allocator();
var fba: std.heap.FixedBufferAllocator = undefined;
const fbaa = fba.allocator();
break :blk [_]*const anyopaque{
@ptrCast(@alignCast(aaa.vtable.alloc)),
@ptrCast(@alignCast(fbaa.vtable.alloc)),
};
};

fn fastDeinitAllowed(self: *Self) bool {
const ourAllocAddr: *const anyopaque = @ptrCast(@alignCast(self.a.vtable.alloc));
inline for (allocAddrs) |ptr| {
if (ourAllocAddr == ptr) return true;
}
return false;
}

fn create(self: *Self) !Location {
const node = try self.a.create(Location.Node);
node.* = Location.Node.init();
Expand All @@ -125,13 +144,27 @@ fn locationCache(comptime K: type, comptime V: type, comptime Tags: type) type {
};
}

// Options defines some parameters of the tree.
// Options defines some comptime parameters of the tree type.
pub const Options = struct {
// countChildren, if set, enables children counts for every node of the tree.
// the number of children allows to locate a node by its position with a guaranteed complexity O(logn).
countChildren: bool = false,
};

// InitOptions defines some runtime parameters of the tree instance.
pub const InitOptions = struct {
// allowFastDeinit speeds up deinit() call by making it a no-op in cases
// where all the memory can be freed on the allocator level.
// normally, deinit() traverses the tree removing each node, however,
// this might not be necessary, if certain types of allocators are used.
// enum values:
// always - deinit() never deletes the nodes.
// auto - deinit() does not delete the nodes,
// if std.heap.ArenaAllocator or std.heap.FixedBufferAllocator are for allocations.
// never[default] - deinit() always deletes the nodes.
allowFastDeinit: enum { always, auto, never } = .never,
};

// Tree is a generic avl tree.
// AVL tree (https://en.wikipedia.org/wiki/AVL_tree) is a self-balancing binary search tree.
// For each node of the tree the heights of the left and right sub-trees differ by at most one.
Expand Down Expand Up @@ -353,6 +386,7 @@ pub fn TreeWithOptions(comptime K: type, comptime V: type, comptime Cmp: fn (a:
return b;
}

// Iterator traverses the tree.
pub const Iterator = struct {
tree: *Self,
loc: ?Location,
Expand Down Expand Up @@ -387,6 +421,7 @@ pub fn TreeWithOptions(comptime K: type, comptime V: type, comptime Cmp: fn (a:
}
};

io: InitOptions,
lc: Cache,
length: usize,
root: ?Location,
Expand All @@ -395,16 +430,29 @@ pub fn TreeWithOptions(comptime K: type, comptime V: type, comptime Cmp: fn (a:

// init initializes the tree.
pub fn init(a: std.mem.Allocator) Self {
return Self.initWithOptions(a, .{});
}

// initWithOptions initializes the tree.
pub fn initWithOptions(a: std.mem.Allocator, io: InitOptions) Self {
return Self{
.lc = Cache.init(a),
.length = 0,
.root = null,
.min = null,
.max = null,
.io = io,
};
}

// deinit releases the memory taken by all the nodes.
// Time complexity:
// O(1) - if fast deinit is enabled (see InitOptions.allowFastDeinit).
// O(n) - otherwise.
pub fn deinit(self: *Self) void {
if (self.io.allowFastDeinit == .always or self.io.allowFastDeinit == .auto and self.lc.fastDeinitAllowed()) {
return;
}
const min = self.min orelse return;
var loc = goLeftRight(min);
while (true) {
Expand Down Expand Up @@ -1407,6 +1455,58 @@ test "tree random" {
}
}

const failingFreeAllocator = struct {
ptr: *anyopaque,
vtable: std.mem.Allocator.VTable,

fn free(_: *anyopaque, _: []u8, _: u8, _: usize) void {
@panic("should not happen");
}

fn init(a: std.mem.Allocator) failingFreeAllocator {
return failingFreeAllocator{ .ptr = a.ptr, .vtable = .{
.alloc = a.vtable.alloc,
.free = free,
.resize = a.vtable.resize,
} };
}

fn allocator(self: *failingFreeAllocator) std.mem.Allocator {
return std.mem.Allocator{
.ptr = self.ptr,
.vtable = &self.vtable,
};
}
};

test "arena allocator: auto fast deinit" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
try testFastDeinit(.{ .allowFastDeinit = .auto }, arena.allocator());
}

test "arena allocator: always fast deinit" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
try testFastDeinit(.{ .allowFastDeinit = .always }, arena.allocator());
}

test "fixed buffer allocator: auto fast deinit" {
var buff: [512]u8 = undefined;
var fb = std.heap.FixedBufferAllocator.init(&buff);
try testFastDeinit(.{ .allowFastDeinit = .auto }, fb.allocator());
}

fn testFastDeinit(io: InitOptions, a: std.mem.Allocator) !void {
const TreeType = TreeWithOptions(i64, i64, i64Cmp, .{});
var ta: failingFreeAllocator = failingFreeAllocator.init(a);
var t = TreeType.initWithOptions(ta.allocator(), io);
defer t.deinit();
_ = try t.insert(0, 0);
_ = try t.insert(1, 1);
_ = try t.insert(2, 2);
}

fn checkHeightAndBalance(comptime T: type, loc: ?T.Location) !void {
_ = try recalcHeightAndBalance(T, loc);
}
Expand Down
1 change: 1 addition & 0 deletions src/lib.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ const avl = @import("./avl.zig");
pub const Tree = avl.Tree;
pub const TreeWithOptions = avl.TreeWithOptions;
pub const Options = avl.Options;
pub const InitOptions = avl.InitOptions;
9 changes: 7 additions & 2 deletions src/tests.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ fn i64Cmp(a: i64, b: i64) math.Order {
test "test pub decls" {
const a = std.testing.allocator;
const TreeType = lib.Tree(i64, i64, i64Cmp);
var t = TreeType.init(a);
const options = lib.InitOptions{
.allowFastDeinit = .auto,
};
var aa = std.heap.ArenaAllocator.init(a);
defer aa.deinit();
var t = TreeType.initWithOptions(aa.allocator(), options);
defer t.deinit();
_ = try t.insert(0, 0);
var it = t.ascendFromStart();
Expand All @@ -29,7 +34,7 @@ test "tree example usage" {
defer _ = gpa.detectLeaks();
// first, create an i64-->i64 tree
const TreeType = lib.TreeWithOptions(i64, i64, i64Cmp, .{ .countChildren = true });
var t = TreeType.init(gpa.allocator());
var t = TreeType.initWithOptions(gpa.allocator(), .{ .allowFastDeinit = .auto });
defer t.deinit();
// add some elements
var i: i64 = 10;
Expand Down
Loading