In Ruby, What Is Stored on the Stack

Does ruby ever keep variables in hard drive?

The Ruby Language Specification does not mandate or forbid any particular storage strategy. Any implementation is free to store values anywhere they want in any way. The Specification only says what the result of running a Ruby program should be, not how the program is being run. (Just like any other language specification.)

In SmallRuby, for example, objects may under some circumstances be stored on the disk. And the whole purpose of MagLev is to have a Ruby implementation which can deal with heaps that are orders of magnitude larger than RAM, by storing them in a distributed cluster on disk.

Ruby immutability of strings and symbols (What if we store them in variables)

Ruby variables are references to objects, so when you send a method to a variable, the object it references is the context in which it is evaluated. It's probably more clear to look at the first image in the top rated answer (below the accepted answer) here.

So, to figure out what's going on, let's dig into the documentation a bit and see what happens with your code snippet.

Ruby's Symbol class documentation:
https://ruby-doc.org/core-2.5.0/Symbol.html

Symbol objects represent names and some strings inside the Ruby interpreter. They are generated using the :name and :"string" literals syntax, and by the various to_sym methods. The same Symbol object will be created for a given name or string for the duration of a program's execution, regardless of the context or meaning of that name. Thus if Fred is a constant in one context, a method in another, and a class in a third, the Symbol :Fred will be the same object in all three contexts.

Ruby's Object#object_id documentation:
https://ruby-doc.org/core-2.5.1/Object.html#method-i-object_id

Returns an integer identifier for obj.

The same number will be returned on all calls to object_id for a given object, and no two active objects will share an id.

So here's what's happening step-by-step:

# We create two variables that refer to the same object, :foo
var1 = :foo
var2 = :foo

var1.object_id = 2598748
var2.object_id = 2598748
# Evaluated as:
# var1.object_id => :foo.object_id => 2598748
# var2.object_id => :foo.object_id => 2598748

As discussed in the first link above, Ruby is pass-by-value, but every value is an Object, so your variables both evaluate to the same value. Since every symbol made of the same string ("foo" in this case) refers to the same object, and Object#object_id always returns the same id for the same object, you get the same id back.

What and where are the stack and heap?

The stack is the memory set aside as scratch space for a thread of execution. When a function is called, a block is reserved on the top of the stack for local variables and some bookkeeping data. When that function returns, the block becomes unused and can be used the next time a function is called. The stack is always reserved in a LIFO (last in first out) order; the most recently reserved block is always the next block to be freed. This makes it really simple to keep track of the stack; freeing a block from the stack is nothing more than adjusting one pointer.

The heap is memory set aside for dynamic allocation. Unlike the stack, there's no enforced pattern to the allocation and deallocation of blocks from the heap; you can allocate a block at any time and free it at any time. This makes it much more complex to keep track of which parts of the heap are allocated or free at any given time; there are many custom heap allocators available to tune heap performance for different usage patterns.

Each thread gets a stack, while there's typically only one heap for the application (although it isn't uncommon to have multiple heaps for different types of allocation).

To answer your questions directly:

To what extent are they controlled by the OS or language runtime?

The OS allocates the stack for each system-level thread when the thread is created. Typically the OS is called by the language runtime to allocate the heap for the application.

What is their scope?

The stack is attached to a thread, so when the thread exits the stack is reclaimed. The heap is typically allocated at application startup by the runtime, and is reclaimed when the application (technically process) exits.

What determines the size of each of them?

The size of the stack is set when a thread is created. The size of the heap is set on application startup, but can grow as space is needed (the allocator requests more memory from the operating system).

What makes one faster?

The stack is faster because the access pattern makes it trivial to allocate and deallocate memory from it (a pointer/integer is simply incremented or decremented), while the heap has much more complex bookkeeping involved in an allocation or deallocation. Also, each byte in the stack tends to be reused very frequently which means it tends to be mapped to the processor's cache, making it very fast. Another performance hit for the heap is that the heap, being mostly a global resource, typically has to be multi-threading safe, i.e. each allocation and deallocation needs to be - typically - synchronized with "all" other heap accesses in the program.

A clear demonstration:
Sample Image

Image source: vikashazrati.wordpress.com

Ruby process memory structure

It is likely that your application is allocating objects that are then groomed by the Garbage Collector. You can check this with a call to GC.stat

Ruby does not release memory back to the operating system in any meaningful way. (if you're running MRI) Consequently, if you allocate 18GB of memory and 15GB gets garbage collected, you'll end up with your ~3GB of heap data.

The Ruby MRI GC is not a compacting garbage collector, so as long as there is any data in the heap the heap will not be released. This leads to memory fragmentation and the values that you see in your app.



Related Topics



Leave a reply



Submit