Live data from Hacker News

Taming Go’s memory usage, or how we avoided rewriting our client in Rust

akitasoftware.com

201–210 of 231 posts

Re: Taming Go’s memory usage, or how we avoided rewriting our client in Rust

#201
post #176
post #80

Earlier quoted context omitted.

I have trouble believing this, at least in any generalizable way. I'm comfortable in both Go and Rust at this point (my Rust has gotten better since last year when I was griping about it on HN), and it's simply the case that I have to think more carefully about things in Rust because Go takes care of them for me. It's not a "think more carefully and you're rewarded with a program that runs more reliably and so you ma…

I don't do much Go, so I can't really compare it with Rust all that well, but I think it's a plausible result. To take two GC'd languages, I'm proficient in both Java and Scala. It usually takes me a little longer to write something in Scala, but when I'm done, I've almost certainly written fewer bugs in the Scala program than the Java program (I've also written many fewer lines of code, but that's another topic). Fo…

> I personally value greater chances of correctness at compile time way more than development speed

In my experience, I don’t get much additional correctness for the extra effort, but rather I get independence from the GC, which is worth much less to me.

If we’re optimizing for correctness alone, I think development times could improve significantly by swapping the borrow checker for a gc. I know the borrow checker aids in correctness beyond what a gc does, but IMO the returns diminish rapidly. And I’m not sure how well this would work in practice, but maybe you could keep the borrow checker and add a GC, with every reference type being gc by default (not sure if that would recoup any of the extra correctness that a borrow-checker affords or not).

Re: Taming Go’s memory usage, or how we avoided rewriting our client in Rust

#202
post #187
post #107

Earlier quoted context omitted.

It's a question I ask often in interview, how do you upload a 5GB file over the network with only 1MB of memory.

I am even not sure what this question is aiming at - I hope you are phrasing it more detailed than put here, or it would fit in those posts about the problems with interview questions :). Assuming the file is on a disk and the 1 MB refers to the system memory - like you do with any potentially unbound data, you read and write it in chunks. Reading in data of any kind in whole is only reasonable, if you can clearly se…

This is not vague, it aims to see if the candidate has a notion of buffering, streaming ect ... the numbers don't really matter, you would be surprised at how many candidate have no idea how to load data in memory.

Re: Taming Go’s memory usage, or how we avoided rewriting our client in Rust

#203
post #174
post #39

The big wins in this article, in what I believe was the order of impact: * They do raw packet reassembly using gopacket, and gopacket keeps TCP reassembly buffers that can grow without bound when you miss a TCP segment. They capped the buffers, and the huge 5G spikes went away. * They were reading whole buffers into memory before handing them off to YAML and JSON parsers. They passed readers instead. * They were usin…

>Memory usage was going to be fiddly no matter what they built with. That is true. I do find however the explicitness of the Rust way of dealing with memory, whether it be lifetimes, who can and can't mutate it and who the memory belongs to, makes it much easier to reason about the right way of doing these things. In C++ the same is often possible, but there is no way to have guarantees at the interfaces. Const is a…

Rust and Go are equivalently explicit about the memory concerns surfaced in this analysis.

Re: Taming Go’s memory usage, or how we avoided rewriting our client in Rust

#204
post #176

Earlier quoted context omitted.

I don't do much Go, so I can't really compare it with Rust all that well, but I think it's a plausible result. To take two GC'd languages, I'm proficient in both Java and Scala. It usually takes me a little longer to write something in Scala, but when I'm done, I've almost certainly written fewer bugs in the Scala program than the Java program (I've also written many fewer lines of code, but that's another topic). Fo…

> I personally value greater chances of correctness at compile time way more than development speed In my experience, I don’t get much additional correctness for the extra effort, but rather I get independence from the GC, which is worth much less to me. If we’re optimizing for correctness alone, I think development times could improve significantly by swapping the borrow checker for a gc. I know the borrow checker a…

Things like the borrower checker helps manage all resources, a GC only manages memory.

Re: Taming Go’s memory usage, or how we avoided rewriting our client in Rust

#205

Earlier quoted context omitted.

> I personally value greater chances of correctness at compile time way more than development speed In my experience, I don’t get much additional correctness for the extra effort, but rather I get independence from the GC, which is worth much less to me. If we’re optimizing for correctness alone, I think development times could improve significantly by swapping the borrow checker for a gc. I know the borrow checker a…

Things like the borrower checker helps manage all resources, a GC only manages memory.

Or rather RAII, borrow checker is a different feature that is not related to garbage collection

Re: Taming Go’s memory usage, or how we avoided rewriting our client in Rust

#206
post #188

Earlier quoted context omitted.

Yes, Rust kinda doesn't fit super cleanly into a very black/white binary here. It is automatic in the sense that you do not generally call malloc/free. The compiler handles this for you. At the same time, you have a lot more control than you do in a language with a GC, and so to some people, it feels more manual. It's also like, a perception thing in some sense. Imagine someone writes some code. They get a compiler e…

> At the same time, you have a lot more control than you do in a language with a GC Are there some examples of that?

Two examples: LockGuard and Box.

Control is primarily exerted over consumers of your API rather than the actual resources. This can be enforced through a combination of Drop implementations, and closures / lifetimes; the classic example is Mutex's LockGuard. In a GC language (eg Go) they give you defer or finally blocks that can accomplish the same thing, but that is always optional and up to other programmers to remember to do. Compare: you can't typically make someone run destructors in a GC language; you also wouldn't be able to guarantee the destructors have run at any particular point in time.

The one area you have more control over actual resources is knowing when memory is freed. Some people need to know when memory is freed, because they have allocated a lot and if they do it again without freeing, they'll run into trouble. To know for sure, simply use a normal owned type or a unique pointer (Box); when it goes out of scope, that's when its destructor is run. No such feature exists in a GC language, because you can never know at compile time when nobody else holds a reference.

As a thought experiment: in JavaScript with WebAssembly, an allocation in WASM can be returned to JS as a pointer. You need to free it, somehow. Can you write a class that will deallocate a WASM allocation it owns when an instance of the class is freed by the JS GC? (Answer: no! You need a new language-provided FinalizationRegistry for that.)

Re: Taming Go’s memory usage, or how we avoided rewriting our client in Rust

#207
post #188

Earlier quoted context omitted.

> At the same time, you have a lot more control than you do in a language with a GC Are there some examples of that?

Two examples: LockGuard and Box. Control is primarily exerted over consumers of your API rather than the actual resources. This can be enforced through a combination of Drop implementations, and closures / lifetimes; the classic example is Mutex's LockGuard. In a GC language (eg Go) they give you defer or finally blocks that can accomplish the same thing, but that is always optional and up to other programmers to rem…

Ah, so it's more about library writer control then about library consumer control? Since for example in Common Lisp, the latter can still be accomplished through declarations, such as DYNAMIC-EXTENT (http://clhs.lisp.se/Body/d_dynami.htm). (Not sure if the former is necessarily related to memory usage control, but you'd probably achieve that type of resource control by exposing only WITH-* macros in your API.)

Maybe D people would have something to say about this as well, but I'm not a D person. What you're describing doesn't seem impossible in D to me, though.

Re: Taming Go’s memory usage, or how we avoided rewriting our client in Rust

#208

Earlier quoted context omitted.

> I personally value greater chances of correctness at compile time way more than development speed In my experience, I don’t get much additional correctness for the extra effort, but rather I get independence from the GC, which is worth much less to me. If we’re optimizing for correctness alone, I think development times could improve significantly by swapping the borrow checker for a gc. I know the borrow checker a…

Things like the borrower checker helps manage all resources, a GC only manages memory.

Yes, I understand. To quote myself:

> I know the borrow checker aids in correctness beyond what a gc does

Re: Taming Go’s memory usage, or how we avoided rewriting our client in Rust

#209
post #32

Earlier quoted context omitted.

More importantly, GC'ed languages tend to use at least 2x the memory of un-GC'ed languages and have to deal with the consequences of GC-induced pauses and generally inferior native code interop. Whether that matters to you or not depends on your application. No one is going to use a GC'ed language in the Linux Kernel, but practically 100% of backend applications are written in GC'ed languages because the productivity…

I’m not really sure if that 2x figure is accurate. I’ve seen charts on both sides of this and a lot here depends on your programming language and the things it can optimize: with Linear/Affine types, I’m fairly sure Haskell could, in theory, eliminate GC deterministically from the critical sections of your code-base without forcing you to adopt manual memory management universally. But, there’s just the fact that peo…

> … tasks like implementing lock-free hash maps…

Please be specific.

You pointed to spectral-norm, what does that have to do with lock-free hash maps?

The 2.java program seems to be 4x slower than the 7.rs program !

Re: Taming Go’s memory usage, or how we avoided rewriting our client in Rust

#210
post #177

Earlier quoted context omitted.

True, but even then you only have to decide between passing a copy of the value or a reference to it, no need to think about ownership.

Kind of, if it is a struct with destructors you need to ensure a region exists. So either do something like using MyStructType something = new MyStructType () Or a more FP like stuff with myVar.WithXYZResource(res => { /* .... */ }) And then consider if it should be a ref struct, so that is only stack allocated. This from C# point of view, in something like D, there would be another set of considerations. Still much…

Well, destructors come up if you have non-memory resources to manage, and there you do go back to ownership and deterministic destruction issues.
Post reply on HN