Garbage Collection is Wrong
lb-stuff.com
lb-stuff.com
In what cases is this not possible? I model the resource as an object and that object has a memory location.
(Yes, in a non-deterministic GC, you can't do this for expensive resources, but that's a problem of GCs, not an in-principle problem).
EDIT: I am guessing the parent actually meant "associate the life time of a resource with the life time of a single, non-/never-shared storage location". The statement as written is one I have seen proponents of non-deterministic GCs make.
Something is handling both list [1], that something could own the object, or maybe something external to that. Giving ownership of the object to both list is a design error [2].
[1] e.g. if the two list are an implementation detail of a data structure, the data-structure itself could own the objects in the lists.
[2] Do non-deterministic garbage collectors that handle cycles allow you to have a resource with multiple owners? Yes. Should you do it? No, god, please don't.
Imagine you have an arbitrary long lived connected cyclic graph that can be incrementally updated from multiple short lived worker threads. With a GC, this is a no brainer: just put the objects in the graph. No workarounds, no extra tracking.
Without a GC, on removing a node, you have to walk to essentially do a mark/sweep of the graph to find dead nodes that were connected through the node you removed.
Removing an object is just as easy as removing an element from the vector. (If you test the weak_ptrs on use, that's actually the only thing you would need to do).
Reference counting works fine as long as you don't have cycles, of course.
And I know it for the .NET GC, they tried a reference counting GC as alternative to a collecting GC and it performed worse and comes with the cycle trouble.
Of course, not having to implement the garbage collector yourself is a benefit.
I don't think I asked for that but correct me if I did (maybe I'm just not understanding your point).
Anyways, could you elaborate why shared_ptr, unique_ptr, and weak_ptr don't work in that case?
shared_ptr works fine for my example. Shared immutable state is a case where reference counting works extremely well, since immutability implies no cycles.
User decides to view a couple of orders. Orders reference products. Those products are managed by something (e.g. an unordered_map of shared_ptr). The order can check, is my product there? If so, i'll copy the shared_ptr to it. Otherwise, I create a shared_ptr for a product and store a copy in the manager. When the order's destructor is triggered, you check the count of the shared_ptr. If it is 2 (i.e. the order and the manager), the order removes the shared_ptr.
[1] Single ownership is the key concept, not reference counting which is just an implementation detail.
https://news.ycombinator.com/item?id=7315575
(Don't want to duplicate the comment once again.)
Global variables exist and can be used to hold resources. If you have a resource that's not associated with any single function context you can move it into the global variable.
And reference counting also renders the point of determinism moot - now you never know if releasing a reference while trigger freeing a resource. You could of course delay offload it to a separate thread, but this makes you lose any guarantee about the time when the resource is actually freed, too.
You know that releasing the last reference will. That is why the important concept is single ownership of resources and not reference counting by itself.
And are generally considered bad practice.
Yep! Or, ideally, you use one of the good reference counting libraries that your language almost certainly ships with if it is one which encourages RAII.
I think the article is actually more of an argument against that line of reasoning than an argument about performance. Its main proposition is that properly used RAII gives you less opportunities for resource leaks of all sorts, and is very easy to do. I agree with you that it isn't wrong in all scenarios (I mostly program in Ruby and Javascript for goodness sake!), but I think a lot of people are unaware of some of the advances in patterns, libraries, and compilers that make it much easier to write software that doesn't need a GC.
Although in some situations it isn't acceptable and it's interesting to look at what languages like Rust do about it. It ends up guaranteeing safety, but with not much more effort than GC.
See for example http://ashutoshmehra.net/blog/2008/12/notes-on-zdds/ - "ZDD-bases, which though conceptually easy to understand, are non-trivial to implement efficiently because behind the scenes, lots of things have to be taken care of: o New nodes are born and old ones die — nodes have to be efficiently allocated, ref-counted and garbage-collected."
Or from http://www.ecs.umass.edu/ece/labs/vlsicad/ece667/reading/som... :
> One would like to release the memory used by those BDDs, but there are two problems. First, some subgraphs may be shared by more than one function and we must be sure that none of those functions is of interest any longer, before releasing the associated memory. Second, BDD nodes are pointed from the unique table and the computed table, as well as from other BDD nodes. There are therefore multiple threads and one cannot arbitrarily free a node without taking care of all the threads going through it [12].
> A solution to these two problems is garbage collection.
Similarly, http://vlsi.colorado.edu/~fabio/CUDD/node3.html ("The CUDD package relies on garbage collection to reclaim the memory used by diagrams that are no longer in use. The scheme employed for garbage collection is based on keeping a reference count for each node."),
1. plenty of garbage collectors do not pause all threads or wait for memory to be full.
2. you CAN ask for gc anytime you want in java and in C# (you need not wait for it to happen)
3. new and delete are still integral to C++ (check out any large codebase, like llvm)
As for point 3, just because it's in use, doesn't mean it's correct. With the introduction of unique_ptr, most uses of new and delete can and should be factored out.
"Right or Wrong" are irrelevant in the face of "Practical or Not".
The fact that all large pieces of serious code use new and delete would either imply that the author is smarter than all the people writing that code combined, or (more likely) he did not consider something w.r.t. the practicality of not using them.
Let me go further and explain my original post in a more succinct fashion:
One giant [[citation needed]] on every claim in this article, including, but not limited to, new and delete being useless.
As for the citation, if you are immersed in the C++ community in general (going native, c++ now, subreddits, irc, accu meetings, etc), it's the general feeling.
2. Designing your code in such a way that requires it to invoke the GC seems counter-productive. If your algorithm is producing a bunch garbage that is statically known, why not just release that memory explicitly? Invoking the GC is way more expensive than necessary here.
3. New/delete will always be used in performance critical code but I think the point is that in general it's not a good practice.
2. fair but irrelevant. the argument was that there is no way to make GC happen. It was wrong. I never objected to the (perhaps more interesting) argument that one should be able to manually delete objects
3. then the parent should have said "in toy codebases, whose function is to look pretty, new and delete have no place" instead of a sweeping generalization that all uses of new and delete are archaic and wrong. That one generalization was an insult to every llvm & WebKit developer out there.
Disclaimer: i have no dog in this race - i prefer to work in C and think anyone who cannot free their own memory should maintain a safe 5 meter distance from any compiler. (this is my opinion only, of course)
There's zero runtime overhead to using std::unique_ptr - the compiler will inline the calls to delete you'd otherwise be writing by hand.
It's been quite a while since I wrote any significant C++ code, but this statement seems wrong. Is this really "state of the art" for C++?
unique_ptr is gaining traction, of course.
Seeing new and delete has very heavy code smell and can be avoided 99% of the time.
Though while new and delete are best avoided where possible, it's hardly the end of the world if you do use them. And my experience has always been that you're better off just using them, if the alternative is heavy use of smart pointers.
As member variables, smart pointers are tolerable, if copied to an ordinary pointer before heavy use; as locals or parameters, they're almost always best avoided. Over the years I've have had far more annoyance from painful single stepping, spitefully poor unoptimised performance and inconvenient watch window behaviour than I have ever had from just picking out all the memory leaks after the fact. (Which, if the code is written relatively tastefully, is usually pretty straightforward. And unless your colleagues are actively being dicks, I've always found there aren't too many to get rid of anyway.)
Like 99.99999999% of the time you can use those. Like the only situation where I think they might be not enough is for some weird uses of placement new.
...which, for some of us, isn't an argument, as we have basically no choice in the matter. But I'm still interested to know whether this is actually useful input or whether it's more holier-than-thou posturing.
To me, this is missing the forest for the trees. It's easy to argue about language implementation semantics when, in practice, it means almost nothing to anyone as we're stuck with the idiosyncrasies of the platforms we work on, even when we choose the platform.
I guess it's good that someone is looking at these things, but I can't see the utility in it. Best case scenario, this becomes a paradigm shift in new languages (which doesn't help us or anyone else for years) or gets implemented in the next major revision of language X (which doesn't go mainstream for years).
The vast majority of resources are short-lived and don't create cycles. Our garbage collections "systems" should be designed for this case.
Cycles are a special case required for few data structures. They are not the norm and we shouldn't ship a huge heaping mess of a garbage collection system and make everything else slower to account for this rarely used special case.
Anyway people programming today shouldn't be thinking in terms of pointers and references. We should be thinking in terms of VALUES. Finite values have no cycles! The Haskell and C++ community have already embraced this, everyone else is still catching up.
Yet another reason why Java is a horrible language holding people back and the JVM is basically a hamster wheel keeping itself busy.
I have to disagree with your second statement, ref-counting is extremely fast. If you consider it a GC then it's the fastest GC. It's also deterministic and does not pause.
No, it's not, not unless you use a lot of cleverness. "We find that an existing modern implementation of reference counting has an average 30% overhead compared to tracing…" (They did perform a lot of optimizations to get it up to speed with tracing garbage collection... however, these are far beyond what shared_ptr does.)
http://users.cecs.anu.edu.au/~steveb/downloads/pdf/rc-ismm-2...
Of course, you could queue up and lazily delete the resources, but then you're back to nondeterministic behavior.
With refcounting you have:
- Slow memory allocations (need to manage a fragmented heap)
- Slow accesses and pointer handovers (updates to the refcounter)
- Slow free (need to manage the free list)
- No asynchronous pauses (since there is no garbage collector)
With a GC, you get: - Fast allocations (usually just an "add" instruction since the heap is not fragmented)
- Zero cost accesses and pointer handovers
- Zero cost free (just stop using the pointer)
- Some asynchronous pauses and CPU usage while running the GC
It turns out that the cost of the first three points when using refcounting are much higher than that of the GC. Another reply to your post included references to actual research on this subject.www.cs.virginia.edu/~cs415/reading/bacon-garbage.pdf
Furthermore, the only "advantage" I see in garbage collectors is that they can deal with cycles.
I say "advantage" because i'm of the strong opinion that having a cycle in your code is a software design error.
It is performance wise infeasible to avoid that circular reference because it is very easy to pull back a lot of data from entity framework and change none of it.
Also I would say that it's very uncommon to do/need something like this so it shouldn't be cited as an reason for cycles to be generally handled.
Many modern GC systems are tuned for exactly this. It often is called generational garbage collection and the youngest generation is tuned to quickly collect these short-lived and non cyclic objects.
I used to work in this field so I've got at least a decent grasp of what modern GC's do and don't do well:)
Cycles aren't common at all and the answer shouldn't be "let's create a huge complex GC that handles all cases, then down the road we'll optimize for the common case" the answer should be "let's do the sane reasonable thing that handles the common case and push the problem of cycle-management to the programmer"
Also, in general GC's are much faster than reference counting (especially when the reference count updates needs to be thread-safe), so the only reason one would use them is to have deterministic object destructions. These aren't common at all and should not be the deciding factor when choosing a memory management scheme.
Yes, garbage collection is inappropriate for things with finalizers---for resources other than memory. Other than that, I can't see anything useful here.
For everything else, there is garbage collection.
The way I understand it, no garbage collection means you take care of cleaning after yourself, whereas garbage collectors do it for you. It sounds like what he calls resource acquisition.
https://en.wikipedia.org/wiki/Smart_pointer#C.2B.2B_smart_po...
So, what are you going to do? Just say "Most programmers are lousy, deal with it"? Introduce garbage collection, which solves 90% of the problem (memory) with no programmer intervention, but does nothing whatsoever to help the other 10% of cases (files, sockets, mutexes, etc.)? Rely on RAII, which in turn relies on programmer education and discipline? Or do you have another alternative?
Optional GC could be interesting - maybe some kind of a switch that you set at compile time that says "this program uses GC" or "this program uses destructors". But then you're essentially talking about two similar, but different languages (like, say, Java and C++).
Rust actually does that, and GC there is optional by the way.
An optional opt-out would invite a wider range of developers.
My hero.
Edit: Actually, reading about auto_ptr<> and friends I see what you're getting at. The codebase I worked on some years ago didn't use those so I'm not totally familiar with that idiom for wrapping heap allocation.