【问题标题】:Memory doesn't get freed in multiple threads内存不会在多个线程中释放
【发布时间】:2018-09-18 07:08:12
【问题描述】:

我一直在使用 Julia 对大量数据进行多线程处理,并观察到一种相互交织的模式。内存使用量(由htop 报告)缓慢增长,直到进程被操作系统杀死。项目复杂,很难做出合适的MWE,但我做了一个简单的实验:

using Base.Threads
f(n) = Threads.@threads for i=1:n
    x = zeros(n)
end

现在,我为各种 n 值(在我的 64 Gb 机器上介于 10^4 和 10^5 之间)反复调用 f(n)。结果是有时一切都按预期工作,内存在返回后被释放,但有时情况并非如此,htop 报告的已用内存量保持在一个很大的值,即使似乎没有进行计算:

显式垃圾回收GC.gc() 帮助不大,释放了一些内存,但只有一小块。此外,有时在函数f 的循环中调用GC.gc() 会有所帮助,但问题仍然存在,当然,性能也会降低。退出 Julia 后,分配的内存恢复正常(可能被操作系统释放)。

我已经阅读了有关 julia 如何管理其内存以及仅当内存计数大于某个值时才释放内存的信息。但在我的情况下,它导致进程被操作系统杀死。在我看来,GC 不知何故忘记了所有分配的内存

谁能解释一下这种行为,以及如何通过重复调用GC.gc()来防止它在不减慢代码速度的情况下发生?为什么垃圾回收会以这种方式被破坏?

更多细节:

  • 仅当在多个线程中处理大数据(分配大量内存)时才会发生这种情况。我无法仅用一个线程重现相同的内容。
  • 我检查了我的代码,了解我所知道的所有可能增加内存消耗的内容(全局变量、类型稳定性……),但没有任何积极的结果。据我所知,这些问题会导致更高的内存分配,我的问题是从函数返回后内存没有释放
  • 这是我的versioninfo 输出:

    julia> versioninfo()
    Julia Version 0.7.0
    Commit a4cb80f3ed (2018-08-08 06:46 UTC)
    Platform Info:
      OS: Linux (x86_64-pc-linux-gnu)
      CPU: Intel(R) Xeon(R) Platinum 8124M CPU @ 3.00GHz
      WORD_SIZE: 64
      LIBM: libopenlibm
      LLVM: libLLVM-6.0.0 (ORCJIT, skylake)
    Environment:
      JULIA_NUM_THREADS = 36
    

【问题讨论】:

  • 我无法在 Julia v1.1 中重现此行为(未尝试 v0.7)。如果它仍然发生,那么这将是一个错误,您可能应该在 julialang slack 上询问或在 GitHub 上的 Julia 存储库上打开一个问题。

标签: garbage-collection julia


【解决方案1】:

由于很久以前有人问过这个问题,希望这种情况不再发生——尽管没有 MWE 我无法确定。

然而,值得注意的一点是 Julia 垃圾收集器是单线程的。即,无论您有多少线程生成垃圾,都只有一个垃圾收集器。

因此,如果您要在并行工作流程中生成大量垃圾,通常建议使用多处理(即 MPI.jl 或 Distributed.jl)而不是多线程。在多处理中,与多线程相比,每个进程都有自己的 GC。

【讨论】:

    【解决方案2】:

    我在使用 Distributed.jl 时也遇到过类似的问题。同样,我在每个工作进程上都尝试了GC.gc(),这有助于在一定程度上减轻内存消耗,但对于大型任务,它最终会削弱机器/进程,唯一的解决方法是重新启动 Julia。

    我可以在 Julia 1.7.1 上重现你的 MWE:

    Julia Version 1.7.1
    Commit ac5cc99908 (2021-12-22 19:35 UTC)
    Platform Info:
      OS: Linux (x86_64-pc-linux-gnu)
      CPU: AMD EPYC-Rome Processor
      WORD_SIZE: 64
      LIBM: libopenlibm
      LLVM: libLLVM-12.0.1 (ORCJIT, znver2)
    Environment:
      JULIA_NUM_THREADS = 32
    

    我还有一个使用 CSV.jl 的 MWE,它将使用所有可用的 CPUS 来读取一个大文件:

    ##start julia such that multiple processes are available i.e. Threads.nthreads()>1
    Threads.nthreads()
    using CSV,DataFrames
    ## memory usage in GB after starting julia and loading packages:
    used_mem() = (println("$(round((Sys.total_memory()-Sys.free_memory())/2^30 -9))G used"))
    used_mem()
    
    ## create large file for CSV.jl to read (you can adjust n as appropriate for your machine, this maxes out at about 7.5GB on my machine) 
    n = 100000000
    CSV.write("test.csv",DataFrame(repeat([(1,1,1)],n)))
    used_mem()
    ##during the above process, memory peaks, but running garbage collection returns it to original state
    GC.gc()
    used_mem()
    
    ##now we load in test.csv, using all available CPUS
    CSV.read("test.csv",DataFrame)
    ## there is now a very large dataframe in ans, so memory usage is high again
    used_mem()
    ##clear ans and collect garbage
    1+1
    GC.gc()
    
    ### if Threads.nthreads()>1, memory usage is still very high, even with nothing running and no big variables to explain it
    ## If Threads.nthreads()=1 (i.e. start julia with `JULIA_NUM_THREADS=1`) then used_mem() is as on initialisation
    varinfo()
    used_mem()
    

    我没有为 Distributed.jl 用例提出 MWE,但那是我的应用程序中主要遇到问题的地方。有趣的是,当我杀死所有工作进程时,会释放一些内存,但之后也会继续保留一些内存。

    【讨论】:

      猜你喜欢
      • 2014-06-14
      • 1970-01-01
      • 2011-09-13
      • 1970-01-01
      • 1970-01-01
      • 2012-10-11
      • 2016-01-26
      • 2010-09-26
      • 1970-01-01
      相关资源
      最近更新 更多