jas*_*van 8 linux filesystems cache nfs tmpfs
这个问题的结构如下:
我首先给出一些关于我为什么对这个主题感兴趣以及它如何解决我正在处理的问题的背景。然后,我询问有关文件系统缓存的实际独立问题,因此如果您对动机(某些 C++ 项目构建设置)不感兴趣,请跳过第一部分。
我正在寻找一种方法来加快我们项目的构建时间。设置如下: 一个目录(我们称之为workarea)位于 NFS 共享中。它最初只包含源代码和生成文件。然后,构建过程首先在 中创建静态库workarea/lib,然后在 中创建共享库workarea/dll,使用 中的静态库workarea/lib。在共享库的创建过程中,这些不仅被写入,而且还使用例如再次读取nm在链接时验证没有符号丢失。并行使用许多作业(例如 make -j 20 或 make -j 40),构建时间很快就会被链接时间所支配。在这种情况下,链接性能受文件系统性能的限制。例如,并行连接 20 个作业在 NFS 共享中大约需要 35 秒,但在 RAM 驱动器中仅需要 5 秒。请注意,使用 rsync 复制dll回 NFS 共享还需要 6 秒,因此在 RAM 驱动器中工作并随后同步到 NFS 比直接在 NFS 共享中工作要快得多。我正在寻找一种无需在 NFS 共享和 RAM 驱动器之间显式复制/链接文件即可实现快速性能的方法。
注意我们的 NFS 共享已经使用了一个缓存,但是这个缓存只能缓存读访问。
AFAIK,NFS 要求任何 NFS 客户端在 NFS 服务器确认写入完成之前不能确认写入,因此客户端不能使用本地写入缓冲区,并且写入吞吐量(即使在峰值)受网络速度限制。在我们的设置中,这有效地将组合写入吞吐量限制在大约 80MB/s。
然而,读取性能要好得多,因为使用了读取缓存。如果我在 NFS 中链接(创建 的内容dll)workarea/lib并workarea/dll作为到 RAM 驱动器的符号链接,性能仍然不错 - 大约 5 秒。请注意,构建过程需要workarea/*在 NFS 共享中完成:lib需要在共享中(或任何持久安装)以允许快速增量构建,并且dll需要在 NFS 中才能被使用这些 dll 启动作业的计算机访问。
因此,我想对下面的问题应用一个解决方案,workarea/dll也可能workarea/lib(后者是为了缩短编译时间)。以下快速设置时间的要求是由于需要执行快速增量构建,仅在需要时复制数据。
我可能应该对构建设置更具体一些。以下是更多详细信息: 编译单元被编译为临时目录(在 /tmp 中)中的 .o 文件。然后将它们合并到静态库中lib使用ar。完整的构建过程是增量的:
make。尽管如此,完整或接近完整的重建还是很有必要的,因为可能会使用多个编译器 ( gcc, clang)、编译器版本、编译模式 ( release, debug)、C++ 标准 ( C++97, C++11) 和其他修改 (例如libubsan)。所有组合都有效地使用不同的lib和dll目录,因此可以在设置之间切换,并根据该设置的最后一次构建增量构建。此外,对于增量构建,通常只需要重新编译几个文件,花费很少的时间,但触发(可能很大)共享库的重新链接,需要更长的时间。
与此同时,我了解了noctoNFS 挂载选项,它显然可以解决我在除 Linux 之外的基本上所有 NFS 实现上的问题,因为 Linux 始终刷新写入缓冲区close(),即使使用nocto. 我们已经尝试了其他一些事情:例如,我们可以使用另一台async启用的本地 NFS 服务器作为写入缓冲区并导出主 NFS 挂载,但不幸的是,在这种情况下,NFS 服务器本身没有写入缓冲。这似乎async只是意味着服务器不会强制其底层文件系统刷新到稳定存储,并且在底层文件系统使用写缓冲区的情况下隐式使用写缓冲区(显然文件系统就是这种情况)在物理驱动器上)。
我们甚至考虑过在同一台机器上使用非 Linux 虚拟机的选项,该机器使用 挂载主 NFS 共享nocto,提供写缓冲区,并通过另一个 NFS 服务器提供此缓冲挂载,但尚未对其进行测试,并希望避免这样的解决方案。
我们还发现了几个FUSE用作缓存的基于文件系统的包装器,但没有一个实现了写缓冲。
考虑一些目录,我们称之为orig,它驻留在慢文件系统中,例如 NFS 共享。对于很短的时间跨度(例如几秒或几分钟,但这应该无关紧要),我想创建一个orig使用目录的完全缓存和缓冲的视图,该目录cache驻留在快速文件系统中,例如本地硬盘驱动器甚至内存驱动器。缓存应该可以通过例如挂载访问cached_view并且不需要 root 权限。我假设在缓存的生命周期内,没有直接读取或写入访问orig(当然除了缓存本身)。通过完全缓存和缓冲,我的意思是:
orig,缓存该结果并从那时起使用它来回答读取查询,并且 cache完成后确认,即缓存也是一个写缓冲区。这甚至应该close()在写入文件上调用时发生。然后,在后台,写入被转发(可能使用队列)到orig. cache当然,使用 中的数据回答对写入数据的读取查询。 此外,我需要:
orig. 刷新的运行时间应仅取决于写入文件的大小,而不是所有文件。之后,可以orig再次安全访问。orig,而不取决于 中文件的大小orig,因此复制orig到cache一次不是一种选择。最后,我也可以使用不使用其他文件系统作为缓存的解决方案,而只是缓存在主内存中(服务器有足够的 RAM)。请注意,使用例如 NFS 的内置缓存不是一种选择,因为 AFAIK NFS 不允许写入缓冲区(参见第一部分)。
在我的设置中,我可以通过符号链接origto的内容来模拟稍微差一点的行为cache,然后使用cache(因为所有写操作实际上都用新文件替换文件,在这种情况下符号链接被更新的版本替换),然后同步修改文件到orig之后。这并不完全符合上述要求,例如读取不是只进行一次并且文件被符号链接替换,这当然对某些应用程序产生影响。
我认为这不是解决这个问题的正确方法(即使在我的简单设置中),也许有人知道一个更干净(更快!)的解决方案。
哇,很惊讶还没有人回答“overlayfs”。
其实我有两个建议。第一种是使用overlayfs,这基本上正是您所描述的,但有一个警告。Overlayfs(自 Linux 3.18 左右起成为标准)允许您从两个虚拟合并的目录树中读取数据,同时仅写入其中之一。您要做的就是采用快速存储(如 tmpfs)并将其覆盖到 NFS 卷上,然后在两者的覆盖合并中执行编译。完成后,NFS 上的任何文件的写入次数为零,并且另一个文件系统保存着您的所有更改。如果您想保留更改,只需将它们同步回 NFS 即可。您甚至可以排除您不关心的文件,或者只是从结果中手动挑选一些文件。
您可以在我的一个小项目中看到一个相对简单的overlayfs示例:https://github.com/nrdvana/squash-portage/blob/master/squash-portage.sh 该脚本还展示了如何使用UnionFS,以防您位于没有 Overlayfs 的旧内核上。
就我而言,Gentoo 使用 rsync 命令更新其软件库需要非常长的时间,因为它有数百万次微小的磁盘写入。我使用overlayfs 将所有更改写入tmpfs,然后使用mksquashfs 构建树的压缩映像。然后我扔掉 tmpfs 并在其位置安装压缩映像。
我的第二个建议是“树外”构建。这个想法是,您将源代码和 makefile 放在一个树中,然后告诉 automake 在镜像第一个树的单独树中生成所有中间文件。
如果幸运的话,您的构建工具(automake 或诸如此类)已经可以做到这一点。如果你不幸运,你可能不得不花一些时间来修改你的 makefile。
| 归档时间: |
|
| 查看次数: |
2287 次 |
| 最近记录: |