15 ms·
> It wastes time, it wastes space, it wastes energy. But all of these are much cheaper than developer labour and reputation damage caused by leaky/crashy softw
by davedx 8y ago
> It wastes time, it wastes space, it wastes energy.
But all of these are much cheaper than developer labour and reputation damage caused by leaky/crashy software. The economics make sense.
Anecdotally, I spent the first ~6 years of my career working with C++, and when I started using languages that did have GC, it made my job simpler and easier. I'm more productive and less stressed due to garbage collection. It's one less (significant) cognitive category for my brain to process.
Long live garbage collection!
- arcticbull 8y ago> But all of these are much cheaper than developer labour and reputation damage caused by leaky/crashy software. The economics make sense. Of course, with traditional languages, that's the trade-off we're being asked to make. That's my point! We need to develop languages that accurately encapsulate lifetimes statically so that we can express that to the compiler. If we do, the compiler can just make instances disappear statically when we're done with them -- not dynamically! Like Rust but without the need for Rc and Arc boxes. Rust gets us 80% of the way there. The truth is with most of the Rust I write, I don't have to worry about allocation and deallocation of objects, and it happens. It's almost ubiquitous. We need to extend that to 100%. > Long live garbage collection! Long live the rocket powered horse!
- cormacrelf 8y agoHow do you see multi-threaded anything working without Arc? Some owners (threads) are created and destroyed dynamically. Are you thinking Arcs get added wherever necessary?
- szemet 8y agoOr a multi-threaded app where plenty of dynamically created closures operates on persistent data structures? ;)
- arcticbull 8y agoOh to be clear I don't have an answer for this off-hand, I'm not that smart. However, if we'd given the 30 years of people developing faster for-loops the task of figuring out how to manage memory statically, I'd wager we'd not be having this conversation right now. It is by no means an easy problem, however.
- Hercuros 8y agoI think there are essentially two ways of making the compiler statically aware of lifetimes: you can annotate the program with lifetimes, as in Rust, or you can have the compiler infer all of the lifetime information somehow. Adding annotations requires more programmer effort and reduces the level of abstraction of your programming language. Rust takes this approach, but tries to keep the level of annotation somewhat reasonable, and provides the "unsafe" escape hatch for when you cannot convince the compiler that what you're doing makes sense. (Although I do believe that it would be possible for the Rust compiler to infer most/all lifetimes). For instance, in a functional programming language, you are typically creating closures all over the place, like one of the other posters mentioned. It seems quite unlikely that there is any kind of reasonable lifetime annotation system that would capture all of the complex lifetime patterns that you would see in such a language. One can say "Oh, but surely someone smarter than me must be able to come up with something that works for those situations", but I don't think that is a very constructive line of thought, since you could say that about pretty much everything. Rust is an example of a language where lifetimes work reasonably well, but Rust already makes some tradeoffs that reduce its level of abstraction to some degree. It doesn't provide the same level of abstraction that, say, Haskell does. Program analysis would allow you to keep your level of abstraction and ideally not require only little annotations. However, since any non-trivial program property is undecidable, program analysis is fundamentally incomplete and it is again unlikely that it would be able to deal with all lifetime patterns that you see in practice. Therefore, it seems to me that there is no free lunch here. Either you sacrifice abstraction by requiring annotations or you don't require any annotations but then you have an incomplete program analysis. (Also, you seem to think 30 years of good research has been wasted on the "obviously bad idea" of garbage collection, but somehow none of these researchers were good enough to realize this and come up with the silver bullet that makes GC unnecessary? After all, people were already thinking about GC-less languages long before Rust was a thing)
- jules 8y agoThere are solutions for that, but it requires a more powerful type system / logic than Rusts: http://www0.cs.ucl.ac.uk/staff/p.ohearn/papers/permissions_paper.pdf http://www0.cs.ucl.ac.uk/staff/p.ohearn/papers/permissions_p...
- jules 8y agoIt isn't clear that what you're asking for is even theoretically possible, and it's far from obvious that the end result would be comparable to GC in terms of productivity.
- bollu 8y agoThis only works if your language is strict. With a lazy language like Haskell, you can't statically infer (at least naively with respect to scoping rules) when something lives and dies. Performing this statically is hard, and GHC tries to do it (Demand analysis). But without a GC, these sorts of languages would be a no-go, as would logic-based languages like Prolog which need to GC their internal data structures. While we now have the expertise for eliminating the GC from single threaded strict programs (multi threading in Rust is still quite complex, and you do see Rc<Box<..>> more often than not in these settings, which is essentially refcounting/GC), this _does not scale_ to all models of languages.
- eru 8y agoGHC infers these kinds of things all the time. It can't do so in general, but neither can you do that in a static language. GHC even has a strictness analyzer.
- tempguy9999 8y ago> If we do, the compiler can just make instances disappear statically when we're done with them -- not dynamically! Not possible generally. It would be easy to create a situation where some kind of refcount is a necessary final fallback. > The truth is with most of the Rust I write, I don't have to worry about allocation and deallocation of objects, and it happens. If Rust is the answer, why are you pushing for new langs when Rust is sufficient? Something doesn't add up here. > Long live the rocket powered horse! Your response to someone giving their years of experience is that? Well, we're all convinced now.
- ascar 8y ago> If Rust is the answer, why are you pushing for new langs when Rust is sufficient? Something doesn't add up here. Maybe because he said, twice, that Rust is not all the way there?
- tempguy9999 8y agoYou're quite right, thank you, upvoted. However if he says "Rust gets us 80% of the way there" as you point out then he is obliged, IMO, to point out explicitly the 20% where it fails or we can't even begin to discuss it.
- DevoidSimo 8y agoHe mentions Rc and Arc which I assume is what he means by the 20%. I tend to agree, although I'm not sure how you would get rid of them.
- eru 8y ago>> If we do, the compiler can just make instances disappear statically when we're done with them -- not dynamically! > Not possible generally. It would be easy to create a situation where some kind of refcount is a necessary final fallback. Either refcount or a GC. But those situations might be rare in practice with a good system. If you think about it, generational garbage collection implements lots of those heuristics. (But it's closer to a JIT optimizer, not a static optimizer.)
- pedrocr 8y ago> Like Rust but without the need for Rc and Arc boxes. Rust gets us 80% of the way there. I think you're qualifying Rc/Arc as kludges to get around the type system. They may end up being used like that sometimes but when well used they are actually encoding extra semantics, just like &Type and &mut Type. A simple example is a cache get() method: pub fn get(&self, key: &K) -> Option<Arc<V>> https://docs.rs/multicache/0.5.0/multicache/struct.MultiCache.html#method.get https://docs.rs/multicache/0.5.0/multicache/struct.MultiCach... This allows you to get and use a cache value across your program and depending on outside input the value can disappear first in the cache by eviction or in the consumer. So Arc<V> is already expressing to the compiler the exact semantics. It just so happens that the actual lifetime is runtime defined from outside input so it must be managed dynamically. I don't see a general way around that without copying but maybe a lot more can be inferred automatically so you get similar ergonomics to a GC language by introducing Rc/Arc/etc automatically at compile time. I don't think that's a good fit for a system programming language like Rust though where you don't pay extra costs without asking for them explicitly. But maybe there's space for a higher level language that does all that magic and doesn't use a GC. Someone trademark Rustscript.
- mcguire 8y agoIn Rust, does the standard way of implementing a graph structure still use a vector of nodes and indices for edges? I think I saw a Rust implementation of an arena. Is it working?
- holy_city 8y ago>In Rust, does the standard way of implementing a graph structure still use a vector of nodes and indices for edges? You'd probably want to do this whenever you implement a graph in a performant language, it's faster and safer (depending on the problem). Implementing edges as vectors of pointers is an exercise in memory leaks and data races, imo. You can see an implementation with adjacency lists here: https://github.com/bluss/petgraph/blob/master/src/graph_impl/stable_graph/mod.rs#L103 https://github.com/bluss/petgraph/blob/master/src/graph_impl... >I think I saw a Rust implementation of an arena. Is it working? https://docs.rs/typed-arena/1.4.1/typed_arena/ https://docs.rs/typed-arena/1.4.1/typed_arena/
- geocar 8y ago> But all of these are much cheaper than developer labour and reputation damage caused by leaky/crashy software. And that's why so much effort has gone into making it fast and low latency, but it was a false dichotomy: We can have memory safety without garbage collection. - Automatic reference counting: Most people know about Objective-C's efforts in this space, but it's admittedly less automatic than programmers would like so perhaps it doesn't get enough attention. And yet it should, since q/kdb+ uses reference counting exclusively, and it holds top performance numbers on a large number of data and messaging problems. - Linear lisp[1] asked functions to fully consume their arguments making (cons x y) basically a no-op. Again, no garbage collection, and no fragmentation (at least as long as all objects are cons), and yet no matter how promising this path looked, garbage collection got much more attention. - Rust's borrow checker/tracker makes ownership explicit; somewhat of a middle-ground between the two... There's other scattered efforts in this space, and I don't know about all of them but for more on "everything we know about languages is wrong", also consider that Perl5 uses plain old reference counting, executes the AST directly, and still outperforms python for data and IO[2]! I think the thing to take away is that memory management has to be automatically managed for developer sanity, not that garbage collection is the way to do it. [1]: http://home.pipeline.com/~hbaker1/LinearLisp.html http://home.pipeline.com/~hbaker1/LinearLisp.html [2]: The asyncio stuff in Python looks really promising though...
- tempguy9999 8y agoYou say > but it was a false dichotomy: We can have memory safety without garbage collection. but then say > Automatic reference counting (ARC henceforth). Naive ARC is AFAIK very expensive as it causes a lot of updates at each pointer 'take' even if it's a read only (chasing pointers), trashing caches. Poss. even worse if multithreading is used as a memory barrier may have to be issued. Also it does not collect cycles. Also I've read it causes memory fragmentation (take that with a pinch of salt, that's from my memory!). ARC has some advantages but it has some real costs. Also and even more, if ARC is what you think it is, how is it not an implementation of GC? > Perl5 uses plain old reference counting, executes the AST directly, and still outperforms python for data and IO[2]! Ok... 1) outperforming python is not a high bar. 2) IO is usually handed off to the underlying OS so talking about IO performance is probably misleading. 3) Python now has a fallback non-ARC GC to pick up cycles. 4) GC in python is IIRC an implementation detail, not language-mandated. Finally, if it outperforms it, please provide a reference. I'll check out linear lisp if I can find the time.
- Patient0 8y agoIn the ~15 years I spent building software in C++ I don't recall a single time that I wished for garbage collection. By using RAII techniques, it was always possible (nay, easy!) to write code that cleaned up after itself automatically. I always found it easier to reason about and debug programs because I knew when something was supposed to be freed, and in which thread. The only time that use of simple reference counted smart pointers did not work was when you had cyclic references: but cyclic object graphs caused other problems in any case - so I would always refactor my design to avoid creating them in the first place. In contrast, in the ~10 years I spent working in Java, I frequently ran into problems with programs which needed an excessive amount of memory to run. I spent countless frustrating hours debugging and "tuning" the JVM to try to reduce the excessive memory footprint (never entirely successfully). And of course, as soon as your JVM needed to interact with native code (e.g. a quant library written in C++), you were even worse off, because there was no language support whatsoever for ensuring when, or even if, some native memory would be freed. It made it even harder to debug these leaks - because the "leak" was always very complicated to reproduce. Garbage collection is an oversold hack - I concede there are probably some situations where it is useful - but it has never lived up to the claims people made about it, especially with respect to it supposedly increasing developer productivity.
- apta 8y agoWhile RAII is a very good technique, it is not the solution to all issues here. I read that some concurrent datastructure implementations would not be possible without a GC. Furthermore, when a project reaches a certain level of complexity, it ends up implementing GC anyway (see Unreal engine).
- Patient0 8y agoI agree that RAII does not solve everything - in particular, issues with memory fragmentation. However, I prefer the direction being taken by Rust and suggested by the top level poster: rather than having to run a separate thread which tries to infer which resources are no longer needed, with potentially large runtime costs, instead augment the language semantics with a clear ownership model, and thereby give the compiler enough information to generate code that runs in a stable, predictable memory footprint.
- nurettin 8y agoMy first 8-9 years was full of very competitive, bleeding edge C++ and I can tell for sure that your productivity increase was not caused by garbage collection, but by using a more orthogonal language. I feel it in delphi the same way I feel it in go and python.
- eru 8y agoTo steelman the idea a bit: giving more context for the GC is not the same as manual memory management. Better static analysis of programs can precompute a lot of GC decisions. Allocation on the stack is one example of that kind of optimisation. But you can imagine more sophisticated optimizations.
- tjoff 8y ago> But all of these are much cheaper than developer labour and reputation damage caused by leaky/crashy software. Exactly! And that is the biggest advantage of not relying on a GC, it is easier to detect awkward usage and requires more thought upfront (but not any more than what is actually needed). This results in less bugs and easier maintenance.
- maxxxxx 8y agoI think C++ could get most GC usability benefits if there was a better/simpler syntax for shared_ptr and unique_ptr. I have written code with extensive shared_ptr usage and it was quite pleasant if you ignore the ugly syntax. On the other hand even after years of C# I still miss deterministic destructors.
- powerapple 8y agoC++ is not bad. I think it sits in the middle between C and GC languages. I actually like what C++ offers. Languages with GC are actually easy to debug. I remember once I had my own memory management code, suddenly the write beyond boundary is not segfaulting any more, took me more time to figure out the problem.
- sershe 8y agoAnecdotally for me, I spent more time in my first 0.5 years of Java tuning GC and debugging GC issues, than I spent chasing memory leaks in my previous 5 years of C++. The worst part is that you quickly learn to not introduce too many memory leaks, but as far as GC tuning goes the skill-set feels more like voodoo then science and is constantly obsoleted by GC code changes (or wholesale replacement). I can see how this could be true in some cases and especially for throw-away code and when perf doesn't matter, but for anything at scale and/or with perf requirements, GC is terrible and wastes tons of developer time fighting it (debugging, tuning semi-opaque configs) and working around it (object pools, off-heap buffer managers, controlling object lifetime to avoid certain characteristics, controlling object size, etc.)