We might be at the point where this is an acceptable simplification for very fine grained processes and services. Has anyone explored this model?
We might be at the point where this is an acceptable simplification for very fine grained processes and services. Has anyone explored this model?
Given this setup I can prototype programs without thinking about memory at all. When my program runs out of memory I start measuring allocations and insert the odd pointer clear at strategic places until I don't, then I continue on my merry way. 99.9% of programs we write don't require thinking of "no memory leaks" as a black-and-white cast-iron guarantee.
This setup also provides a really nice teaching experience where students can learn about pointers and write all sorts of programs without ever running into weird bugs due to use-after-free or overflowing bounds. However it adds some overhead akin to a realtime GC: everytime you copy a struct you have to increment refcounts of any pointers nested arbitrarily deep within it (Mu maintains a flattened bitmap for each type to make this not utterly suck). Sum types get even crazier, because you might have a pointer at offset x depending on the state of the tag at offset y, and so on. I'll try to measure the overhead at some point..
Elegant! It's funny though, on persistent storage we sort of invented this decades ago in the form of hard links. You can have as many hard links as you want to a file, and the file will only be deleted when all links are removed and all file handles are closed.
After all, the essential rule behind garbage collection is that all variables are available to a program for ever - yet the implementation is allowed to clear up memory if it can prove that a value can never be retrieved again (for instance, because all references to it have gone out of scope).
It has also crossed my mind that a large number of small, terminating programs might well be better off running to completion without any collection taking place. But language implementors are pretty smart - probably quite a lot of the small programs you have in mind already /do/ run right through without ever doing any garbage collection.
"DMD does memory allocation in a bit of a sneaky way. Since compilers are short-lived programs, and speed is of the essence, DMD just mallocs away, and never frees. This eliminates the scaffolding and complexity of figuring out who owns the memory and when it should be released. (It has the downside of consuming all the resources of your machine if the module being compiled is big enough.)"
http://www.drdobbs.com/cpp/increasing-compiler-speed-by-over...
The downside to this is it makes it difficult to use tools like Valgrind that trace memory allocation in one's own program.
Maybe I'm in minority but in C I tend to flag unnecessary free()'s before program exit in code reviews (There is not much that you can do about that in "safe" languages like C++).
I'd just mention it makes tracking memory allocation harder for an end-user of an application framework that does. In those instances, it might be nice to, say, disable this approach with compiler flag or something.
Edit: it also depends on how concerned you are with memory leaks. Making sure every single allocation has a delete attached is a simple rule. The rule of "every allocation has a delete, except the allocations we're sure will last for the life of the program", making sure that rule is followed seems a little harder and presents a "mental overhead". This kind of thing probably depends on how prone to memory leaks your program is (I've seen big, messy, memory leak prone programs where "oh this one isn't deleted 'cause of optimization" would make the leak-detective's job just that much harder).
In general copy-on-write schemes would be useful for this, and some persistent data structures can be as well.
JonL White wrote a paper about this idea in 1980: http://3e8.org/pub/scheme/doc/lisp-pointers/v1i3/p17-white.p... (favorite quote: "GC Once a Year: Enough?")
Rivest and Shamir wrote an interesting paper on write once memory in 1982 that I feel might have some non-obvious applications to this: https://people.csail.mit.edu/rivest/RivestShamir-HowToReuseA...
Linear types (http://home.pipeline.com/~hbaker1/LinearLisp.html) is a closely related idea in terms of avoiding garbage collection that has very useful concurrency properties. Most time-of-check-to-time-of-use and kernel/userspace race conditions would be prevented by using linear types for system call arguments. IMO linear types have enough benefits outside of memory management to make the trade-off of using them worth it vs the other techniques mentioned above.
On the other hand, generational GC is almost trivial to implement for immutable languages, exactly because there is no need for write-barrier. BEAM's GC is perfect example of how trivial can well performing GC be in that case (and good study material that is perfectly relevant for runtimes with mutable objects).
Speaking of safe software, I came across a free chapter/snippet on memory and safety in Ada[1]:
"(...)Restrictions
There is a general mechanism for ensuring that we do not use certain features of the language and that is the pragma Restrictions. Thus if we write
pragma Restrictions(No_Dependence =>
Unchecked_Deallocation);
then we are asserting that the program does not use
Unchecked_Deallocation at all – the compiler will reject the program if this is not true.There are over forty such restrictions in Ada 2005 which can be used to give assurance about various aspects of the program. Many are rather specialized and relate to multitasking programs. Others which concern storage generally and are thus relevant to this chapter are
pragma Restrictions(No_Allocators);
pragma Restrictions(No_Implicit_Heap_Allocations);
The first completely prevents the use of the allocator
new as in new Cell'( ... ) and thus all explicit use of the heap. Just occasionally some implementations might use the heap temporarily for objects in certain awkward circumstances. This is rare and can be prevented by the second pragma.Hint: Ada is also a high level language that allows low-level programming, and might be fun for writing a lisp ;-)
[1] http://www.adacore.com/adaanswers/gems/gem-43-safe-and-secur...
(http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.39....)
Tolpin and Toft designed an extension to the functional language ML which used region based memory managed instead of traditional garbage collection for ML.
This lifted the lifetimes of variables into ML's type system (!!!) while the underlying implementation IIRC could achieve O(1) memory behaviour except when an exception occurred.
While this sounds amazing, there were draw-backs on the implementation / theory as certain optimisations were near necessary to get good performance. I.E. word sized integers had to live in the heap as opposed to registers. Another issue was that loops had to be restructured from idiomatic ML style to a slightly different one, other the region inference logic would cause O(N) allocations in a loop which would otherwise use O(1) allocations.
http://www.elsman.com/pdf/retro.pdf
or "Tauplin and Toft region based memory management retrospective" should lead you to the paper.
The main issue is that in most cases the complexity increase is not worth the trouble.
So such languages end up being used in embedded systems and high integrity deployments. Usually where human lifes are at risk.
The mode of not freeing has been explored in programs that are short-lived and run on top of an OS that reliably cleans up memory. For instance, system utilities and compilers and such.
Got a C program that crashes due to premature allocation, according to Valgrind, and it is hard to figure out why? It it just a short-lived utility that does some job and exits? Then just #define free(p) ((void) 0) and rebuild.
Actually, D's compiler more or less works this way: http://www.drdobbs.com/cpp/increasing-compiler-speed-by-over...
http://lists.gnu.org/archive/html/coreutils/2014-08/msg00012...
[fork, allocate, exit] is a very valid and robust pattern leveraging the OS as a garbage collector.
This kind of pattern reminds me of crash-as-an-operation-mode software.
Also, some hapless intern is going to copy a chunk of code out of your project in their new project and then give you hell because their program leaks memory like crazy.
Also, some mid level manager is going to decide that all code must be run through static analysis and come out cleanly and you're going to have a bad day.
However, the paradigm you're referring to is interesting. Short-living processes that allocate little memory could work. Also, servers could use this model: create a slab of memory for each request, and allocate memory on that slab when processing the request. When the request is completed, you can free the entire slab at once. I know the Ur language does something like this.
It uses explicit regions instead of garbage collection.
Explicit regions are both very simple and very fast, but how far can one get with them? I want to find out, so I am developing this interpreter.
So we've come full circle.
All research of Interlisp-D, Smalltalk and Mesa/Cedar at Xerox PARC:
https://archive.org/details/bitsavers_xerox
Stéphane Ducasse also maintains the original Smalltalk manuals on his legacy books:
http://stephane.ducasse.free.fr/FreeBooks.html
Oberon at ETHZ:
http://www.ethoberon.ethz.ch/books.html
If you want to see how the latest incarnation of Oberon used to look like:
http://www.progtools.org/article.php?name=oberon§ion=com...
For Modula-3 it is a bit harder, because of DEC/Olivetti being acquired by Compaq, which was then acquired by HP. So lots of sites are full with broken links.
So the older books are a better source than the Internet.
But still you can get some info here:
http://ftp.labs.hp.com/ftp/pub/dec/SRC/
http://www-spin.cs.washington.edu/
Burroughs overview:
http://www.smecc.org/The%20Architecture%20%20of%20the%20Burr...
It lives on as Unisys MCP mainframes. There are some more informations when searching for it.
Algol-68RS used at Royal Navy:
https://en.wikipedia.org/wiki/ALGOL_68RS
This is just a small taste. If one cares about history of computing there are lots of other resources scattered around the web and libraries.
dynamic memory allocation is frowned upon... to many issues with fragmentation and non-deterministic behaviour.