The goal of this project was to research memory management in Ruby. Ruby deals with memory automatically so that the user does not need to deal with allocating space when memory is needed and deallocating space when the memory can be freed. Thus, Ruby hides memory allocation from the user. Luckily, it has a useful hash table called GC (for garbage collector) that allows the user to view how much memory has been allocated and freed. I will give some examples of when memory is allocated, freed or maintained for possible future use.
The first example is with a global constant:
global_list is a global constant because it is defined in the global scope. Since there is a chance that it will be used in the future, the memory associated with it cannot be garbage collected. This example also shows how GC works: by gathering the total freed objects and total allocated objects before and after the loop is executed, the difference shows how much memory was allocated and freed just for the loop. In this case:
Thus, a lot of memory is allocated in the loop, but almost none is garbage collected. If the user does not know that this happens, this could be a problem.
While reading about memory management in Ruby, I read about how there is a function freeze that can help with this problem. For example:
The idea is that freeze indicates that a certain piece of memory is going to be used many times. Therefore, it is set aside and all of the times it is used, a reference points to the one object rather than making a new one each time through the loop. In theory, very little memory will be allocated and very little memory will be freed. This is a much better way to do this. Unfortunately, my ruby version is old and does not treat this situation correctly. It still allocates a lot of memory:
Another case is when a local variable is given a lot of memory in a loop or other inner scope, it is freed by garbage collection when the scope is done. For example:
A list with 100,00 strings in it is also made in this example, but the variable it is stored in is in scope of the outer do loop. Therefore, after the end statement for that loop, that memory can no longer be referenced since the variable is no longer in scope, so it is garbage collected:
This shows that all of the objects that were allocated in the loop were freed at the end of the loop by garbage collection.
The last example I will give is when a lot of memory is allocated in a function. This is similar to the global constant problem, and is something that programmers need to be aware of:
Since the list could be used in the future, the memory cannot be garbage collected while the function is executing:
But, once the function is done executing, the variable list can no longer be accessed. Therefore, it can be garbage collected:
Thus, the overall message is that although this type of automatic memory management seems easier, the user needs to be more careful in programs that deal with large amounts of memory because there is no way to explicitly free memory to make room for something else.
The goal of the second part of this task was to try to determine when garbage collection happens in Ruby. The basic way tht garbage collection works is that Ruby waits until it is almost out of memory, then stop executing to determine what memory can be freed to make room for something new. Thus, garbage collection does not happen in parallel and will show up in a timed program as a loop that takes much longer than the res.
The func in this example simply makes a list then loops 300 times and adds "a string" to it, just like the examples in part 1. Since the function does the same thing every time, one would expect that it would take the same amount of time for each iteration. But, this is the output of the program:
In this example, it takes about 0.09-0.11 seconds for the 1st-24th iterations. These are pretty consistent times. The 25th iteration however took 0.88 seconds, which is 8 times as long as the others. Since nothing different is happening in this iteration, it is reasonable to say that this is the iteration where garbage collection happened. Thus, garbage collection took about 0.78 seconds during this iteration. I expected that garbage collection would happen again around iteration 50 since 25-50 should use the same amount of memory as 0-25, but garbage collection did not happen again until the 100th iteration, where it took almost a whole second:
Thus, since Ruby garbage collects by stopping the program when memory is getting scarce, it is easy to see when garbage collection happens.
- I wrote two haikus in the same program -
The first shows how global constants are not garbage collected after being allocated. The second shows how local variables are garbage collected after the lifetime of their scope. This is important to realize when the programmer is dealing with enough memory that they could be close to running out of memory. Using this memory, the programmer can make sure not to make a variable a global constant unless it really needs to be.
- I rewrote the func example above in C explicitly handling the memory that Ruby does implicitly.
This creates an array for 10,000 strings, then in the function loops through and mallocs enough room for the string and adds it to the list. Then the function is called in the main loop, and since in Ruby the memory is not deallocated in the function, there is a loop after the function to deallocate all of the memory added to the list.