某些软件包包含本机代码,以动态加载库 (DLL) 的形式动态链接到 R。最近,R 用户开始加载越来越多的软件包;“工作流文档”是此模式的一个来源。最终导致达到 R 中的 DLL 限制,表现为运行时错误“达到最大 DLL 数”。
打开文件数量的限制
R 中的 DLL 限制很重要,原因之一。每个加载的 DLL 都将至少占用动态加载器实现中的一个打开文件描述符(在 Unix 中位于 dlopen
内)。由于加载依赖库,它可能会占用更多。操作系统限制每个进程打开的文件数量,在某些系统上,限制非常低。过去曾有报道称,某些系统上的默认值低至 256,而当今的 OS/X 平台默认值仍为 256。Linux 上的限制通常较高(例如 1024),而 Windows 上的限制非常高(实际上不存在)。它可以在 OS/X 和 Linux 上增加,但对于普通用户来说并不容易。如果达到打开文件数量的限制,R 将开始表现得不可预测,因为打开文件将开始失败 - 诊断文件限制是问题所在可能非常困难(失败可能出现在 R 运行时的任何代码中,也可能出现在软件包中;错误消息可能不会以全部详细信息正确传播给用户)。同时,诊断是否达到 DLL 限制很容易,在尝试加载 DLL 时会收到一条标准的 R 错误消息,确切地说,通常是通过加载软件包来加载 DLL。
在 POSIX 系统上(适用于 R 平台的 Unix 和 OS/X),打开文件数量的限制称为 RLIMIT_NOFILE
,它可以通过 getrlimit()
检测,并且在遵循某些限制后,可以通过 setrlimit()
更改。存在硬限制和软限制。硬限制 可以由用户进程不可逆地降低。软限制 可以(可逆地)设置为硬限制允许的任何值。可以使用实用程序 ulimit
从 shell 更改这些限制。POSIX 不要求实用程序支持文件限制,但它通常在 Linux 和 OS/X 上的 shell 中支持。来自 OS/X 10.13.3 系统的一个示例
$ ulimit -n
256
$ ulimit -Sn
256
$ ulimit -Hn
unlimited
在示例中,系统的软限制为 256,但没有(小)硬限制。事实上,硬限制存在限制,只是调用没有显示。因此,拥有用户权限的人员可以简单地增加软限制,以便在 shell 进一步执行的进程中打开更多文件,因此可以在运行 R 之前执行此操作
$ ulimit -Sn 2048
ulimit -n 2048
也可以,但它会修改软限制和硬限制,因此无法进一步增加,例如出于实验目的。
操作系统内核中设置的打开文件数量也存在限制。在 OS/X 上,这些参数为 kern.maxfiles
和 kern.maxfilesperproc
,可以通过 sysctl
进行更改(在我的系统上,它们为 98304
和 49152
)。不太可能需要更改这些参数。
R 中的 DLL 注册表表示
R 中已加载 DLL 的元数据保存在 R 启动时分配的固定大小数组中,因此将大小设置得过高会产生内存开销。原则上,数据结构可以更改为链表(我们收到了一份相当广泛的补丁,建议这样做)。但是,数组的一个条目仅占用 96 个字节(在我的 64 位 Linux 上),因此默认情况下将限制设置得非常高(比如一千个或更多)在当今拥有大量内存的系统上并不是一个真正的问题,并且可能不值得增加此代码的复杂性。相反,可以考虑按需重新分配数组,但似乎这些条目中可能存在指针(根据阅读代码,我无法说服自己移动内存中的条目是安全的)。内存开销仍然很小,真正阻止人们加载 DLL 的问题是打开的文件数量。
加载许多包的开销
除此之外,从概念上来说,加载过多的包可能不是一个好主意,出于性能原因也不建议这样做。即使包使用所谓的延迟加载,在加载包时也会执行一些操作,特别是 S4/方法实现。据观察,一些 Bioconductor 包在现代计算机上加载需要 12 秒(包括依赖包),我曾使用 yriMulti
进行过实验,它花费了这么长时间(包括依赖包),并且似乎大部分时间都花在了更新方法表上。将来减少这些开销会很好,但现在应该考虑这些开销。
R 最近版本中的 DLL 限制
在 R 3.3.x 中,DLL 的最大数量已固定为 100。已知的打开文件数量的最小默认限制为(仅)256,因此有 156 个文件的缓冲区来满足 DLL 可能需要多个文件以及 R 运行时和其他包打开的文件。
在 R 3.4.x 中,可以通过环境变量 R_MAX_NUM_DLLS
修改最大 DLL 数。在 R 启动时检查该变量,并预先分配固定数组(注册表)。在正在运行的 R 中设置变量对该 R 实例无效。最小允许值为 100,最大值为 1000(但允许限制打开的文件数)。在 POSIX 系统上通过 getrlimit()
检测打开的文件数限制,而在 Windows 上则硬编码(非常高)。如果知道此类限制,则最大 DLL 数可以达到文件限制的 60%(因此缓冲区可以比 R 3.3.x 中小一些)。如果不知道此类限制,则最大 DLL 数仍然为 100。如果打开的文件数限制很小,以至于我们甚至无法将 DLL 限制设置为 100,则 R 将无法启动,并显示一条信息丰富的错误消息。
这允许需要加载许多 DLL 的用户增加 DLL 限制,但在打开的文件数限制较小的系统(通常为 OS/X)上,这也需要同时增加该限制。
为了使此行为更易于用户使用,当未设置 R_MAX_NUM_DLLS
时,R 3.5.0 会自动针对更高的 DLL 限制(当前为 614)。当操作系统对打开的文件数的限制对于此限制过小,R 会尝试通过在 POSIX 系统上使用 setrlimit()
来增加限制(在 Windows 上,无需增加)。因此,现在即使在未设置任何变量的情况下,通常也会在 OS/X 系统上获得 614 的限制。当设置了 R_MAX_NUM_DLLS
,但打开的文件数限制过低时,R 会再次尝试增加限制。因此,现在,即使在 OS/X 上,这也将成功(前提是没有严格的硬限制)
env R_MAX_NUM_DLLS=1000 R
还可以看到文件限制会自动增加
$ ulimit -n
256
$ R
> system("ulimit -n")
1024
列出已加载的 DLL
可以从 R 中列出 DLL 注册表,这在诊断已加载 DLL 的来源时可能很有用
> getLoadedDLLs()
Filename Dynamic.Lookup
base base FALSE
methods /Users/tomas/trunk/library/methods/libs/methods.so FALSE
utils /Users/tomas/trunk/library/utils/libs/utils.so FALSE
grDevices /Users/tomas/trunk/library/grDevices/libs/grDevices.so FALSE
graphics /Users/tomas/trunk/library/graphics/libs/graphics.so FALSE
stats /Users/tomas/trunk/library/stats/libs/stats.so FALSE