-
-
Notifications
You must be signed in to change notification settings - Fork 3k
MemoryPool: Allocate only once for preheating & impl. proper resetting. #25429
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
base: master
Are you sure you want to change the base?
Conversation
0b46bb8
to
a56f564
Compare
I thought about this too in #23234, I'm not sure if there's any performance benefit to be had here though. The pool is already backed by an If you want, you can use this piece of code I stole from the article about the original implementation of codepub fn main() !void {
const gpa = std.heap.c_allocator;
var rand: std.Random.DefaultPrng = .init(0);
const r = rand.random();
var open: std.ArrayList(*anyopaque) = try .initCapacity(gpa, 300);
defer open.deinit(gpa);
inline for (.{ [1]u8, [8 << 10]u8, [1 << 10 << 10]u8 }) |T| {
var pool: std.heap.MemoryPoolExtra(T, .{}) = .init(gpa);
defer pool.deinit();
defer open.clearRetainingCapacity();
for (0..2_500_000) |_| {
const fill_rate = open.items.len / 256;
const i = r.int(usize);
if (i <= fill_rate) {
pool.destroy(@ptrCast(@alignCast(open.swapRemove(i))));
} else {
const item = try pool.create();
item[0] = 123;
try open.append(gpa, @ptrCast(item));
}
}
}
}
const std = @import("std");
Just a warning, running this benchmark can trigger the OOM killer (at least on my machine), if it does just adjust the allocation sizes until it doesn't. Maybe a good solution would be to make |
I ran a slightly modified version of your test and the new implementation indeed performs better (I have also modified the code to avoid Out-of-Memory errors). Generally speaking the performance benefits are
Here is the test results:
My code looked like thispub fn main() !void {
const gpa = std.heap.page_allocator;
var rand: std.Random.DefaultPrng = .init(0);
const r = rand.random();
var open: std.ArrayList(*anyopaque) = try .initCapacity(gpa, 300);
defer open.deinit(gpa);
inline for (.{ [1]u8, [8 << 10]u8, [1 << 10 << 10]u8 }) |T| {
const iterations = 25_000_000 / @sizeOf(T);
var pool: MemoryPoolExtra(T, .{}) = try .initPreheated(gpa, iterations);
defer pool.deinit();
defer open.clearRetainingCapacity();
for (0..iterations) |_| {
const fill_rate = open.items.len / 256;
const i = r.int(usize);
if (i <= fill_rate) {
pool.destroy(@ptrCast(@alignCast(open.swapRemove(i))));
} else {
const item = try pool.create();
item[0] = 123;
try open.append(gpa, @ptrCast(item));
}
}
}
}
const std = @import("std");
const MemoryPoolExtra = @import("std").heap.MemoryPoolExtra;
// you can replace this line with (if you download the file from my commit):
// const MemoryPoolExtra = @import("memory_pool.zig").MemoryPoolExtra; But in my opinion even more important is, that when preheating fails, you know you haven't allocated any memory. Without reading the implementation, the old version does not clarify some allocations may have succeeded. |
I have just implemented the TODO. Resetting a Memory pool is now possible. The modes Why don't we need to save a list of nodes? Because building nodes is actually pretty cheap with the new Added basic test to showcase the capabilities. |
PS: also, the memory pool isn't even allowed to save a list of previously allocated memory and work with that (if we want to reset the arena allocator too), as the previous version suggests, because the underlying arena allocator actually frees its buffers and allocates one single buffer for preheating purposes (with the desired capacity). I think, when calling reset on the memory pool, a reset on the arena is desired also (for example to avoid fragmentation in the long run), so disabling resetting of the underlying arena allcoator just to able to create a new list of free_nodes in [non-armotized] O(1) is a big trade off. i will try to illustrate the pros and cons in this table:
|
More uniform comments.
a7106e9
to
e79f8b0
Compare
Respect arena reset result.
e79f8b0
to
728b54c
Compare
Mempool calls the wrapped allocator N times when preheating it with N items.
I think it is more appropriate, if it only allocates one time. This should be faster and more importantly,
in case preheating fails (due to an allocation error), the state of the Mempool is easier to reason about.