如何在无需重启 R 会话的情况下清理 R 内存

Sim*_* C. 6 memory garbage-collection r

我知道有很多类似的问题都得到了公认的答案(这里这里甚至这个),但到目前为止,我没有找到关于如何在不重新启动 R 会话的情况下释放一些内存空间的明确答案。

我知道,可以保存他的工作区,重新启动 R 并重新加载工作区,但是:

  1. 我不确定,但是这样做您必须重新加载所有库,对吗?
  2. 如果你的工作空间很大,这样做可能需要时间,如果我经常这样做,我不想每次都浪费时间。
  3. 如果我删除一个大对象,是否只是实际释放该对象占用的内存的正常行为?

情况似乎并非如此。即使在删除了我工作区的一半最大对象(感谢这个很好的答案)并运行之后gc()top仍然给我提供了完全相同的内存使用百分比。

这里的一个评论,它说:

R 的垃圾收集将 RAM 标记为可用。由您的操作系统来收回

听起来不错,但不确定这真的发生了。top即使在rm()和之后gc(),甚至在操作系统中启动新的其他进程之后,甚至在 2 小时、10 小时或 3 天之后,仍然向我显示 R 使用的相同数量的内存。

这个评论表明它必须查看加载的库和图形设备,但为什么呢?我该如何解决?

如果我rm()一个3GB对象然后gc()用来释放内存,R 怎么可能仍然使用相同百分比的内存?

Mik*_*gan 42

垃圾收集是“复杂的”。如果x是环境中绑定的变量e,则rm(x, pos = e); gc()不一定会释放object.size(e$x)字节以供操作系统使用。

\n

这是因为 R 对象只是指向内存块的指针。如果多个对象指向同一块内存,则需要删除所有对象以使该内存可用于垃圾回收。如果您的全局环境可能递归地绑定大量变量\xe2\x80\x94,如果您经常使用列表(包括数据框)、配对列表和环境(包括函数求值环境),那么这可能很难做到。

\n

这是一个示例,我在一台运行 Ubuntu 20.04、具有 8 GB RAM 的机器上运行。(它应该可以在大多数 Unix 类似系统上重现,但由于调用中的 Unix 命令而不能在 Windows 上重现system。)

\n
$ R --vanilla\n
Run Code Online (Sandbox Code Playgroud)\n
## Force garbage collection then output the amount of memory\n## being used by R, as seen by R (\'gc\') and by the OS (\'ps\')\nusage <- function() {\n    m1 <- sum(gc(FALSE)[, "(Mb)"])\n    m2 <- as.double(system(paste("ps -p", Sys.getpid(), "-o pmem="), intern = TRUE))\n    c(`gc (MiB)` = m1, `ps (%)` = m2)\n}\nusage()\n## gc (MiB)  ps (%) \n##     19.0     0.6\n\n## Allocate a large block of memory and create multiple\n## references to it\nx <- double(1e+08)\ny <- x\nl <- list(x = x)\np <- pairlist(x = x)\ne <- new.env(); e$x <- x\nf <- (function(x) {force(x); function(x) x})(x)\n\nusage()\n## gc (MiB)  ps (%) \n##    786.1    10.3\n\n## Apply \'object.size\' to each object in current environment \n## and scale from bytes to mebibytes\n0x1p-20 * unlist(eapply(environment(), object.size))\n##            x            y        usage            e            f            l            p \n## 7.629395e+02 7.629395e+02 1.787567e-02 5.340576e-05 1.106262e-03 7.629398e+02 7.629396e+02\n\n## Remove references to double(1e+08) one by one\nrm(x); usage()\n## gc (MiB)  ps (%) \n##    786.1    10.3 \n\nrm(y); usage()\n## gc (MiB)  ps (%) \n##    786.1    10.3\n\nl$x <- NULL; usage()\n## gc (MiB)  ps (%) \n##    786.1    10.3\n\np$x <- NULL; usage()\n## gc (MiB)  ps (%) \n##    786.1    10.3\n\nrm(x, pos = e); usage()\n## gc (MiB)  ps (%) \n##    786.1    10.3\n\nrm(x, pos = environment(f)); usage()\n## gc (MiB)  ps (%) \n##     23.2     0.6\n
Run Code Online (Sandbox Code Playgroud)\n

此示例表明,这object.size并不是确定需要删除哪些变量才能将特定内存块返回给操作系统的可靠方法。要实际释放分配给 的 ~760 MiB (~800 MB) double(1e+08),需要删除六个引用:xyl$xp$xe$xenvironment(f)$x

\n

您的观察gc似乎仅在长期运行的 R 进程中没有任何作用,并且全局环境中绑定了许多变量,这让我怀疑您已经删除了对您试图释放的内存块的一些但不是全部引用。我不会立即得出垃圾收集器行为不正确的结论,尤其是在没有最小可重现示例的情况下。

\n

那是说...

\n

Linux 上的内存释放问题在 R-devel 邮件列表和 Bugzilla 上进行了讨论。它甚至包含在 R FAQ 中。以下是最相关的链接:

\n
    \n
  1. 为什么 R 显然不释放内存? R常见问题7.42
  2. \n
  3. 帮助创建 bugzilla 帐户,R-devel [1] ...标题非常糟糕
  4. \n
  5. 使用 glibc、 R-devel [2][3]的系统上的内存释放/碎片问题
  6. \n
  7. R 不会向系统释放内存BR 14611
  8. \n
  9. glibc malloc 不会向系统释放内存BR 17505
  10. \n
\n

总而言之,事实证明Linux 上存在问题,但这是由于 glibc 的限制超出了 R 的控制范围。具体来说,当 glibc 分配然后释放许多小内存块时,您可能会得到一个碎片堆,操作系统无法从中回收未使用的内存。

\n

最小可重复示例

\n

我们可以通过创建一长串短原子向量而不是一个非常长的原子向量来重现 R 中的问题:

\n
$ R --vanilla\n
Run Code Online (Sandbox Code Playgroud)\n
usage <- function() {\n    m1 <- sum(gc(FALSE)[, "(Mb)"])\n    m2 <- as.double(system(paste("ps -p", Sys.getpid(), "-o pmem="), intern = TRUE))\n    c(`gc (MiB)` = m1, `ps (%)` = m2)\n}\nusage()\n## gc (MiB)  ps (%) \n##     19.0     0.6\n\nx <- replicate(1e+06, runif(100), simplify = FALSE)\nusage()\n## gc (MiB)  ps (%) \n##    847.1    15.9\n\nrm(x)\nusage()\n## gc (MiB)  ps (%) \n##     23.2    15.8\n
Run Code Online (Sandbox Code Playgroud)\n

事实上,操作系统无法回收x其元素占用的大部分内存。它继续为 R 进程保留约 15% 的 RAM,尽管仅使用了约 23 MiB 的内存。

\n

(这是在我的 Linux 机器上。在我的 Mac 上,其 RAM 是原来的两倍,操作系统报告的内存使用百分比从 0.4 变为 6.2 再到 1.2。)

\n

可能的修复

\n

邮件列表主题中建议了一些解决方法:

\n
    \n
  1. 设置环境变量来调整 glibc 的行为。没有提供建议或示例,因此您必须深入研究才能弄清楚这一点。mallopt 您可以从手册页开始。

    \n
  2. \n
  3. 指示 R 使用除 glibc 之外的分配器malloc,例如jemalloctcmalloc。卢克·蒂尔尼写道:

    \n
    \n

    ...可以使用替代malloc实现,要么重建 R 以使用它们,要么使用LD_PRELOAD. 例如,在 Ubuntu 上,您可以将 Rjemalloc

    \n
    sudo apt-get install libjemalloc1\nenv LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 R\n
    Run Code Online (Sandbox Code Playgroud)\n

    这似乎并没有以相同的程度保留内存,但我不知道其性能的任何其他方面。

    \n
    \n
  4. \n
  5. 显式调用 glibc 实用程序malloc_trim来指示操作系统尽可能回收未使用的内存。手册malloc_trim 说:

    \n
    \n

    从 glibc 2.8 开始,此函数释放所有区域和所有块中的内存以及整个空闲页面。

    \n
    \n

    这看起来很有希望!

    \n
  6. \n
\n

Dmitry Selivanov在这里malloc比较了, jemalloc,tcmallocmalloc+ 。他们令人信服地证明,所有、和+都可以帮助减轻. 一些注意事项:malloc_trim jemalloctcmallocmallocmalloc_trimmalloc

\n
    \n
  • 他们仅在 Ubuntu 16.04 上进行了测试。
  • \n
  • 他们没有分享他们安装的 glibc、libjemalloc1 和 libtcmalloc-minimal4 的版本。
  • \n
  • 他们表明,没有一种malloc替代方案是万能的。他们的表现很少比他们差malloc但他们的表现也不总是比他们
  • \n
\n

一些实验

\n

我依次replicate使用每个malloc替代方案重试了上面的示例。在这个(不可概括的)实验中,jemalloc和 的tcmalloc表现并不比malloc, while malloc+malloc_trim允许操作系统回收所有已释放的内存。以下是我使用的库:

\n
sudo apt-get install libjemalloc1\nenv LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.1 R\n
Run Code Online (Sandbox Code Playgroud)\n

结果见下文。

\n

jemalloc

\n
libc6                 version 2.31-0ubuntu9.2\nlibjemalloc2          version 5.2.1-1ubuntu1\nlibtcmalloc-minimal4  version 2.7-1ubuntu2\n
Run Code Online (Sandbox Code Playgroud)\n
$ sudo apt install libjemalloc2\n$ env LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2 R --vanilla\n
Run Code Online (Sandbox Code Playgroud)\n

tcmalloc

\n
usage <- function() {\n    m1 <- sum(gc(FALSE)[, "(Mb)"])\n    m2 <- as.double(system(paste("ps -p", Sys.getpid(), "-o pmem="), intern = TRUE))\n    c(`gc (MiB)` = m1, `ps (%)` = m2)\n}\nusage()\n## gc (MiB)  ps (%) \n##     19.0     0.6\n\nx <- replicate(1e+06, runif(100), simplify = FALSE)\nusage()\n## gc (MiB)  ps (%) \n##    847.1    13.9\n\nrm(x)\nusage()\n## gc (MiB)  ps (%) \n##     23.2     9.4\n
Run Code Online (Sandbox Code Playgroud)\n
$ sudo apt install libtcmalloc-minimal4\n$ env LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libtcmalloc_minimal.so.4 R --vanilla\n
Run Code Online (Sandbox Code Playgroud)\n

malloc+ malloc_trim,来自西蒙·厄本内克 (Simon Urbanek)mallinfo::malloc.trim

\n
usage <- function() {\n    m1 <- sum(gc(FALSE)[, "(Mb)"])\n    m2 <- as.double(system(paste("ps -p", Sys.getpid(), "-o pmem="), intern = TRUE))\n    c(`gc (MiB)` = m1, `ps (%)` = m2)\n}\nusage()\n## gc (MiB)  ps (%) \n##     19.0     0.7\n\nx <- replicate(1e+06, runif(100), simplify = FALSE)\nusage()\n## gc (MiB)  ps (%) \n##    847.1    13.8\n\nrm(x)\nusage()\n## gc (MiB)  ps (%) \n##     23.2    13.8\n
Run Code Online (Sandbox Code Playgroud)\n
$ R --vanilla\n
Run Code Online (Sandbox Code Playgroud)\n