microsoft / microsoft/mimalloc
Move a heap to a new thread?
- Dominant language
- C
- Stars
- 13.4k
- Forks
- 1.2k
- Avg merge
- 4d 45m
- Merged PRs (30d)
- 13
Description
I'm currently using `mi_heap_new` as a kind of arena allocator
It works very well when allocations only occur on a single thread
What do you suggest doing if the data needs to move to another thread? By "move", I mean arrays potentially need to be re-allocated on a different thread than the one it was created on.
The specific usecase is an HTTP server where most requests are processed by one thread, but if it's a websocket connection or a connection which will need to run some JavaScript, it runs that in a separate thread
In case it helps, here's the code for the arena allocator (zig lets you pass in memory allocators to most functions)
```zig
const mem = @import("std").mem;
const builtin = @import("std").builtin;
const std = @import("std");
const mimalloc = @import("./allocators/mimalloc.zig");
const Environment = @import("./env.zig");
const FeatureFlags = @import("./feature_flags.zig");
const Allocator = mem.Allocator;
const assert = std.debug.assert;
pub const Arena = struct {
heap: *mimalloc.mi_heap_t = undefined,
pub fn backingAllocator(this: Arena) Allocator {
var arena = Arena{ .heap = this.heap.backing() };
return arena.allocator();
}
pub fn allocator(this: Arena) Allocator {
return Allocator{ .ptr = this.heap, .vtable = &c_allocator_vtable };
}
pub fn deinit(this: *Arena) void {
mimalloc.mi_heap_destroy(this.heap);
}
pub fn reset(this: *Arena) void {
this.deinit();
this.* = init() catch unreachable;
}
pub fn init() !Arena {
return Arena{ .heap = mimalloc.mi_heap_new() orelse return error.OutOfMemory };
}
pub fn gc(this: Arena, force: bool) void {
mimalloc.mi_heap_collect(this.heap, force);
}
// Copied from rust
const MI_MAX_ALIGN_SIZE = 16;
inline fn mi_malloc_satisfies_alignment(alignment: usize, size: usize) bool {
return (alignment == @sizeOf(*anyopaque) or
(alignment == MI_MAX_ALIGN_SIZE and size > (MI_MAX_ALIGN_SIZE / 2)));
}
fn alignedAlloc(heap: *mimalloc.mi_heap_t, len: usize, alignment: usize) ?[*]u8 {
if (comptime FeatureFlags.log_allocations) std.debug.print("Malloc: {d}\n", .{len});
// this is the logic that posix_memalign does
var ptr = if (mi_malloc_satisfies_alignment(alignment, len))
mimalloc.mi_heap_malloc(heap, len)
else
mimalloc.mi_heap_malloc_aligned(heap, len, alignment);
return @ptrCast([*]u8, ptr orelse null);
}
fn alloc(
arena: *anyopaque,
len: usize,
alignment: u29,
len_align: u29,
return_address: usize,
) error{OutOfMemory}![]u8 {
_ = return_address;
assert(len > 0);
assert(std.math.isPowerOfTwo(alignment));
var ptr = alignedAlloc(@ptrCast(*mimalloc.mi_heap_t, arena), len, alignment) orelse return error.OutOfMemory;
if (len_align == 0) {
return ptr[0..len];
}
// std.mem.Allocator asserts this, we do it here so we can see the metadata
if (comptime Environment.allow_assert) {
const size = mem.alignBackwardAnyAlign(mimalloc.mi_usable_size(ptr), len_align);
assert(size >= len);
return ptr[0..size];
} else {
return ptr[0..mem.alignBackwardAnyAlign(mimalloc.mi_usable_size(ptr), len_align)];
}
}
fn resize(
_: *anyopaque,
buf: []u8,
buf_align: u29,
new_len: usize,
len_align: u29,
return_address: usize,
) ?usize {
_ = buf_align;
_ = return_address;
if (new_len <= buf.len) {
return mem.alignAllocLen(buf.len, new_len, len_align);
}
const full_len = mimalloc.mi_usable_size(buf.ptr);
if (new_len <= full_len) {
return mem.alignAllocLen(full_len, new_len, len_align);
}
return null;
}
fn free(
_: *anyopaque,
buf: []u8,
buf_align: u29,
return_address: usize,
) void {
_ = buf_align;
_ = return_address;
mimalloc.mi_free(buf.ptr);
}
};
const c_allocator_vtable = Allocator.VTable{
.alloc = Arena.alloc,
.resize = Arena.resize,
.free = Arena.free,
};
```
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by reading the mi_heap_new, mi_heap_destroy, mi_heap_collect, and mi_free APIs referenced in the issue, along with the allocator wrapper shown in the body. Determine whether moving allocations between threads is supported and what documented usage should apply to the websocket and JavaScript cases. Done means a clear recommendation or an agreed API change, with any required behavior documented and tested.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c, zig
- Domain
- operating-systems
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 20/100