12 ms·
I work in HFT, and one of the key concerns when writing low-latency code is "is this code allocating memory, and if so, how can I stop it?" Zig is the perfect l
by logicchains 6y ago
I work in HFT, and one of the key concerns when writing low-latency code is "is this code allocating memory, and if so, how can I stop it?" Zig is the perfect language for this use case as none of the standard library implicitly allocates, rather for anything that allocates, the caller must pass in an allocator. The stdlib also provides a handy arena allocator, which is often the best choice.
This is a huge advantage over C++ and Rust, because it makes it much harder for e.g. the intern to write code that repeatedly creates a vector or dynamically allocated string in a loop. Or to use something like std::unordered_map or std::deque that allocates wantonly.
- AsyncAwait 6y ago> This is a huge advantage over C++ and Rust, because it makes it much harder for e.g. the intern to write code that repeatedly creates a vector or dynamically allocated string in a loop. Or to use something like std::unordered_map or std::deque that allocates wantonly. True. On the other hand, Zig makes a deliberate decision not to bother itself with memory safety too much, so its a win some, loose some sort of situation.
- pron 6y ago> On the other hand, Zig makes a deliberate decision not to bother itself with memory safety too much This is not true. Zig places a strong emphasis on memory safety, it just does so in a way that's very different from either Java's or Rust's. I wrote more about this here: https://news.ycombinator.com/item?id=24293329 https://news.ycombinator.com/item?id=24293329
- littlestymaar 6y ago> It does so with runtime checks that are turned on in development and testing and turned off -- either globally or per code unit -- in production. This isn't “memory safety”, with this reasoning you could say “C is memory safe if you use ASAN during debug”: it is exactly equivalent except Zig checks are less powerful than the full suite of existing sanitizers for C, but it's enabled by default in debug mode, which is nice.
- pron 6y agoNo, this is full memory safety enforced through runtime checks. ASAN does not give you that. Zig has only arrays and slices with known sizes and no pointer arithmetic (unless you explicitly appeal to unsafe operations).
- littlestymaar 6y ago> full memory safety enforced through runtime checks [disabled in production] Which means you cannot guaranty your program doesn't exhibit memory unsafely unless you stumble upon it during testing. Yes there are fewer footguns in Zig[1] than in C (which is the opposite of C++), but dangling pointer dereferences, double free and race conditions will still be lurking in any reasonably-sized codebase. Calling it “memory-safe”, is dishonest. And I actually don't think it serves Zig. It's a clever language with tons of good ideas, no need to oversell it with misleading claims about memory safety. [1]: but still plenty of them https://ziglang.org/documentation/master/#Undefined-Behavior https://ziglang.org/documentation/master/#Undefined-Behavior
- pron 6y ago> Which means you cannot guaranty your program doesn't exhibit memory unsafely unless you stumble upon it during testing. Right, if you disable those checks. > Calling it “memory-safe”, is dishonest. The language is (or will be) memory safe, at least its safe parts -- C/C++ aren't. True, that safety can be disabled selectively. Yes, there can then be undefined behaviour if you disable that safety, but so can Rust when you disable safety selectively. The design is just different. Just remember that using a language with sound guarantees is no one's goal. The goal is to produce programs without bugs. Sound guarantees in the language is one approach toward that goal; there are others. Generally, guaranteeing that some set of bugs cannot happen does not necessarily guarantee fewer bugs overall; in fact the opposite might be true. Nobody knows if Zig's strong safety story yields to more correct programs that Rust's, but nobody knows the opposite, either. There are good arguments either way but little data. In any event, Zig is much safer than C, even with ASAN.
- AsyncAwait 6y agoI meant a borrow checker, which I assumed was clear from what I was replying to. Yes, Zig does do runtime checks in dev builds and I did not mean to imply otherwise, I don't think the runtime checks provide the same set of benefits. Just so we're clear, I like Zig :-)
- pron 6y ago> I don't think the runtime checks provide the same set of benefits. Of course not, but that doesn't mean Zig is less effective at achieving correctness. It just does so in a different way -- it trades sound guarantees for less sound ones and a simpler language with a faster cycle. Is one better than the other? Hard to say. Only empirical study could settle that.
- fsociety 6y agoI’d argue it does mean it is less effective at achieving correctness but the trade-off made is the whole point of Zig. Simple language that is really a better C.
- pron 6y agoIt's hard to make a definitive argument one way or the other because the guarantees Rust makes come at a cost of compilation speed and language complexity that can have a negative effect on correctness. This question is impossible to answer without an empirical study. Unlike C, Zig is (or will be) memory safe, although its safety can be turned off, and often is -- after testing. Unlike C, it provides powerful abstraction capabilities similar to those of C++. The fact that it can do all that yet be very simple seems to suggest at first glance that it's "like C" but that's because we've never had a language like that before. Zig's simplicity is misleading. It turns out you can do a lot with a very simple language. We knew that to be true for high-level languages like Scheme, but Zig shows it's possible in low-level languages, too.
- AsyncAwait 6y ago> It's hard to make a definitive argument one way or the other because the guarantees Rust makes come at a cost of compilation speed I don't think this is actually true. The compiler is slow but not due to memory safety; the 'cargo check' command is rather quick and the compiler itself doesn't seem to spend a lot of time in the frontend, most of the time in spent in the backend, past the borrow checking phrase.
- petr_tik 6y agohow often does it happen that your interns work on the hot path of your trading systems, which is where I assume you care the most about avoid syscalls like malloc?
- cycloptic 6y agoNit pick: On modern systems malloc isn't a syscall, it's implemented in userspace. (Sorry, I couldn't help it) That's not to say you're safe to call other syscalls, many of them either require memory allocations in-kernel (see ENOMEM) or can block indefinitely.
- fanf2 6y agoNever mind modern systems, malloc() was never a syscall :-) One of the great things about K&R is that it shows you how to implement parts of the C library, including a simple malloc(), demonstrating that the library does not need to be magical.
- petr_tik 6y agoThanks you and your parent for pointing this out! I should be more precise, sbrk is the underlying system call that might be invoked inside malloc
- voldacar 6y agommap usually these days
- fanf2 6y agoYes, and often mmap() for large allocations and other parts of the heap. There has been an interesting discussion about memory management in Ritchie’s PDP11 C compiler on the TUHS list this month https://minnie.tuhs.org/pipermail/tuhs/2020-August/thread.html https://minnie.tuhs.org/pipermail/tuhs/2020-August/thread.ht... from the era when large programs could not necessarily afford the overhead of malloc() so sometimes used sbrk() directly.
- voldacar 6y agoYeah when I heard about this I instantly thought of game engines, but it makes total sense for HFT too. "Modern C++", with all its constant little mallocs and frees is so awful for anything that requires ultra low latency
- cycloptic 6y agoCan you explain how this is a problem in modern C++? I was under the impression that all the STL containers (string, vector, list, map, etc.) worked the same and have an allocator parameter. Are there other areas where these are missing? Or is the issue that STL implementations almost always default to an allocator that uses malloc? I'm not trying to dog on Zig here (it's a nice little language) but this just doesn't seem to be something that only Zig can do.
- logicchains 6y agoA concrete example is std::stable_sort. As far as I'm aware there's no way to pass it a custom allocator/buffer to avoid it allocating memory.
- cycloptic 6y agoThank you for the example.
- voldacar 6y agoIt isn't really a matter of can do/cannot do. It's more about the default patterns promoted by the idiomatic way of writing code. Yeah you could write C++ code that constantly passes around allocators while also using STL heavily, but it will be verbose, unnatural, and ugly. As well as having nicer syntax in general and real metaprogramming instead of the brain damage that is templates, zig promotes this kind of allocator-aware programming style in a way that's clean and idiomatic
- cycloptic 6y agoI'm not sure what you mean that it's verbose, unnatural and ugly. To me it looks the same. In C++, you have to pass around an allocator to your templates. You can typedef this away if you want. In Zig, you have to pass around an allocator as a function argument or a struct member. You can comptime this away if you want. Is there some fundamental way that I missed that Zig changes this? If your actual complaint is that C++ templates are bad and you're saying Zig comptime is better, that's different than having woes about allocators.
- littlestymaar 6y ago[deleted]
- deleted 6y ago[deleted]
- logicchains 6y agoI didn't say I actually use it, I said it's the perfect language for that use case. In practice there are many other factors that determine whether a language is adopted, e.g. library availability, compiler maturity, and the fact that it's not simple to integrate a new language into an existing codebase of very many lines of C++.
- littlestymaar 6y agoThanks, I was very afraid for a moment. In fact, I recently worked for a company which started using Rust in a mission critical setting back in… 2013 (yes, long before the language was stable and its future secured). Fortunately it worked, but still, it was far from a safe move.
- The_rationalist 6y agoWith retrospective, do you regret choosing rust? It's poor ecosystem with low human resources can kill a startup.
- littlestymaar 6y agoI didn't make the pick (and I wouldn't have picked Rust then, I still judge this move as way too risky), and I merely worked as a contractor there and the project was already 5 years old. That being said, a few notes on Rust on this project: Cons: - finding people proficient in Rust was a challenge (but that's why I go hired, so for me that was a plus;). - in the first few years of the project, keeping up with language changes (before Rust 1.0 and even after because the project had been using nightly Rust until 2018) added. Neural: - the library ecosystem was nonexistent at the beginning, but because Rust has good C interop, the project just used C libraries for different things. Some where replaced by pure Rust ones later on, some didn't. Pro: (Note: the project had important performance requirements (regarding CPU and memory consumption) so had Rust not been chosen, the project would have been written in C++.) - When your Rust program compiles, it never crashes (except on an assert) - I've spent exactly 0 minutes in a debugger on that project - I've done massive refactoring without issues: you just fix the compiler errors (which are now extraordinarily clear!), you recompile and it works. So overall, the Rust bet was a big success for this project! But you're right: the company wasn't a start-up and the company's ability to count on an existing team was vital here because hiring a new Rust dev would have been impossible in the first few years. With Rust becoming more and more popular each year, the hiring issue shouldn't be as acute right now (well, especially since Mozilla fired dozens of Rust-fluent developers earlier this month…)
- vmchale 6y agoLinear types would be even better though. Still safe like Rust.
- klodolph 6y agoI also wish that Rust had linear types, it would make a lot of FFI easier without resorting to unsafe{}. With linear types you can guarantee that a destructor is run, so you can create objects with lifetimes that are not only bounded from below, but bounded by above. There are some common patterns in e.g. C libraries that rely on this--for example, you might register a callback on an object, and you want to assert that the object's lifetime is shorter than the callback's (so the callback is never called outside its lifetime). Since Rust doesn't have linear types, you have to use unsafe{}.
- nextaccountic 6y agoWhat about making the object borrow from the closure? This can be accomplished by a method that consumes the object and returns the closure (which now owns the object), and a closure method's that borrows the object back from it.
- samatman 6y agoI'm not 100% confident I follow, but this sounds like one of those backflips Rust programmers do to satisfy the borrow checker. As in, you wouldn't write the code this way if you didn't have to. You do get memory safety in return... but you can see where the desire for a more eloquent approach might arise.
- gautamcgoel 6y agoFor a moment I thought Sam Altman was spending his time reading random HN forums and commenting on the intricacies of Rust coding. Took me a minute to catch the missing "l" in your handle ;)
- 6y ago
- skocznymroczny 6y agoD uses a garbage collector by default, but it has a @nogc annotation to mark blocks of code that get statically verified not to allocate through the garbage collector.
- logicchains 6y agoIs there any way to mark "this code does not call malloc"? Or maybe more generally, "this code does not use anything from libc"?
- egnehots 6y agoRust supports a global custom allocator. Per container allocators are on the roadmap : https://github.com/rust-lang/rfcs/blob/master/text/1398-kinds-of-allocators.md https://github.com/rust-lang/rfcs/blob/master/text/1398-kind...
- the_duke 6y agoThere is some ongoing work towards custom allocators for containers in Rust std. [1] Right now you could also go no_std (you still get the core library, which does not contain any allocating data structures) and use custom containers with a passed in allocator. Zig is definitely a cool language, and it will be interesting if they can come up with good solutions to memory management! But sentences like these in the documentation [2] would make me prefer Rust for most low level domains (for now): > It is the Zig programmer's responsibility to ensure that a pointer is not accessed when the memory pointed to is no longer available. Note that a slice is a form of pointer, in that it references other memory. ... > ... the documentation for the function should explain who "owns" the pointer [1] https://github.com/rust-lang/wg-allocators https://github.com/rust-lang/wg-allocators [2] https://ziglang.org/documentation/master/#toc-Lifetime-and-Ownership https://ziglang.org/documentation/master/#toc-Lifetime-and-O...
- pron 6y ago> But sentences like these in the documentation [2] would make me prefer Rust for most low level domains While such use-after-free issues are not prevented at compile time, the plan is to ultimately have safe Zig catch them (and panic) at runtime, i.e. safe Zig shouldn't have undefined behaviour. Because this is done with runtime checks, the expectation is that those checks will be turned off (selectively, perhaps) in production, after testing has satisfied you. In that case, the guarantees aren't as strong as Rusts, but those guarantees come at a significant cost -- to language complexity and compilation time -- that can also have a negative effect on correctness. So while Zig's approach to safety/correctness is certainly very different from Rust's, I don't think it is necessarily weaker (perhaps it could even be stronger, but the question is hard to settle).
- defen 6y ago> In that case, the guarantees aren't as strong as Rusts, but those guarantees come at a significant cost -- to language complexity and compilation time -- that can also have a negative effect on correctness How would those guarantees have a negative effect on correctness? Are you thinking something like, you need to design your data structure / program in a non-intuitive way that makes it more difficult to get the logic right, even though you are protected from memory safety issues?