We have been trying to understand the garbage collector behaviour, since we had some code for which our machine is running out of memory in a matter of an hour.
We already realised that Julia isn't responsible for memory we allocate on the C side unless we use jl_gc_counted_malloc, which we now do everywhere. But it still uses masses of memory where we were roughly expecting no growth in memory usage (lots of short-lived objects and nothing much else). The behaviour of the gc on my machine seems to be to allocate objects until 23mb of memory is allocated, then do a jl_gc_collect. However, even after reading as much of the GC code in C as I can, I still can't determine why we are observing the behaviour we are seeing. Here is a concrete example whose behaviour I don't understand: function doit2(n::Int) s = BigInt( 2234567876543456789876545678987654567898765456789876545678) for i = 1:n s += i end return s end doit(10000000000) This is using Julia's BigInt type which is using a GMP bignum. Julia replaces the GMP memory manager functions with jl_gc_counted_malloc, so indeed Julia knows about all the allocations made here. But what I don't understand is that the memory usage of Julia starts at about 124mb and rises up to around 1.5gb. The growth is initially fast and it gets slower and slower. Can someone explain why there is this behaviour? Shouldn't jl_gc_collect be able to collect every one of those allocations every time it reaches the collect_interval of 23mb (which remains constant on my machine with this example)? As an alternative experiment, I implemented a kind of bignum type using Julia arrays of UInts which I then pass to GMP low level mpn functions (which don't do any allocations on the C side). I only implemented the + operator, just enough to make this example work. The behaviour in this case is that memory usage is constant at around 124mb. There is no growth in memory usage over time. Why is the one example using so much memory and the other is not? Note that the bignums do not grow here. They are always essentially 3 or 4 limbs or something like that, in both examples. Some other quick questions someone might be able to answer: * Is there any difference in GC behaviour between using & vs Ref in ccall? * Does the Julia GC have a copying allocator for the short lived generation(s)? * Does the Julia GC do a full mark and sweep every collection? Even of the long lived generation(s)? If not, which part of the GC code is responsible for deciding when to do a more involved sweep vs a faster sweep. I am having some trouble orienting myself with the code, and I'd really like to understand it a bit better. * Can someone confirm whether the "pools" mentioned in the GC code refer to pools for different sized allocations. Are there multiple pools for the same sized allocation, or did I misunderstand that? Thanks in advance. Bill.