I have some embedded Julia code in C++ where I absolutely need that no gc is triggered under the hood. Could I disable the gc and re-enable soon after? Would this mean that, if some temporary buffers or variables get allocated in between the two calls by some function, it will not be possible to garbage collect them at a later stage? Or will they be referenced in the gc even if they were created in a moment where the gc was disabled?
Also, are all my arrays declared before the gc_enable(0) available back for the gc when I te-enable it?
I’m pretty sure that it makes no difference if GC was enabled or not when an object is created.
Julia does not use reference counts for GC, IIRC, it’s some sort of Mark & Sweep garbage collector
So, I can be sure that temporary arrays or objects that are created when the GC is disabled will eventually be caught by the garbage collector when it is enabled back again, am I correct?
Yes, nothing is done differently with respect object allocation.
Perfect, thanks a lot.
I would also like to ask another sort of related question: In the context of using Julia in a C++ environment, through the C API, is there a way to delete the objects that I might be allocating with jl_calls myself, without relying on the garbage collection (and assuming that I disable it for the whole duration of the program)? This would be particularly useful as in my code each Julia struct / mutable struct is wrapped in a C++ class, and I could just free the memory used by julia in the destructor of my classes. Also, is there a way to tell the GC to not register some things, so that I can handle their lifetime myself?
I really don’t think you should be disabling the GC.
The GC only works on things allocated from Julia, not things you might allocate in C/C++ etc.
If you have some Julia struct (must be mutable for this) that holds a pointer to some C/C++ resource,
then you need to make a finalizer, so that if the Julia struct does get GCed, the resources do get freed up correctly.
[I always create a method called `finalizer!`, for my types that wrap C/C++ resources, and also call finalize with the new object and that function, so that I can finalize it early if I want, because it’s unreliable to depend on when the GC will be called]
Maybe I didn’t explain myself correctly. I don’t mean to collect things that I allocate in C++ through Julia, but some ways to delete Julia allocated stuff, in C/C++, (like an allocated struct (jl_value*) with a jl_call) myself. For example with a call to a function like" jl_delete(jl_value* object_to_delete)" (if such function existed) in the destructor of a C++ class, bypassing the GC. I am strictly talking about using Julia through the C API in C/C++.
OK. That I haven’t done, but I don’t think you can “delete” anything. To retain any Julia object, it must be rooted somewhere on the Julia side, so if you remove that reference, then the GC can free it up later.
No you can’t. It also won’t help much. In some sense the existance of
delete is one of the reason GC can be faster than manual memory management. Freeing things (so that they can be reused later) can be as expensive or more expensive than allocation, especially when it needs to be done incrementally.
Ok, so I will stick with the enabling and desabling of the GC in critical sections (in this case, real-time audio callbacks) where I cannot afford to perform any of these callbacks late, and cannot wait for an eventual GC call. So, I am left now with two more questions concerning edge cases:
- What if I am allocating even the slightest amount of memory when the GC is disabled, and this would get the GC to pass the threshold after which it should perform garbage collection. Would this mean that the collection will just be performed as soon as I enable it back again?
- What if the GC pool is full but all the stuff that it has inside is referenced somewhere, not allowing it to delete anything. Will the GC just allocate more memory to compensate and use that for new allocations? Or what does happen in this case?
Undefined. Right now it should never happen when there’s only one thread but that’s not something you should depend on. OTOH, there’s little advantage to do that.
There isn’t a fixed size pool so that cannot happen. (Unless you count actual OOM as one.)
Ok. Is it just undefined the moment when I turn the GC back on, or the state of the GC itself? If I then allocate something after I re-enabled the GC, would it then act normally? And would it pick also the stuff the should have pushed the threshold up when the GC was disabled?
Sorry I don’t understand what you are asking about. I’m saying that whether there’ll be a GC when you reenable it is undefined. I’m not taking about something that is a function of time so I’m not sure what you mean by “undefined the moment when I turn the GC back on”. The state of the GC is also not what’s undefined. FWIW, I am NOT saying it’s undefined behavior in C/C++ sense. What you are describing is a valid thing to do but whether/when the GC will happen with regard to the moment you reenable the GC is not defined.
Nothing should have acted abnormally at any moment.
What do you mean by “pick”?
In fact, I was interpreting the undifined behaviour in a strict C/C++ sense. It is much clearer now, thanks for the clarification.
I was asking if the GC would still be able to catch the allocated objects that would trigger the threshold in a moment when the GC is disabled. I realize now that I just misunderstood the undefined word, and that these object will be caught when the GC will eventually happen after its re-enabling, in an undefined moment in time.
I am sorry to bring the topic back, but I have another very basic question to ask and I don’t want to open a new thread just for this reason. The question is: would a
gc() call be performed even if the gc is disabled? I know that it is not recommended to perform the
gc() calls by myself, but I would need this for a very specific case.
Also, does Julia’s GC use an allocation pool? If so, is there a fixed size? Or does it malloc every new object?
Yes (multiple). No (or yes, it’s size segregated, so many different ones, assuming you are talking about the cell size not the pool size). No, that’ll be crazily expensive.
I was actually meaning the pool size. Is there one? Or at least, is there a starting one, before it needs to do any resizing if an allocated objects surpasses the limit?
Well, the pool certainly has a size (at any time). There’s no starting size, not more than however many pages the startup uses. There’s 16k page size and all the pool pages are allocated on the fly.