eatonphil2 days ago
Hey folks, this is my project. I really liked OxCaml's zero_alloc feature [0], something you don't get in Rust or Zig or Go*, and have been trying to get a handle on allocations in The Consensus's code search engine.
It's an extremely early project but it's been helping me out a bunch already. Would love to hear your bug reports or requests if you try running it on your codebase.
[0] https://theconsensus.dev/p/2026/06/27/the-feature-in-oxcaml-...
* I think you can get this in Clang and maybe GCC with `__attribute__((nonallocating))` though.
MrBuddyCasino2 days ago
Rust has a mode where no allocator is available.
eatonphil2 days ago
No allocator available isn't the same as being able to enforce no allocations.
dlahoda2 days ago
that option in rust allows to set own allocator, which may be disabled after initial allocation, may be per thread.
andrewla2 days ago
The end state of every garbage collected language is a suite of tools to prevent garbage collection at all costs.
I think we can learn something here.
vatsachak2 days ago
I think that people hyperoptimize for no reason though. How many applications are there that require no GC? I feel like you can write the no-gc code in Rust and then just use it from the C ABI from any GC language.
ted_dunning2 days ago
Embedded systems benefit substantially from decreased GC, especially in the context of non-relocating collectors (as with Go) in machines without memory management units. The problem is not the overhead, it is the effect of memory fragmentation.
The OP article here is nice because it allows selective enforcement of allocation free code. If you have code that has some discipline about heap allocation such as having all small allocations then you are OK on the fragmentation front.
bootsmann2 days ago
Isn’t writing Go for embedded systems kinda like eating soup with a fork tho? This language was designed to write multithreaded asynchronous code to power microservice meshes.
Groxx2 days ago
If it's within like 90% of your targets and it's a far easier development experience, but you need to optimize a small bit to fit in the budget, tools like this can be fantastically useful. Rewriting the whole thing in, like, C, complete with a coroutine stack, would be a hell of a lot more work than "avoid allocations here".
pjmlp2 days ago
There are commercial products using Go in embedded systems already, via TinyGo and TamaGo compilers.
Even ESP32 is much better than any MS-DOS PC, we have to update our understanding of embedded platforms, not everyone is writing Assembly or C dialects to PIC like microcontrollers.
pjmlp2 days ago
Real time Java is doing just fine with PTC and Aicas still in business.
fpoling2 days ago
GC in Java is the best in world with a lot options and knobs that allows to tune to particular application requirements. Still some trading platforms using it disable GC and just ensure enough memory is available so memory leaks during the trading hours would not matter before the application will be restarted.
pjmlp2 days ago
Just like plenty of C and C++ deployment scenarios have special purpose memory allocators, or forbid memory allocations other than at startup.
Yet one still needs to account with processes like MISRA to account for safety.
fpoling2 days ago
I worked on a project with a network server written in Go with soft realtime requirements. In theory Go should be very good fit for it especially given GC tuning towards low latency, not throughoutput. In practice the latency spikes from GC were bad and there were no good options to resolve those given the deployment constrains and the amount of third-party libraries used.
treyd2 days ago
This is a viable approach for many scenarios. Uniffi is a tool/library from Mozilla that makes this a lot easier across platforms.
After a few rounds of this though it might end up seeming like it's not worth the hassle and it's easier to build the whole application in Rust.
Mawr2 days ago
Switching out the entire ecosystem, libraries, tooling; rewriting the entire codebase; learning the new language and its idioms; and then giving up the unknown benefits of the previous language that the new language lacks (for Go->Rust this could e.g. be compile times)—generally, trading known downsides of a language for unknown downsides of another—is a huge undertaking and therefore very unlikely to ever be worth the hassle.
vatsachak2 days ago
Lifetime and smart pointer hell is real though.
treyd2 days ago
Once you internalize it it's really not a problem. That's why I said "after a few rounds of this".
vatsachak2 days ago
I've been writing Rust for a year and change professionally, also as a hobby, and even though I love it, it's really annoying to choose the allocation model and lifetime model at every step.
For example,
- A field in a struct cannot borrow another field so now you'd have to create two structs. I guess you could write an impl to borrow that field instead of storing the borrow.
- In simulation software I wrote I found that mimalloc does just as good as bumpalo (within 5%) so carrying a bump everywhere was useless. Not really a rust issue but rather a loss of faith in manual memory management for me.
- Eventually over time you start adding Arc<Fn(Blah)->Foo> everywhere and Box, Arc and friends start coming in. But you can't have cyclical references there so you have to use Weak. And you have to use OnceCell or LazyCell or LazyLock for things that should run at start time.
Rust is really great for the crates, but ...
For applications which are not absolutely performance critical and cannot* be stated pretty easily in linear logic + clone + drop you can get away with Haskell
hoppp2 days ago
I agree. If the use-case requires no GC then Rust or Zig or C++ is a better fit.
I don't see much point in choosing a language just to bypass it's features.
cpgxiii2 days ago
Coming from an environment in which we extensively use mixed codebases (C++ and Python) to combine performance and ease of prototyping, one of the pain points is that there is almost always some impedance mismatch between the lifetime/ownership models of the two languages. When something goes wrong here, it's usually a real pain to debug and resolve, and often the resolution involves imposing constraints from one language onto the other (e.g. C++ that would idiomatically use unique_ptr instead needs to use shared_ptr because unique_ptr is essentially impossible to completely capture in Python).
Given the issues involved in mixing languages, there can be cases where sticking with one "good enough" GC'd language and having to write a few performance-sensitive parts in very unidiomatic form to minimize/eliminate GC work is preferable.
revengerwizard2 days ago
I feel like that may be more of an issue with how Python's C API is designed, leaking basically most of its internals, and then having to remember to manage (increment/decrement) Python objects correctly.
cpgxiii2 days ago
I don't think that's the problem, at least not directly for us. The immediate interface between the languages is handled by a binding layer (e.g. pybind11 or nanobind), but that rarely lets you fully express object relationships (something that would be more possible without the binding layer) so it is very easy to either not capture ownership (and thus the C++ object is destroyed out from underneath you) or express a cycle that cpython can't understand (and thus the C++ objects are never destroyed and can leak a significant amount of memory). Explicit manual control would avoid that, but at a level of work that would be prohibitive of having extensive bindings in the first place.
eptcyka2 days ago
Go really sucks at calling into C code.
pjmlp2 days ago
Nah, for all Go design decisions that I usually complain about, CGO is actually rather nice.
It feels quite similar to using C++ on .NET via C++/CLI.
There is some cargo cult against CGO, I could not care less.
eptcyka15 hours ago
Calling C functions is not free, compilation becomes a lot more difficult, Go tooling is not as helpful. Signal handling is still a mess. It works in the happy path where the FFI is not on the hot path.
pjmlp14 hours ago
I think it is more a skill issue than anything else.
Yes, marshaling has a visible impact, hence why one should think how to actually make it happen, not much different from using FFI in any other programming language, there is always an impact over staying in the same programming language.
Compilation being harder, only if the developer is not acquainted with C, which seems to be something harder to learn nowadays.
Signal handling is a mess even in pure C, especially since all UNIX implementations added threading support.
eptcyka12 hours ago
Go makes signal handling harder. Further, there is no FFI call overhead in Rust, C++, Swift. It isn’t marshalling, a function with no args will still incur an overhead.
pjmlp5 hours ago
Because Rust, C++ don't have a GC, while Swift does have a GC (RC is a GC algorithm) it was designed to be compatible with Objective-C runtime, thus C.
80fj0348ia day ago
It's fine if you're only targeting one OS with maybe a small handful of architectures. Doing anything cross platform is terrible.
pjmlp19 hours ago
I thought C was a cross platform language.
80fj0348i13 hours ago
Maybe the syntax is, but setting up the compiler and build system and linkers is very much not, especially for cross-compilation.
One of Go's major selling points is cross-compilation to other OSes and architectures is incredibly simple and fast, and _the same short build command works across every OS._
I maintain a CGO app that targets Windows macOS and multiple Linuxes and oh boy the build system is probably more complex than the app itself at this point.
pjmlp4 hours ago
I started coding with Timex 2068, developed for embedded in a past life, thus I fail to see how complicated doing a simple cross compiler in C happens to be.
I also have developed C and C++ native libraries for Tcl, Python, .NET, Java JVM/ART (JNI), Node.js.
bayindirh2 days ago
I don't think so. People love to see how far they can go in some aspect sometimes. If I had the same amount of free time 5 years ago, I'd have tried the same thing with Go, because I like the language, and love the act of programming and seeing things.
Also, I also agree that people optimize without proper testing and profiling. If there's a real bottleneck here, trying to optimize it is the right call. OTOH, optimizing without profiling opens a lot of unnecessary cans.
Lastly, Go doesn't like constant allocation and deallocation of variables, that's true, but I believe knowing this and optimizing hot-loops for that goes a very long way as a start.
kvemkon2 days ago
Similar with malloc in C / new in C++, especially for short-living, repeating, in hot path. And then you hear: avoid allocations, use constant-size buffers or memory pools. Avoid spawning threads, use thread pools.
iguessthislldo2 days ago
That observation checks out. About a decade ago, one of my former coworkers areas of interests was making Java applications and frameworks that didn't use the any additional heap beyond app initialization. This was apparently for embedded/low power devices. It was neat to hear about as a C++ dev, but I'm not sure how much of a business or technical advantage it had beyond that.
pjmlp2 days ago
Job pool, memory safety and lower development costs versus C++ tooling.
"Why we chose Java for our High-Frequency Trading application"
https://medium.com/@jadsarmo/why-we-chose-java-for-our-high-...
jcelerier2 days ago
i'm developing apps that do real-time audio and video and any allocation or anything that may end up in a system call directly or indirectly (for instance accessing swapped or mapped but not yet loaded memory) on the hot path is a complete no-go and will instantly cause very audible glitches for audio, and visual frame pacing issues
pjmlp2 days ago
This DAW uses a mix of Java and C++ for good effect, so at least some people are rather happy with the approach
jcelerier2 days ago
AFAIK all the core of bitwig (e.g. what has the hot path) is a separate native process with no java in it, communicating with shmem or sockets with the java gui
pjmlp2 days ago
I have not said otherwise, "This DAW uses a mix of Java and C++ for good effect".
The point being that it does not need to be a black and white approach, one language to rule them all, as many tend to advocate for.
prophet_margin2 days ago
The big one that comes to mind is MISRA.
legulere2 days ago
Zero allocation is also a thing in rust. What you can learn is to reduce allocations in your critical paths.
kristianp2 days ago
That some people think in black and white?
pjmlp2 days ago
Anti GC folks forget that these workarounds are only required in GC languages that don't offer explicit language features for stack and native heap allocations, like Cedar, Modula-3, all Oberon variants, C#, F#, D, Swift,..
Like everything else in Go, its designers have decided to ignore learnings in GC systems languages.
flavio81a day ago
>The end state of every garbage collected language is a suite of tools to prevent garbage collection at all costs. >I think we can learn something here.
You're making two mistakes here:
1. Confusing "no allocation" with "avoiding the garbage collector". In non-GC languages like Rust or C there are instances you want to avoid allocation too.
2. Being oblivious that in the past many GC'd languages also had mechanisms for circunventing the garbage collector. Since long, long, long ago. Common Lisp for example (1981-). This is not a modern "realization" or "revelation".
robotresearchera day ago
The parent did not make either of these mistakes.
Gys2 days ago
Maybe. Go code is designed to be readable and stable. My guess is that most Go code is running fine with garbage collection. There are always edge cases and then just choose another language.
raggi2 days ago
The end state of inappropriate use of garbage collected languages.
Go/GC are mostly fine for medium frequency medium scale mid stack problems.
It’s bad for high frequency computing. It’s bad for very small targets, for very large targets, and by association software that lives at the very bottom of the stack.
Go is a high efficiency framework for writing mid sized servers, not a systems language. The runtimes hard opinions and closed interface are the reason for this enclosing definition and the GC is one part of that.
gritzko2 days ago
If you don't allocate, why do you even need Go or Rust? LLMs handle less elegant syntax just as well. I use a zero-malloc C dialect with slices and bounds-checking.
Just recently I was sketching a smallish database in zero-malloc C. https://replicated.live/blog/db That was partly because most LSM data structures are read-only, so mmap was good enough and concurrent access was safe for the bulk of the data.
The "hot zone" (memtable) then requires some intricate engineering, but that is the case either way.
You can go quite far with zero-malloc C.
80fj0348ia day ago
It can be useful to have zero-alloc libraries in Go, for use in larger programs that do allocate. See https://github.com/rs/zerolog for example.
steveklabnik2 days ago
Rust doesn't have heap allocation in the language at all. It still has useful features regardless.
gritzko2 days ago
Technically, malloc() is a library function, not core C. My point is this: guarantees cost complexity, and you always have to keep in mind the assumptions and how much of it is theater. So, neither way is strictly better. It depends. For immutable data, lifetimes don't buy much, for example.
steveklabnika day ago
Yeah, I'm not saying C does, just that Rust's features aren't limited to "helping you with heap memory." Rust is also more than lifetimes.
kvemkon2 days ago
Go compiler has an advanced escaping analyzer. Wouldn't it be easier and better to add this noalloc-analyzer feature to golang directly?
eatonphil2 days ago
I agree this isn't the absolute ideal form. I'd rather it be built in as a language directive or something. noalloc does parse compiler escapes emitted by the Go compiler with `-gcflags=-m -S`. One problem is that these are not really a standard format. But I hope that with testing across versions (over time anyway, I only run this on Go 1.27) it'll be stable enough.
kvemkon2 days ago
Thanks for clarification! I'm glad, that you're using the golang compiler capabilities. And now I see:
https://github.com/theconsensuslabs/noalloc#the-compilers-ou...
Gibbon12 days ago
Maybe having a way to enforce that on the compiler. So you can force the compiler to tell you it couldn't eliminate heap allocations.
pjmlp2 days ago
Basically you can run the Go compiler with the escape analysis output flags, and have a script process its output, breaking the build if there are too many of them, or whatever condition you feel like.
Don't folks brag about UNIX philosophy all the time around here?
Here is a good scenario for some pipelining with Make, awk, sed, perl and co.
Gibbon1a day ago
I'm reminded of tail call recursion in C.
Compiler noid: We added tail call recursion. Is super you should use it!
Me: Can I guarantee it will actually do tail call recursion?
Compiler Noid: If course not!
Me: Nope.
raggi2 days ago
Historically that was rejected.
Tides have shifted somewhat, but I suspect this may still be rejected.
Unfortunately a lot of people’s experience with such proposals was also historically getting gaslit by a brigade, so a lot of folks moved on and won’t bother trying again.
ted_dunning2 days ago
The Go community has not been very welcoming for embedded applications, for sure.
That might be changing (slightly) because of the Tamago work on NOOS.
wahern2 days ago
Goroutines and lexical closures create a dilemma for embedded targets. Go was designed around the notion that using goroutines (threading, basically) should be effortless and employed without hesititation. Lexical closures is a critical aspect of that, and by extension GC. Both goroutines and lexical closures require dynamic allocation, at least when combined with some other Go design decisions (e.g. seamless recursion, dynamic method invocation, etc).
Go does a great job of making these impose minimal cost, both technically and cognitively, on typical platforms, from server (including many "embedded" server environments) to desktop to smartphone. That's why it has such a sophisticated yet largely transparent user space threading layer. But it's very difficult and usually impractical for Go to target extremely resource constrained environments without exposing and crippling it's core abstractions. Some people are willing to contort Go to fit that space, but perhaps that's because their vision of what makes Go, Go, differs from the creators' and core maintainers' understanding of Go.
kristianp2 days ago
Interesting difference between Go and Python, `c.counts[name] += n` can allocate if name isn't already present, whereas in Python you get a key error.
NewJazz2 days ago
Isn't that mainly because Go has a somewhat insane behavior of default values?
Also I'm fairly sure you can accomplish the same thing in python with a defaultdict.
[deleted]2 days agocollapsed
zinodaur2 days ago
I love it. Any thoughts on allowing (value type) Pointers into a Region?