5 ms·
So, a question: is it better or worse that the usage of GCs has changed the mindset of developers to stop worrying about allocation/freeing of resources ? Give
by TimJYoung 9y ago
So, a question: is it better or worse that the usage of GCs has changed the mindset of developers to stop worrying about allocation/freeing of resources ?
Given that developers still need to worry about freeing/releasing native resources, I think it might have inadvertently created a false sense of security and, in the process, resulted in developers that don't even possess this type of thinking anymore. We ran into this with the .NET Data Provider for one of our database engines. A good portion of .NET developers that used it were surprised to find out that they needed to call the Dispose method on certain database objects in order to free/release their native handles/resources. This was followed by a period of confusion about what to dispose of, and what not to dispose of (like this: http://stackoverflow.com/questions/2024021/does-the-dispose-method-do-anything-at-all http://stackoverflow.com/questions/2024021/does-the-dispose-...).
- jerf 9y ago"resulted in developers that don't even possess this type of thinking anymore." The good old days weren't. What it resulted in on average was developers who just bashed on their manual allocation until it "seemed to work", not glorious hand-constructed shimmering perfect crystals. At least now the code that "seems to work" usually does, and is memory-safe now, so yes, it's an improvement. If you still want to be careful and do it by hand in such a language, they're still there, and there's even better options arising.
- mschaef 9y ago> At least now the code that "seems to work" usually does, Some of that may come down to better hardware. It's possible to be incredibly naive with today's hardware and still get results that would've taken high level engineering 20 years ago. Edit: This is down-modded, but it's fundamentally good news. Increases in our capacity to do productive work with these machines come from both software and hardware improvements. Not sure what else you'd expect.
- sgift 9y ago> Edit: This is down-modded, but it's fundamentally good news. Increases in our capacity to do productive work with these machines come from both software and hardware improvements. Not sure what else you'd expect. The downvotes are probably from the realization that this is good and sad news at the same time. On the one side, it is good news because many software projects wouldn't have been realized if you couldn't be that naive, on the other side who knows what would be possible with a bit more "discipline" (for lack of a better word)
- kpil 9y agoI think you could leak a little 20 years ago too, or use unnecessary amounts of memory - in most cases. It's just a few applications and parts of applications that really are resource constrained and critical enough to really matter...
- mcguire 9y agoCertainly. All those Unix-style filter programs had little need to be too careful since they didn't run for long. On the other hand, anyone know if the leak in the X server ever got fixed? IIRC, it was small enough that it was only a problem running the server for months on a resource constrained machine.
- mcguire 9y agoThe power of a notation is not in what it lets you think about, but in what it lets you not think about.
- mschaef 9y agoAgreed... the notational convenience of GC is easy to overlook but important. Particularly when anonymous closures, etc get involved, it's very useful to not have to save off a reference and pass it around to the point where it can be manually freed.
- pbh101 9y agoAlso, many more tasks are now economically feasible whereas before, the high fixed upfront cost of programming would have made them never done. In other words, we're making more programs now to do more more-trivial things because the cost of doing so is lower, and that's more or less a good thing.
- gopalv 9y ago> At least now the code that "seems to work" usually does, and is memory-safe now, so yes, it's an improvement. The most significant part of that statement comes from accepting contributions into your project. Java has made it much saner to just look at a patch and LGTM +1, over any work I've ever done in C/asm. Trust in the careful handling of memory by a stranger has been replaced by trust in the system to complain if they don't ... which has made for bigger and wider communities of strangers sending each other patches.
- marcosdumay 9y agoI do agree, but I think it means we need urgently languages that automatically enforce the release of generic resources, not that we should make manual memory management mainstream. And we are moving there. We are getting higher level constructs for managing resources all the time, on every language.
- iopq 9y agoYou already have Rust, which uses RAII, but also has memory safety.
- marcosdumay 9y agoRust is certainly my next language to learn. It seems to get a pretty nice balance between high and low level features.
- fny 9y agoDon't get too comfy with Rust! Memory leaks are memory safe, so a logic error can still lead to a leak. RAII doesn't guarantee that no leaks are possible. Just take a gander at std::mem::forget[0], which is part of "safe" Rust: > "Leaks a value: takes ownership and "forgets" about the value without running its destructor. Any resources the value manages, such as heap memory or a file handle, will linger forever in an unreachable state." [0]: https://doc.rust-lang.org/std/mem/fn.forget.html https://doc.rust-lang.org/std/mem/fn.forget.html
- dbaupp 9y agoYou're correct but that's kind-of missing the point: I imagine the parent was trying to say you can have the deterministic management of non-memory resources like C++, without the downside of loosing memory safety. It's low-signal to say that memory leaks are possible, since, as the article in question demonstrates, it's perfectly possible to get leaks in languages like Java. On `forget`, it's equivalent to the following Python, which can be translated into pretty much any language one wants: holds_reference = [] def forget(x): global holds_references holds_reference.append(x) There are cases where one needs to think about avoiding leaks, but no more than C++.
- danbruc 9y agoA good portion of .NET developers that used it were surprised to find out that they needed to call the Dispose method on certain database objects in order to free/release their native handles/resources. If the dispose pattern is implemented correctly, you strictly speaking don't have to call Dispose, the finalizer will eventually free the unmanaged resources. But there are not many guarantees when this will happen and so you may hit resource limits before unused unmanaged resources are freed and therefore it is of course a good idea to explicitly free them by calling Dispose explicitly or implicitly.
- TimJYoung 9y agoYeah, I should have clarified that, so thank you for doing so. With our database engine, it was very important that Dispose be called in order to avoid some nasty surprises due to the GC finalizing the outer .NET class instance from a different thread. You can also run into this with a plain ODBC driver using the ODBC.NET data provider.
- osd 9y agoTo be fair, the correct use case for Dispose is extremely confusing in .NET. Dispose feel like a language construct to novice developers, but it's left to library designers to properly implement it. There are multiple cases of Microsoft designed API's that don't properly implement dispose, and therefore can't properly be used with 'using.' I'm sure the amount of non-microsoft-blessed code with this issue is far larger.
- mikeash 9y agoI don't think it has really changed the mindset of developers. All it has done is lessen the consequences of not caring, or of caring and getting it wrong. I once took over an app that did a live search of a dictionary as the user typed. Looking over the code, I noticed that the memory management was a little funky, so I did a leak analysis on it. Turned out it was leaking something like 3,000 objects with every single keystroke. It still "worked," because the OS acts as the ultimate GC by cleaning up all of an app's memory when it terminates. Users mostly didn't leave it running long enough for the objects to build up, and if they did then it would die on its own eventually. There's a famous story of Microsoft's heroic efforts to maintain backwards compatibility in Windows. The original version of SimCity would free memory and then read from it. This worked fine on Windows 3.x because the newly freed memory would be left undisturbed until something else allocated new memory. Windows 95 didn't work that way, so SimCity started crashing. Microsoft added code to check if SimCity was running and change the behavior of the memory allocator to stop it from crashing. I've been programming on Apple platforms for a long time. The current standard languages are Objective-C and Swift, both with automatic reference counting (which is mostly a GC, although you have to manually break cycles). Automatic reference counting is a relatively new addition to Objective-C, and before 2011, everyone did manual reference counting instead. For a long time, the state of the art was to follow the official rules for writing refcounting code and occasionally look for leaks and crashes by running memory debugging tools on the live app. About eight years ago, the Clang project put out their static analyzer which could look at Objective-C code and tell you when you weren't following the rules properly. The reaction of every Objective-C programmer I knew, even the really good ones, upon running the static analyzer was something like, "holy shit, I had no idea my code was so full of bugs!"
- algesten 9y ago> The current standard languages are Objective-C and Swift, both with automatic reference counting (which is mostly a GC, although you have to manually break cycles). I don't want nitpick, but isn't there a _world_ of difference between ARC and your standard GC? In a GC I expect various different "spaces" for different generations of memory allocation and an assortment of algorithms for detecting unreferenced memory at various times. "Stop the world" events etc. I may be wrong, but I always thought of ARC as a somewhat glorified pointer structure where each allocated memory got a counter of how many places are currently pointing to it, and when that counter reaches 0, deallocation happens. No fancy generation spaces, no complicated cycle finding algorithms and no stopping the world.
- bluGill 9y agoI think the right answer is what we are missing a middle ground. All the "leaks" are weird special cases. I think modern C++ is getting closer to the right answer: you always have to think about object lifetime, but sometimes the thought is easier than others. One of the things C++ gets right is deterministic destruction. 5-10% of the time this is important. In particular C++ has the ability to manage resources other than memory along with object lifetime. (Python has a simplified for of this, I don't know about other languages). Of course the inverse is: 90% of the time deterministic destruction is not important. 80% of the time object lifetime is easy: this object is tied to the stack lifetime (if you have a garbage collector is should be a fatal error if the object is accessible outside of the stack lifetime). Of this 5% of the time deterministic destruction when the stack frame goes out of scope is important. Generally stack based destruction uses less CPU than real garbage collection. (though real garbage collection may move destruction outside of the performance critical parts) Then 15% of the time the object is shared outside of the stack, but reference counted garbage collection will take care of it. Of that maybe 2% is cases where reference counting (deterministic destruction) is important, the other 13% other garbage collection is better. 4% of the time you have a shared object outside of a stack frame that cannot be reference counted, but other garbage collection algorithms will work just fine. The last 1% of the time something weird and complex is going on. You will need your best engineers to spend a lot of effort making sure you have all the cases right, and this effort will continue as long as the project lives. Note the the above percentages are educated guesses. They seem right based on my experience, but every problem domain is different. I'm not aware of any formal study of the problem. (I can't even think of a useful way to run one)
- btschaegg 9y ago> In particular C++ has the ability to manage resources other than memory along with object lifetime. Such a simple statement, yet I can't tell you how much I miss this property every time I have to deal with some hacky IDisposable in C#.
- hdhzy 9y agoThe problem is even worse than it looks on the surface (from the client side). If you're implementing a library that holds native resources in .NET it's better to familiarize yourself with CriticalFinalizerObject [0], SafeHandles and Constrained Execution Regions. Writing correct finalizers that work even when out of memory exceptions are thrown is no easy task... [0]: https://msdn.microsoft.com/en-us/library/system.runtime.constrainedexecution.criticalfinalizerobject(v=vs.110).aspx https://msdn.microsoft.com/en-us/library/system.runtime.cons...
- deleted 9y ago[deleted]
- problems 9y agoThis is why I prefer the Rust and modern C++ approach more personally. Put everything on the stack by default for fast, easy access, it automatically gets destroyed when you leave the scope. Then use reference counted shared pointers or other tactics when you need to store large amounts of data in memory. This keeps users thinking about lifetimes but at the same time encourages safety and performance.