Ruby 2.x source code learning: memory management & GC

Preface

data structure

object space

rb_objspace_t

RVALUE

heap

rb_heap_t

heap page

heap_page_body

heap_page_header

heap_page_body

heap_page

memory management

Heap initialization (Init_heap)

Reference resources Ruby 2.x source code learning bootstrap The Ruby interpreter initializes the heap by calling the Init_heap function at startup

// gc.c

void Init_heap(void) {
    rb_objspace_t *objspace = &rb_objspace;
    ...
    heap_add_pages(objspace, heap_eden, gc_params.heap_init_slots / HEAP_PAGE_OBJ_LIMIT);
    ...
}

A slot corresponds to a RVALUE, and gc_params.heap_init_slots is the number of RVALUE objects that are initially idle for the interpreter, and HEAP_PAGE_OBJ_LIMIT is the number of RVALUE objects that a heap page can accommodate, so dividing the two into the number of heap pages that need to be added initially

// gc.c

static void heap_add_pages(rb_objspace_t *objspace, rb_heap_t *heap, size_t add) {
    size_t i;

    heap_allocatable_pages = add;
    heap_pages_expand_sorted(objspace);
    for (i = 0; i < add; i++) {
        heap_assign_page(objspace, heap);
    }
    heap_allocateable_pages = 0;
}

Object memory allocation

How do new functions create objects

Let's use RubyVM::InstructionSequence to look at the virtual machine instructions generated by String.new

irb> code =<<EOF
irb> s = String.new
irb> EOF
irb> puts RubyVM::InstructionSequence.compile(code, '', '', 0, false)
== disasm: <RubyVM::InstructionSequence:<compiled>@>====================
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] s
0000 putnil
0001 getconstant      :String
0003 send             <callinfo!mid:new, argc:0, ARGS_SKIP>
0005 dup
0006 setlocal         s, 0
0009 leave
=> nil

When calling compile, the last one is compile options, which specifically uses fase to disable compile optimization, track the implementation of send instructions, and create objects that eventually fall into the newobj_of function.

// gc.c

static inline VALUE newobj_of(VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, int wb_protected) {
    rb_objspace_t *objspace = &rb_objspace;
    VALUE obj;

    if (!(during_gc || ruby_gc_stressful || gv_event_hook_available_p(objspace)) &&
        (obj = heap_get_freeobj_head(objspace, heap_eden)) != False) {
        return newobj_init(klass, v1, v2, v3, wb_protected, objspace, obj);
    } else {
        ...
    }
}

if condition judgment consists of two parts. The former part is used to determine whether an obj (RVALUE) can be allocated from the free list of eden heap using heap_get_free obj_head.

// gc.c

static inline VALUE heap_get_freeobj_head(rb_objspace_t *objspace, rb_heap_t *heap) {
    RVALUE *p = heap->freelist;
    if (LIKELY(p != NULL)) {
        heap->freelist = p->as.free.next;
    }
    return (VALUE)p;
}

GC

gc params

start (trigger)

Visual analysis triggers GC when virtual machines cannot allocate space for new objects. Let's review an else branch of allocating space for objects.

// gc.c

static inline VALUE newobj_of(VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, int wb_protected) {
    rb_objspace_t *objspace = &rb_objspace;
    VALUE obj;

    if (/* Objects can be allocated from the free list of heap page s */) {
        ...
    } else {
        return wb_protected ? newobj_slowpath_wb_protected(klass, flags, v1, v2, v3, objspace):
            newobj_slowpath_wb_unprotected(klass, flags, v1, v2, v3, objspace);
    }
}

Both functions selected by wb_protected will eventually call newobj_slowpath

// gc.c

static inline VALUE newobj_slowpath(VALUE klass, VALUE flags, VALUE v1, VALUE v2, VALUE v3, rb_objspace_t *objspace, int wb_protected) {
    VALUE obj;
    if (UNLIKELY(during_gc || ruby_gc_streeful)) {
        // Object allocation in GC process is considered BUG
        if (during_gc) {
            dont_gc = 1;
            during_gc = 0;
            rb_bug(...);
        }
        // If the ruby_gc_stress flag is set, GC is forced every time an object is allocated.
        if (ruby_gc_stressful) {
            // GC function: garbage_collect
            if (!garbage_collect(objspace, FALSE, FALSE, FALSE, GPR_FLAG_NEWOBJ)) {
                rb_memerror();
            }
        }
    }

    obj = heap_get_freeobj(objspace, heap_eden);
    ...
    return obj;
}

We have found an entry that triggers GC: when the object cannot be allocated from heap page free list and the ruby_gc_streeful flag is set. Let's look at the heap_get_freeobj function again.

// gc.c

static inline VALUE heap_get_freeobj(rb_objspace_t *objspace, rb_heap_t *heap) {
    RVALUE *p = heap->freelist;

    // Loop until RVALUE is successfully allocated
    while (1) {
        if (LIKELY(p != NULL)) {
            heap->freelist = p->as.free.next;
            return (VALUE)p;
        } else {
            p = heap_get_freeobj_from_next_freepage(objspace, heap);
        }
    }
}

The while loop attempts to call heap_get_freeobj_from_next_freepage until freelist is available

// gc.c

static RVALUE *heap_get_freeobj_from_next_freepage(rb_objspace_t *objspace, rb_heap_t *heap) {
    struct heap_page *page;
    RVALUE *p;
    // Loop call heap_prepare until heap free pages are available
    while (heap->free_pages == NULL) {
        heap_prepare(objspace, heap);
    }
    ...
    return p;
}

heap_prepare function:

// gc.c

static void heap_prepare(rb_objspace_t *objspace, rb_heap_t *heap) {
    // Keep cleaning up!!!
#if GC_ENABLE_LAZY_SWEEP
    if (is_lazy_sweeping(heap)) {
        gc_sweep_continue(objspace, heap);
    }
#endif
    // Continue marking!!!
#if GC_ENABLE_INCREMENTAL_MARK
    else if (is_incremental_marking(objspace)) {
        gc_marks_continue(objspace, heap);
    }
#endif
    // gc_start start start tag & clearance
    if (heap->free_pages == NULL &&
    (will_be_incremental_marking(objspace) || heap_increment(objspace, heap) == FALSE) &&
        gc_start(objspace, FALSE, FALSE, FALSE, GPR_FLAG_NEWOBJ) == FALSE) {
    rb_memerror();
    }
}

mark

Swep (Clearance)

summary

Tags: Ruby REST

Posted on Sat, 06 Apr 2019 22:36:30 -0400 by bobc