Memory Management

I’m trying to understand how Julia allocates memory. I ran into some trouble when I tried to run multiple threads with the below code because memory wasn’t being cleared.

struct Container
    field::Vector{Matrix{Float64}}
end
function f!(x::Container)
    push!(x.field, randn(10, 10000))
end
function g!(x::Container, n::Int64)
    for _ in 1:n
        f!(x)
    end
end

X = randn(10, 10000);
c = Container([X]);
g!(c, 10000)
GC.gc()
# c is about 7.5GiB in size now, and I have about 8.3GB in memory occupied on System Monitor.

c = nothing
GC.gc()
# Memory usually isn't freed here, sometimes it is partially.

If I keep running this snippet over and over again

X = randn(10, 10000);
c = Container([X]);
g!(c, 10000)
GC.gc()

I never run out of memory, but it fluctuates between 60 and 80 percent of my total memory (32 GB). So it seems that even though I am explicitly calling gc, Julia is following some other rule for allocating memory. This becomes a problem when I’m using Threading or Distributed.

I’m guessing that maybe Julia is putting some memory on the side in case I run my code again and need to build c. But is there a way to force Julia to clear the memory? Is the memory even being used or is the top command reporting my memory wrong?

What part of RSS is used by the Julia process? It may very well be that Julia has actually freed the memory, but the OS hasn’t yet taken it back, so to speak. Of course, it’s also possible that Julia holds onto the memory for a bit - it’s likely that you’re going to allocate more objects of that size if you’ve allocated so many of them before.

As a general strategy, it’s a good idea to reuse memory if possible, especially in multithreaded code.

ccall(:malloc_trim, Int32, (Int32,), 0) can get Julia to release more memory to the OS

2 Likes

Which version? It could be the bug before 1.10 like The improvement of memory management in multihtreading Julia 1.10beta is amazing!

This works. What exactly is going on here?

1 Like

As I said, the GC may have already called free. malloc_trim explicitly asks the underlying malloc to reclaim the memory that free has been called on, but not yet given up for new allocations. See also malloc_trim(3) - Linux manual page.

That’s why I asked for the resident set size :slight_smile: In general, such “used memory” is not a problem; malloc will in the future reclaim the memory by itself, when it’s required.

2 Likes

Thank you. I apologize I was just not familiar with what a resident set size is :laughing: Is there any way to report a more accurate measure of used memory instead of having to call malloc_trim?

It’s unfortunately a bit difficult to estimate that; from Julias POV, it already freed the memory. From the OS POV, Julia is still “using” that memory, because it hasn’t yet checked whether it actually does. If you want to know how much memory is currently used from Julias’ POV, I’m not aware of any API providing those numbers at the moment (at least in a “total memory” kind of view; there is Base.summarysize, but that requires passing an object and IIRC that call has some issues as well should `Base.summarysize` include alignment? · Issue #32881 · JuliaLang/julia · GitHub).