13 ms·
Friendship ended with the garbage collector
- js8 5y ago> Of course this approach has its own downside: a program may panic at when dropping an owned value, if it still has one or more references pointing to it. I wonder if this could be also handled by having a custom error handler available on each created reference, to decide what to do with the "hanging" value, whether to reassign ownership, or invalidate the reference, or panic, or do whatever else.
- rwmj 5y agoYour solution to the supposed overhead of garbage collection is to make every single object fat and have an extra test every time a pointer to an object is dropped?
- js8 5y agoNo, the handler(s) would only be called when the owner wants to dispose the object and there are still references to it.
- rwmj 5y agoHow do you know there are still references without counting them somewhere or inspecting all other objects? And what would the handler really do? What common code has other owners willing to take over ownership (and you have to handle this problem recursively too).
- js8 5y agoIn the article, they already assume reference counting of references, see the previous paragraph: "When the reference goes out of scope, the count is reduced. When an owned value goes out of scope and its reference count is not zero, the program terminates with an error (which I'll refer to as a "panic")."
- 55555 5y agoI assume the title is a reference to this meme: https://knowyourmeme.com/memes/friendship-ended-with-mudasir https://knowyourmeme.com/memes/friendship-ended-with-mudasir
- Arnavion 5y agoYes it is.
- throwaway81523 5y agoSingle ownership (std::unique_ptr) works pretty well in C++ a lot of the time, but not always. Reference counting including unique_ptr also have to be locked for updates, slowing down multi-threaded programs, as exemplified by the notorious CPython GIL (global interpreter lock). And the idea of transferring ownership between lightweight processes leads to memory fragmentation within the processes. Erlang deep-copies message contents except for large binaries, so each lightweight process has its own semispace GC and maintains good cache locality. There are also styles of programming (such as using RB trees as persistent data structures with multiple versions sharing parts of the structure) that are not conducive to single ownership. At the end of the day, in a general purpose language, it's great to have machinery to avoid needing GC when possible, but you really do want to have GC to fall back on.
- vbezhenar 5y agoMay be it's time to implement hardware CoW for RAM? Or can OS do it efficiently?
- throwaway81523 5y agoI don't understand what you're getting at, but you can implement CoW at the page level with MMU hardware. That's how Redis dumping works, for example (the process forks into two, with one dumping to disk and the other continuing to take updates, CoW'ing pages that get modified and sharing the rest). What would CoW even mean inside a single address space? The idea of persistent data structures (as used in functional programming and other places) is that you never visibly modify anything, but instead make new objects that share structure with the old objects, relying on the GC to clean up data that stops being reachable.
- hutzlibu 5y agoWhat is CoW?
- detaro 5y agocopy-on-write
- habibur 5y agoWe have gone full circle with GCs. It was reference counting in the 80s and 90s. And then tracing GC people stepped in. "Reference counting is worse of both worlds. It's slower than GC because all these memory have to be touched!" But real life experience continues to show ref counting doing better. And GC users pre allocating object pool to avoid GC pauses. Proponents still in denial. "What if a large object is deleted? That will also create GC pause like delay." And now here we stand. Back to reference counting.
- kungito 5y agoWe have not gone full circle. We are just expanding into various scenarios where a gc language is preferable to non gc language and vice versa. There will never be a perfect compiler nor do we always want to do everything manually.
- chronogram 5y agoEver since Java became immensely popular GC has been just as popular, but that didn't mean reference counting ever went away. They're branching paths and different tools and more people than ever before pick and choose what to use for the job. No circle was ever made.
- habibur 5y agoRight Java made GC popular. And right now Rust is making reference counting, borrow-owner popular. This is what I am observing. That languages get popular is no surprise. But what was a little bit surprising for me was that the tech the language uses gets spread and popular all over -- outside of that language as long as that language stays popular. I right now am think automatic reference counting is the way to go. In a sense C++ RAII is also ARC. But a compiler level support is better.
- pjmlp 5y agoSwift and Objective-C like performance? No thanks.
- 5y ago
- zabzonk 5y agoAs someone who has spent most of his career writing high-performance server-based software, I've never really understood why people like GC. Even back in the day when I was writing BASIC programs (a long time ago now) I thought GC was pants, and spent some considerable time trying to avoid it happening. How to avoid it? Think. Be careful. Write good test suites. Run them a lot. It really isn't so hard.
- djur 5y agoWhy not just use a language without GC?
- zabzonk 5y agoI do. I'm suggesting that others do too.
- alephu5 5y agoWhy use a language with a GC at all if you don't like it?
- alpaca128 5y ago> Think. Be careful. Write good test suites. Run them a lot. It really isn't so hard. This mindset lead to countless security problems and other bugs in all kinds of software. Even now we still find exploits in sudo and openSSL. And the problem wasn't developers leaning back and saying "today I'm going to program carelessly and do a bad job because why the hell not".
- zabzonk 5y agoDo we have good test suites for those programs? If so, could you direct me to the code for them? And of course these are run-once programs (not that excuses them)- I am talking about 24/7 stuff.
- deleted 5y ago[deleted]
- winrid 5y agoI've been impressed by nim's GC. My understanding is it's not super complicated. Not having a big header on each object and having each thread have its own GC goes a long way.
- mojuba 5y agoWhat happens to objects shared between threads then?
- elcritch 5y agoIt depends on the GC you're using with Nim. Guessing the OP means the newer ARC GC, then you can use `move` semantics to move some data to another thread, or you can do a deepCopy. You can do shared data structures but that requires a bit more work.
- winrid 5y agoIt becomes more difficult. There are a few supported memory managers by Nim (including Go's GC!), but with ARC you explicitly move or deep copy the structure. Obviously this is a bit more difficult than say Java, but then you never have to stop the world.
- alpaca128 5y ago> not being able to implement certain patterns in safe code (e.g. linked lists) This may be correct for doubly linked lists in the classical implementation, but I've never needed one outside of programming exercises. Trees can be safely implemented in at least 2 ways. Rust is limiting, but usually more in terms of approach, not possibilities. As long as the program isn't interacting with the "unsafe" outside environment it's usually no problem avoiding unsafe blocks completely.
- tick_tock_tick 5y agoehh depends on what you're actually coding one job intrusive doubly-linked list were everywhere and very appropriate while my current job never touch them at all.
- arethuza 5y agoI believe the Linux kernel has its own implementation of doubly-linked lists: https://0xax.gitbooks.io/linux-insides/content/DataStructures/linux-datastructures-1.html https://0xax.gitbooks.io/linux-insides/content/DataStructure... Edit: Not that I know much about the Linux kernel, but I do remember it being used as a counter example when someone stated before that nobody uses doubly-linked lists.
- zozbot234 5y agoRust developers are exploring new safe ownership patterns (such as 'QCell' and 'GhostCell') that should make it feasible to implement double linked lists without unsafety. Not very simple, though: these solutions do come with an inherent tradeoff wrt. lack of modularity.
- JaggerJo 5y agonot directly related to this post, but it’s mentioned that ‘async’ spawns a process. This seems very heavy. I guess Process does not refer to an OS Process here?
- jstx1 5y agoConcurrency and multiprocessing terminology is the worst. Trying to learn about it, it seems like every language is doing their own thing with their own set of terms and definitions.
- YorickPeterse 5y agoMy bad for not making it more clear: it refers to a lightweight process here (e.g. like Erlang), not an OS process. Merely spawning these is basically just an allocation (Inko doesn't use PIDs either), so about as fast as it gets.
- lbriner 5y agoThis might be a stupid question but can't we mix the best of both worlds in a programming language? To have GC where it makes sense and doesn't add too much overhead but allowing non-GC objects that require a bit more care and where you know you need to free memory yourself or using some basic smart_ptr-style tracking object? Just like .net allows you to use "unsafe". It is a clear marker that you are stepping outside of the safe zone and need to be careful, but at least you can.
- flohofwoe 5y agoYou can already do that in any GC language (to some extent) by drastically reducing the amount and creation/destruction-frequency of garbage collected objects (for instance by reducing "object granularity" and using object pools). But this means that the programmer suddenly needs to actively think about memory management (and already in the design/planning phase, because later is too late), while the whole selling point of automatic memory management solutions was that this wouldn't be necessary anymore (which was a deception all along, but that's a different topic).
- rwmj 5y agoBut it's still a better trade off: The programmer can not think about memory management most of the time, but if they want to they can optimize hot paths in the code. Optimization (in all programming languages) often involves complicated and non-intuitive code backed by careful measurement.
- pradn 5y agoYes, this approach works best with the "premature optimization is the root of all evil" axiom. When I worked on high-performance servers in C#, we would occasionally find ways to 1) reduce allocations 2) make allocations either very short-lived so they stay in the short-term GC timeframe or very long-lived (object pooling/reuse or creating object-aggregates for pseudo-slab allocation).
- guenthert 5y ago
- dexen 5y agoI like this approach, and exploration of alternatives to popular solution is good. In particular the simple approach to circular structures looks good - including the reverse lexial order deallocation.
- Shadonototro 5y agoGC can be a good thing, when you want to quickly sketch a new program of simply want to act as a scripting language and do a task as quick as possible without too much thinking That said, i'd still prefer a simple Reference Counting than a full GC as it's quite memory hungry and collections introduce pauses.. wich is not a desirable behavior at all, even though pauses can be solved, i'm no a fan of the idea that a language manage my memory in unpredictable ways The reason i use D is because i can use it just like C/C++ (with modern niceties such as module, slices, metaprogramming and fast compile time) with my own allocators, completely ignoring the GC But whenever i need a quick and dirty "script" (it almost never happen), instead of using bash or python, well i simply just use D with its GC, but again, as i said above, i'd prefer a simple RC instead.. but yeah.. it's no big deal since i prefer to manage memory my way anyways A perfect language understands your intent without impacting your workflow with slow compile time due to heavy, restrictive and slow compile time static analysis (borrow checker for example)
- JoeAltmaier 5y agoI'm amused when I see all the hoops folks jump thru to solve 'memory reference bugs'. Garbage collectors 'leak' if you forget to null a reference in scope after you're done with it. Those null pointers can fault if used afterward. Its as much work to use a garbage collector efficiently as it is to allocate and delete your own memory. And with about the same bugs. It's been problematic from the start. Not a fan of automatic systems to 'manage' memory. Overhead for automatic systems create lag, latency and bloat. I'm old-fashioned, and create code that has strict ownership of allocated memory by subsystem. If a module that allocates also frees, leaks become negligible. If references are not shared but accessed formally, nulls become returned errors etc. Discipline is hard, but leaky buggy garbage-collecting behemoths are hard to live with too.
- messe 5y agoYou might be interested in Zig[1]. Allocation is manual. Standard library containers and functions require an allocator to be passed to them, so nothing allocates on the heap unless it's explicitly told to. Cleanup can be done at end of scope via defer / errdefer. Seamless interop with C, even when cross compiling which Zig handles natively: the standard install of Zig comes with a "zig cc" wrapper around clang that allows you to cross compile[2][3] C and zig code for any number of target architectures (it includes C standard library headers and code for them). Zig is the only language I've come across which functions perfectly as a better C. [1]: https://ziglang.org https://ziglang.org [2]: https://actually.fyi/posts/zig-makes-rust-cross-compilation-just-work/ https://actually.fyi/posts/zig-makes-rust-cross-compilation-... [3]: https://andrewkelley.me/post/zig-cc-powerful-drop-in-replacement-gcc-clang.html https://andrewkelley.me/post/zig-cc-powerful-drop-in-replace...
- nine_k 5y agoI think you slightly overstate the problem. Most of the time (in my practice, like 99% of the time) you don't have to care about nulling references. The objects that need collection are created in local scopes of functions, and get detached and ready for GC right when the function returns. When you have many small functions, this happens often, so unused objects don't spend much time being referenced and ineligible for GC. The problematic part is long-living mutable structures, which you hopefully have few, and know well where they are in your code. Also, as an exercise, I suggest that you try to imagine a Lisp with explicit memory management.
- jrochkind1 5y agoSimilar to ObjC reference counting? (Not sure, I don't use ObjC and am not an expert in it, it just reminds me of what I've heard of it, so honest question). https://clang.llvm.org/docs/AutomaticReferenceCounting.html https://clang.llvm.org/docs/AutomaticReferenceCounting.html
- cratermoon 5y agoYes, the author has re-discovered reference counting.
- YorickPeterse 5y agoNot so sure, does Objective-C have a single ownership model and move semantics? As far as I know it doesn't, which would make it pretty different.
- flohofwoe 5y agoObjective-C (and I guess Swift too?) has ARC (see link in grandparent) which attempts to statically track the lifetime of objects at compile time. This essentially results in a "move operation" if the compiler detects that the original reference isn't used anymore after the assigment. It's basically halfway between C++'s shared_ptr-vs-unique_ptr approach, and Rust's lifetime and ownership tracking.
- pjmlp 5y agoARC was a marketing gimmick, after the failure of getting a conservative GC to properly work with the underlying C semantics alongside the mix of frameworks compiled in different modes. The only thing it does is to automate the retain/release calls of Cocoa classes, or classes that offer similar API. It doesn't apply to any other C like types from Objective-C, and basically the generated machine code is hardly different than when devs keep doing those calls by hand. Yes there is some potential for the compiler to remove needless set of retain/release pairs for short lived objects, it doesn't happen all the time though. Swift, also despite the ARC marketing, made the sensible choice to go with ARC, because one of the key designs was interoperability with Objective-C runtime. As shown by RCW/CCW runtime used by .NET for interoperability with COM's AddRef/Release, there is a lot of more machinery required to have a tracing GC cooperate with RC scheme, so it was understandable this route wasn't chosen for Swift. As some papers prove, current performance isn't that great, which is also Swift 5.5 is bringing a more aggressive optimization, which is turned off by default. Check WWDC 2021, "ARC in Swift: Basics and beyond".
- ameixaseca 5y agoYou can implement a linked list in safe Rust (even a doubly linked list if your prefer), there are a number of examples available if you google for it - for instance: https://gist.github.com/matey-jack/3e19b6370c6f7036a9119b79a82098ca https://gist.github.com/matey-jack/3e19b6370c6f7036a9119b79a... In fact, the Rust book explicitly mentions doubly linked lists when discussing Pin: https://doc.rust-lang.org/std/pin/index.html#example-intrusive-doubly-linked-list https://doc.rust-lang.org/std/pin/index.html#example-intrusi... The ownership system might make it less trivial to implement, but the statement that you cannot implement it in safe Rust is incorrect regardless.
- aszen 5y agoI recently watched an interesting talk about a new language and the author quite well described that the best memory management strategy varies by use case. So web servers benefit from arena allocation GUIs benefit from ARC Short running programs can benefit a lot from just leaking memory.