When you write and run a computer program you are processing data. You load data, create more data, process it and so on. All this data takes up memory on your computer. Without any other action the memory starts to look like a kid’s bedroom – all toys, and stale clothes, and empty cans and magazines and books everywhere. There are typically two ways programming languages deal with this clutter.
One is the “clean your room or I’ll blow up the house” method. I realize this is not what parents say, but this is what happens in languages like C++ where you – the programmer – are responsible for cleaning up after yourself. If you don’t your program crashes and burns and exits out with a nasty error message. If your operating system is not a grown up operating system, it often freezes up your whole computer.
The other is automatic memory management, which is a bit like having a housekeeping service. Common lisp is a garbage collected language. Which means that the runtime of the language takes care of the clutter for you. The service sends over a person who goes through your room and tidies up. As you can imagine, this is a lot easier for you, since now you don’t have to worry about all this low level housekeeping stuff and can concentrate on the fun things in life/computer science.
So imagine my surprise when I ran the following bit of code and my house blew up (program memory space, not my house literally, but you’ve caught on)
(defun random-string (length) (with-output-to-string (stream) (let ((*print-base* 36)) (loop repeat length do (princ (+ (random 26) 10) stream))))) (defun insert (count) (let ((ht (make-hash-table :test 'equal))) (loop repeat count do (setf (gethash (random-string 30) ht) 1)))) (defun gc-test (loop-count ins-count &optional (nudge-gc nil)) (loop repeat loop-count do (insert ins-count))) (gc-test 20 1000000 nil)
I wasn’t actually doing this exact thing, but the principle was the same: I was inserting keys into a hash table which got larger and larger, and then I discarded the hash table. After a few cycles of this BAM! my program crashed and SBCL printed out the delightfully campy:
Heap exhausted, game over.
Common Lisp’s handy (room) function prints out the memory usage of the program, and when I instrumented my program with it I saw this:
Now you can see that up to the 4th iteration housekeeping kind of keeps up, though there is this funny sawtooth pattern where housekeeping slacks off on the first pass, does a thorough job on the second, slacks off on the third, catches up on the fourth. But then, the union goes on strike! Housekeeping refuses to show up and the dirty pizza boxes start to pileup and then the floor collapses. Contrary to what your friends working in sales will tell you, not all graphs that rise exponentially on the right hand side are good.
SBCL has a neat function
that allows you to manually call for housekeeping, and I modified my program to do this each time I discarded the hash-table, and when I reran the program I saw this:
So, manually forcing the garbage collector to run does do the trick but it made me a little disappointed with SBCL’s implementation. I suspect commercial Lisp compilers do a better job. If any one has a guess as to why the GC fails in this case, please drop a line.
In case you are interested, the function, instrumented with (room) and with the garbage collection forcing looks like this
(defun gc-test (loop-count ins-count &optional (nudge-gc nil)) (loop repeat loop-count do (progn (insert ins-count) (when nudge-gc (sb-ext:gc :full t)) (room nil)))) (gc-test 20 1000000 nil)