This was originally opened as a github issue #28909, though I was directed here for the question.
The behaviour is that, even though the loop itself is pure(except for printing), and that results can be safely discarded after execution, Julia still consumes large amount of memory, seeming to keep the entire array being looped over in memory. Ideally, the used items should be freed.
This is the code I used. The goal was to exhaustively search for specific pattern in the entire range of 32-bit random number:
using Random
# dice rolling
function ndx!(rng, sp, N)
L = 0
for n in 1:N
L += rand(rng, sp)
end
L
end
#generate a sequence
function gensum!(seed)
rng = MersenneTwister(seed)
d6 = Random.Sampler(rng, 1:6)
ndx!(rng,d6,21)
end
l = Threads.SpinLock()
Threads.@threads for i in 0x00000000:0xFFFFFFFF
s = gensum!(i)
if (i | 0xFFF00000 == 0xFFF00000)
lock(l)
if (i | 0xFF000000 == 0xFF000000)
if (i | 0xF0000000 == 0xF0000000)
print(";")
end
print(",")
end
print(".")
unlock(l)
end
if (s == 126)
lock(l)
println("Found Legendary Character, seed ",i)
unlock(l)
end
end
I did not shrink my example because it cost me 3 hours to run under 12 threads, and I did not want to rerun the program. During the beginning, memory usage rapidly climbs up to 15G rapidly, and stays steady throughout the majority of the computation. However, when the computation nears the end (I presume the last minute or so), the memory usage quickly doubles to 30G.
I cannot explain this behaviour since I am unfamiliar with internal implementation of Julia, but I strongly suspect that it is caused by GC. I did not use alternative loops due to parallel computation, and attempting a map
operation using filter(condition,map(f, 0x00000000:0xFFFFFFFF))
immediately throws an out of memory exception(possibly due to the lack of lazy evaluation).
@rfourquet has suggested that GC might not be fast enough to clean up the MersenneTwister
objects, and provided a workaround which I have not tested yet, and is posted below:
const RNGs = [MersenneTwister(0) for i in 1:Threads.nthreads()]
function gensum!(seed)
rng = Random.seed!(RNGs[Threads.threadid()], seed)
d6 = Random.Sampler(rng, 1:6)
ndx!(rng,d6,21)
end
I do not know whether GC have trouble removing the MT object, as memory usage increases only at the beginning (to 15G
), and the end (to 30G). I thought objects are cleaned soon after they are out of scope.