[ $davids.sh ] — david shekunts blog

🔤 GC + Allocators = ❤️

# [ $davids.sh ] · message #250

🔤** GC + Allocators = ❤️**

Did you know that even in languages with GC you can manage memory?

#go #zig

  • @ [ $davids.sh ] · # 1544

    The core idea is this: your language already has a garbage collector (GC), BUT from time to time, there are specific scenarios where you need to allocate a set of objects and then, at a specific point, clean up that entire set at once.

    For example, actors: when a client connects to the application, we fetch all the necessary data for them, put it into memory, process it, and when the client disconnects, we save it and need to unload it from memory.

    For loading and unloading from memory, the GC would have to perform countless checks to see if this piece is still in use and then free it or keep it. Moreover, it would do this on every cycle until the actual deallocation occurs.*

    * - The GC's principle depends on the language, but this is the average case.

    Now, imagine if we could use a structure, let's call it an Allocator, with "create" and "destroy" methods that allow us to explicitly specify "create here, and clean up here," and the GC would completely ignore data created through the Allocator.

    Let's explore 3 implementation paths for this using Go as an example:

    1. Pre-allocate empty buffers, serialize data into them upon creation, and zero them out upon deletion, then reuse them as the program runs.

    Here's an example implementation of this behavior using a Slab allocator: https://github.com/couchbase/go-slab

    Pros – it works. Cons – (1) controlling memory is harder than with the other two methods, (2) the GC will still re-check these objects (though it will consume fewer resources), (3) serialization/deserialization consumes resources.

    1. Use C bindings and allocate/deallocate memory through them.

    One of the best articles + a link to a library on the topic: https://dgraph.io/blog/post/manual-memory-management-golang-jemalloc/

    Pros – it works. Cons – (1) complex typing, (2) potentially complex serialization/deserialization to and from Go structs.

    1. Experimental feature: ArenaAllocator

    https://uptrace.dev/blog/golang-memory-arena.htmlhttps://medium.com/@esimov/go-memory-arenas-1ba930bf79c1https://www.youtube.com/watch?v=eglMl21DJz0

    This is an almost ideal implementation, very similar to how it's done in Zig, and it works wonderfully there.

    BUT the problem is that it's an experimental feature, and the authors are still unsure whether to keep it or not 😢

    The authors' main point: "What if libraries start using this, thereby forcing users to use it?" – I agree with this, but still, this issue can be resolved with a compile-time flag: if the flag is present, use allocators; if not, don't.

    I believe the Allocator + GC approach is very optimal because it allows us to keep the language simple for standard tasks on one hand, while providing suitable tools for scenarios where we need to work with memory extensively.

    What do you say, should Go have Allocators?

  • @ [ $davids.sh ] · # 1546

    I'm particularly interested to hear who is against it, and most importantly, why.

  • @ Leijona · # 1547

    A very low-level language, for example?

  • @ [ $davids.sh ] · # 1548

    Do you mean using another language?

    I have encountered situations from time to time where the GC was lagging, and I wanted something faster without allocating it to a separate application.

    For example, a conditional, stateful cache tied to an instance.

    If you write in another language, you'd either have to write a transport layer or use FFI.

    Then any reasonable person would say, "use a ready-made solution" - but for a simple, yet memory-intensive task, I don't want to add anything else to the system (in the form of a separate instance or language).

  • @ Leijona · # 1549

    Damn, I read the post without context (without the post above)