Garbage collection from C / Storing jl_value_t * in global C variables

embedding

#1

Hi all,

So, I’ve been trying to understand how to properly do memory management (i.e., preventing premature garbage collection) in Julia from the C API. I know there are posts on this topic already, but I feel like I’m still missing something. Apologies if any of this is duplicated, I’d really appreciate the help.

As I understand it, the basic structure of memory management in Julia is the following stack-based situation:

void my_function() {
    jl_value_t *val = some_function_that_allocates_a_new_julia_object();
    // the garbage collector knows about val, and it can decide to free it in an jl_* call

    JL_GC_PUSH1(&val);
    // the garbage collector now holds another reference to val, so it will not be freed

    // do things with val

    JL_GC_POP();
    // the garbage collector may now free val whenever it feels like
}

Is this correct? If yes, my question is how do I handle a situation like this:

void my_function() {
    jl_value_t *val = some_function_that_allocates_a_new_julia_object();
    // the garbage collector knows about val, and it can decide to free it in an jl_* call

   // Here I want to safely store val in a global C data structure (one example would be a std::vector of jl_value_t *) and 
   // much later, in some entirely unrelated function, I want to allow the garbage collector to dispose of it. How do I do this? 
   // As far as I understand, JL_GC_PUSH and JL_GC_POP don't make sense here because this isn't stack-based
}

Thanks!

Irwin


#2

Store it to a julia global value at the same time. How you get a handle to such a value depends on your application.


#3

Is the basic idea here to generate a random name, use jl_set_global to store it in the global variable, then set the global variable to nothing when it is ready to be GC’ed? It’s not possible to remove the global variable, is it?


#4

No, you create a global array/dict and store to it.


#5

Thanks, this makes sense and it totally answers my question!

Quick follow-up: Is there anything in the C API for inserting a value into an OrderedDict? I see jl_arrayset for arrays, but nothing for dicts.


#6

Ref Libjulia / c-api : how to unbox SparseMatrixCSC
https://groups.google.com/forum/#!searchin/julia-users/$20Julia$20embedding$20--$20Create$20Dict$20variable$20from$20C|sort:relevance/julia-users/E4FNM7m_Alw/3AL1bcJmAgAJ

There isn’t and won’t be one since it’s defined in julia and there’s no standard C api for it.
Define your own in julia (or use getindex/setindex etc) and access it with cfunction or jlcall*.


#7

I recently tried to do the same thing, but the performance using a dictionary was very poor. It essentially caused a 10x slowdown. The reason, I believe is due to cache misses in the dictionary when there are a large number of objects being tracked.

I managed to get it down to a 1.5-2.0x slowdown using a 2 tiered system. Essentially I punt on Julia using more or less consecutive memory locations where many small allocations of the same size are requested. And even when that is not the case, the addresses will be relatively close most of the time, at least for large blocks of memory Julia is internally requesting from the system for GC pools.

The first tier of my hack shifts the address of the allocated object right by 24 bits, then looks this address up in a dict. The associated value for this key is a secondary structure (an array) which has a count associated with it. The allocated object is stored to the array (some hackery is also required here to prevent additional slowdowns), always storing new items at the next location in the array to get good cache locality.

Objects are never removed from the array, but a count is associated with the array. When an object is no longer needed, the count is simply reduced. When the count reaches zero, the entire key value pair is removed from the dict.

This still has unacceptable performance, and it’s an awful hack which has the potential to go really wrong, but in the absence of a mechanism to deal with foreign objects in the Julia GC, (or possibly a free list on the C/C++ side storing an index into a global Julia array, if that’s an option for your application), it’s an acceptable solution for now.

Obviously it’s only a temporary workaround until there’s an interface for doing it properly.


#8

I should add that this is NOT an appropriate solution if you have a large number of objects at irregular intervals which are rarely or never likely to be freed.

The hack I described is good for large numbers of small objects that are guaranteed to be freed fairly soon, and only a very small number of objects that are going to have a long lifetime.

More complicated situations would require something like an auxiliary garbage collector to properly manage, if they can’t be managed from the C side.