mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-26 13:01:34 +03:00
std.heap.ArenaAllocator: make it threadsafe
Modifies the `Allocator` implementation provided by `ArenaAllocator` to be threadsafe using only atomics and no synchronization primitives locked behind an `Io` implementation. At its core this is a lock-free singly linked list which uses CAS loops to exchange the head node. A nice property of `ArenaAllocator` is that the only functions that can ever remove nodes from its linked list are `reset` and `deinit`, both of which are not part of the `Allocator` interface and thus aren't threadsafe, so node-related ABA problems are impossible. There *are* some trade-offs: end index tracking is now per node instead of per allocator instance. It's not possible to publish a head node and its end index at the same time if the latter isn't part of the former. Another compromise had to be made in regards to resizing existing nodes. Annoyingly, `rawResize` of an arbitrary thread-safe child allocator can of course never be guaranteed to be an atomic operation, so only one `alloc` call can ever resize at the same time, other threads have to consider any resizes they attempt during that time failed. This causes slightly less optimal behavior than what could be achieved with a mutex. The LSB of `Node.size` is used to signal that a node is being resized. This means that all nodes have to have an even size. Calls to `alloc` have to allocate new nodes optimistically as they can only know whether any CAS on a head node will succeed after attempting it, and to attempt the CAS they of course already need to know the address of the freshly allocated node they are trying to make the new head. The simplest solution to this would be to just free the new node again if a CAS fails, however this can be expensive and would mean that in practice arenas could only really be used with a GPA as their child allocator. To work around this, this implementation keeps its own free list of nodes which didn't make their CAS to be reused by a later `alloc` invocation. To keep things simple and avoid ABA problems the free list is only ever be accessed beyond its head by 'stealing' the head node (and thus the entire list) with an atomic swap. This makes iteration and removal trivial since there's only ever one thread doing it at a time which also owns all nodes it's holding. When the thread is done it can just push its list onto the free list again. This implementation offers comparable performance to the previous one when only being accessed by a single thread and a slight speedup compared to the previous implementation wrapped into a `ThreadSafeAllocator` up to ~7 threads performing operations on it concurrently. (measured on a base model MacBook Pro M1)
This commit is contained in:
committed by
Andrew Kelley
parent
2f8e660805
commit
a3a9dc111d
+1
-1
@@ -263,7 +263,7 @@ set(ZIG_STAGE2_SOURCES
|
||||
lib/std/hash/wyhash.zig
|
||||
lib/std/hash_map.zig
|
||||
lib/std/heap.zig
|
||||
lib/std/heap/arena_allocator.zig
|
||||
lib/std/heap/ArenaAllocator.zig
|
||||
lib/std/json.zig
|
||||
lib/std/leb128.zig
|
||||
lib/std/log.zig
|
||||
|
||||
Reference in New Issue
Block a user