Live data from Hacker News

The curious case of a memory leak in a Zig program

iamkroot.github.io

41–50 of 80 posts

Re: The curious case of a memory leak in a Zig program

#42
post #5
post #3

TL;DR: the author had to figure out the hard way that Zig's FixedBufferAllocator is a bump allocator, and that it doesn't reuse freed memory except when it's the last allocation.

What an awful API design choice. It’s a stack allocator that leaks your memory if you don’t free in reverse order. Why would anybody ever want that behavior, let alone as the default?

One often uses these allocators for temporary allocations in contexts where you can reset them at a known time. For example, in a game you put a lot of temporary stuff in them faster than by using a general purpose allocator, and then call .reset() at the end of every frame. You then reuse the same memory buffer next frame.

Every allocator other than a general purpose allocator has a use case where it's faster, and assumes you know what you're doing with it.

Re: The curious case of a memory leak in a Zig program

#43
post #35

I know absolutely nothing about Zig (but I know C) and when I read "FixedBufferAllocator" I immediately guessed what the problem would be. I can see why it is claimed as a C replacement. I am actually kind of surprised the author spent so much time figuring it out. The name of the allocator is not that well-defined, but at least to me it hints of it being simpler rather than full-featured allocator. I would also imag…

After reading the description of the problem, I also had the thought, "Maybe FixedBufferAllocator is a bump allocator?". So before reading further, I checked the documentation to see if I was right. Turns out .. there is no documentation for FixedBufferAllocator? Here's the documentation page: https://ziglang.org/documentation/master/std/#A;std:heap.Fix... . It just lists a couple methods, most of which have the desc…

> From those docs, it doesn't even look like there's a way to allocate memory using the FixedBufferAllocator.

There is but not directly through the FBA, you need to get a concrete Allocator struct out of that by calling the allocator() or threadsafeAllocator() functions.

Re: The curious case of a memory leak in a Zig program

#44

Earlier quoted context omitted.

Because this is a specific allocator different from the general purpose allocator which is the "default" option. It is aimed at some specific use cases, when developers want to fine-tune their allocation strategies.

I get that it is a specific allocator for niche use cases but i think it would be better that free throws some exception if it is done out of order rather than just being a no-op and making the programmer figure it out.

The point of these kinds of allocators is to not ever free their allocations. Unfortunately not having a free breaks generic code, so having a no-op free is the only real solution.

Re: The curious case of a memory leak in a Zig program

#45

Earlier quoted context omitted.

Because this is a specific allocator different from the general purpose allocator which is the "default" option. It is aimed at some specific use cases, when developers want to fine-tune their allocation strategies.

I get that it is a specific allocator for niche use cases but i think it would be better that free throws some exception if it is done out of order rather than just being a no-op and making the programmer figure it out.

No. You definitely want it to not cause an error/panic (zig does not have exceptions).

1. Frees are never supposed to error

2. You want code to be interchangeable so that you can try out different allocators.

3. Eventually when someone writes a lifetime analysis tool for zig, you'll want to signify the memory as freed

4. If your program is long-running (the bumping is part of a subtask) you probably want the bump allocator to be itself freed on its own lifetime, so you'll eventually free that memory.

Re: The curious case of a memory leak in a Zig program

#46
post #35

Earlier quoted context omitted.

After reading the description of the problem, I also had the thought, "Maybe FixedBufferAllocator is a bump allocator?". So before reading further, I checked the documentation to see if I was right. Turns out .. there is no documentation for FixedBufferAllocator? Here's the documentation page: https://ziglang.org/documentation/master/std/#A;std:heap.Fix... . It just lists a couple methods, most of which have the desc…

> From those docs, it doesn't even look like there's a way to allocate memory using the FixedBufferAllocator. There is but not directly through the FBA, you need to get a concrete Allocator struct out of that by calling the allocator() or threadsafeAllocator() functions.

Yeah, I guessed it was something external. I just said it to illustrate how surprisingly incomplete the docs are, I would expect the docs for an allocator to include enough information to figure out how to allocate something with the allocator.

Re: The curious case of a memory leak in a Zig program

#47
post #39

I know absolutely nothing about Zig (but I know C) and when I read "FixedBufferAllocator" I immediately guessed what the problem would be. I can see why it is claimed as a C replacement. I am actually kind of surprised the author spent so much time figuring it out. The name of the allocator is not that well-defined, but at least to me it hints of it being simpler rather than full-featured allocator. I would also imag…

I feel like this could also be fixed by a simple algorithm to keep track of the start index in FixBufAlloc.

Not really. If you allocate a bunch of objects then deallocate one in the middle of the sequence neither head or tail will help you. And once you’ve done that you’ve hit holes you can’t track anymore.

To fix this issue you need a completely different allocator design, e.g. a bitmap, which can keep track of individual locations within its buffer.

Re: The curious case of a memory leak in a Zig program

#48

Earlier quoted context omitted.

Because this is a specific allocator different from the general purpose allocator which is the "default" option. It is aimed at some specific use cases, when developers want to fine-tune their allocation strategies.

I get that it is a specific allocator for niche use cases but i think it would be better that free throws some exception if it is done out of order rather than just being a no-op and making the programmer figure it out.

Bump allocators of this type are meant to be reset to 0 at a known time when it's safe to do so. Its perfectly legal to not free in order. You really shouldn't care about freeing in order with them. Instead, one normally pays attention to when reset() is supposed to be called to not break anything.

For games it's easy to find a safe spot (end of frame). For a server, you might have have a pool of bump allocators (1 per connection), and you'd reset them after every http request. etc.

Re: The curious case of a memory leak in a Zig program

#49

I know absolutely nothing about Zig (but I know C) and when I read "FixedBufferAllocator" I immediately guessed what the problem would be. I can see why it is claimed as a C replacement. I am actually kind of surprised the author spent so much time figuring it out. The name of the allocator is not that well-defined, but at least to me it hints of it being simpler rather than full-featured allocator. I would also imag…

> I am actually kind of surprised the author spent so much time figuring it out.

I wouldn't be. People learn different programming techniques at different times. I'd actually assume quite a lot of programmers don't know what a bump allocator is and eventually run into one in the wild. Kudos for the author for successfully debugging and discovering that.

> One would guess the point of this would be to destroy the entire allocator on every iteration

Not necessarily every iteration, but yes, these allocators are meant to be reset to 0 at a known time, when it's safe to do so.

Re: The curious case of a memory leak in a Zig program

#50
post #13

> As a personal challenge, I strived to explicitly limit the amount of memory needed for solving each AoC problem to something that fits on the stack (typically a few MBs at most). If the purpose is to "use limited amount memory" I would suggest to use a GeneralPurposeAllocator and setting "enable_memory_limit" and "requested_memory_limit": https://github.com/ziglang/zig/blob/8f481dfc3c4f12327499485e... . If the purp…

Thanks for the pointers! > use a GeneralPurposeAllocator and setting "enable_memory_limit" and "requested_memory_limit" Interesting! I hadn't looked at GeneralPurposeAllocator too closely, but yes these seem like the right way to do things instead of abusing FixedBufferAllocator as I did. > If the purpose is to "only use the stack"... Not really, I just had to decide on some arbitrary upper bound on the mem usage, an…

I think you might be able to abuse an ArenaAllocator wrapping a FixedBufferAllocator. I haven't tested it, but IIRC, Zig's ArenaAllocator deallocates in reverse order once it's reset (it uses a singly linked list to keep track of allocations, so that's the natural way to do deallocation), so it might play nicely with the underlying FixedBufferAllocator's requirements.

If this is correct, it's likely an undocumented implementation detail, so probably not something you should rely on always being the case.

Post reply on HN