概观

这个问题的结构如下:

我首先介绍一下我为什么对这个主题感兴趣以及如何解决我正在处理的问题.

然后,我询问有关文件系统缓存的实际独立问题,因此如果您对动机(某些C项目构建设置)不感兴趣,请跳过第一部分.

最初的问题:链接共享库

我正在寻找一种方法来加快我们项目的构建时间.设置如下:目录(让我们称之为工作区)位于NFS共享中.

它最初只包含源代码和makefile.然后,构建过程首先在workarea / lib中创建静态库,然后使用workarea / lib中的静态库在workarea / dll中创建共享库.在创建共享库期间,这些库不仅被写入,而且还使用例如nm在链接时验证没有符号丢失.并行使用许多作业(例如make -j 20或make -j 40),构建时间很快就会被链接时间所主导.在这种情况下,链接性能受文件系统性能的限制.例如,在NFS共享中并行链接20个作业大约需要35秒,而在RAM驱动器中只需要5秒.请注意,使用rsync将dll复制回NFS共享还需要6秒,因此在RAM驱动器中工作并在之后同步到NFS比直接在NFS共享中工作要快得多.我正在寻找一种方法来实现快速性能,而无需在NFS共享和RAM驱动器之间显式复制/链接文件.

请注意,我们的NFS共享已使用缓存,但此缓存只能缓存读取访问.

AFAIK,NFS要求任何NFS客户端在NFS服务器确认写入完成之前可能无法确认写入,因此客户端无法使用本地写入缓冲区,并且写入吞吐量(即使在尖峰中)受网络速度的限制.这有效地将我们的设置中的组合写入吞吐量限制为大约80MB / s.

但是,读取性能要好得多,因为使用了读缓存.如果我在NFS中使用workarea / lib链接(创建dll的内容),并且workarea / dll是RAM驱动器的符号链接,性能仍然很好 – 大约5秒.请注意,构建过程需要完成驻留在NFS共享中的workarea / *:lib需要位于共享(或任何持久性挂载)中以允许快速增量构建,并且dll需要在NFS中进行访问通过计算机使用这些dll启动作业.

因此,我想将下面问题的解决方案应用于workarea / dll,也可能是workarea / lib(后者是为了改善编译时间).以下快速设置时间的要求是由于必须执行快速增量构建,仅在需要时复制数据.

更新

我应该对构建设置更具体一些.以下是一些更多细节:编译单元被编译为临时目录中的.o文件(在/ tmp中).然后使用ar将它们合并到lib中的静态库中.完整的构建过程是增量的:

>如果编译单元本身(.C文件)或包含的标头已更改(使用编译器生成的依赖文件包含在make中),则仅重新编译编译单元.

>只有在重新编译其中一个编译单元时,才会更新静态库.

>只有在其中一个静态库发生更改时,才会重新链接共享库.如果共享库提供的符号依赖于共享库本身已更新的更改,则仅重新检查共享库的符号.

但是,由于多个编译器(gcc,clang),编译器版本,编译模式(发布,调试),C标准(C 97,C 11)和其他修改(例如libubsan)可能需要完全或接近完成的重建.使用.所有组合都有效地使用不同的lib和dll目录,因此可以根据该设置的最后一次构建在设置和构建之间切换.此外,对于增量构建,通常只需要重新编译几个文件,花费很少的时间,但触发(可能很大的)共享库的重新链接需要更长的时间.

更新2

与此同时,我了解了nocto NFS mount选项,它显然可以解决我基本上所有NFS实现的问题,除了Linux之外,因为Linux总是在close()上刷写缓冲区,即使使用nocto也是如此.我们已经尝试了其他几个方面:例如,我们可以使用另一个启用了异步的本地NFS服务器作为写缓冲区并导出主NFS挂载,但不幸的是,NFS服务器本身在这种情况下不执行写缓冲.似乎异步只是意味着服务器不强制其底层文件系统刷新到稳定存储,并且在底层文件系统使用写缓冲区的情况下隐式使用写缓冲区(因为它显然是文件的情况)物理驱动器上的系统).

我们甚至考虑过在使用nocto安装主NFS共享的同一个盒子上使用非Linux虚拟机的选项,提供写缓冲区,并通过另一个NFS服务器提供这个缓冲的挂载,但是没有测试它并希望避免这样的解决方案.

我们还发现了几个基于FUSE的文件系统包装器作为缓存,但没有一个实现了写缓冲.

缓存和缓冲目录

考虑一些目录,让我们称之为orig,它驻留在一个慢速文件系统中,例如NFS共享.对于短的时间跨度(例如秒或分钟,但这无论如何都无关紧要),我想使用目录缓存创建一个完全缓存和缓冲的orig视图,该目录缓存驻留在快速文件系统中,例如,本地硬盘甚至RAM驱动器.缓存应该可以通过例如访问mount cached_view并且不需要root权限.我假设在缓存的生命周期中,没有直接对orig的读或写访问(当然在缓存本身旁边).

通过完全缓存和缓冲,我的意思是:

>一旦将查询转发到orig文件系统,缓存该结果并从那时开始使用它,就可以回答读取查询

>写入查询被写入高速缓存并直接在完成时确认,即高速缓存也是写入缓冲区.甚至在写入文件上调用close()时也会发生这种情况.然后,在后台,写入被转发(可能使用队列)到orig.当然,使用高速缓存中的数据来回答对写入数据的读取查询.

此外,我需要:

>缓存提供了一个关闭缓存的功能,它会刷新所有对orig的写入.刷新的运行时应该仅取决于写入文件的大小,而不是所有文件.之后,人们可以再次安全地访问orig.

>设置时间很快,例如高速缓存的初始化可能仅取决于orig中文件的数量,而不取决于orig中文件的大小,因此将orig复制到高速缓存一次不是一种选择.

最后,我也可以使用不使用其他文件系统作为缓存的解决方案,但只需在主内存中缓存(服务器有足够的RAM).注意,使用例如内置缓存. NFS不是一个选项,因为AFAIK NFS不允许写缓冲区(参见第一节).

在我的设置中,我可以通过将orig的内容符号链接到缓存来模拟稍差的行为,然后使用缓存(因为所有写操作实际上都是用新文件替换文件,在这种情况下,符号链接被更新的版本替换),以及之后将修改后的文件恢复为orig.

这不完全符合上述要求,例如读取不是只进行一次,文件被符号链接替换,这当然对某些应用程序有所不同.

我认为这不是解决这个问题的正确方法(即使在我更简单的设置中),也许有人意识到更清洁(更快!)的解决方案.

解决方法:

哇,惊讶没人回答“overlayfs”了.

其实我有两个建议.第一个是使用overlayfs,这基本上就是你所描述的,但有一点需要注意. Overlayfs(Linux 3.18以后的标准版)允许您从两个虚拟合并的目录树中读取,同时只写入其中一个.您要做的是采用快速存储(如tmpfs)并将其覆盖到NFS卷上,然后在两者的重叠合并中执行编译.完成后,对NFS上的任何文件都没有写入,而其他文件系统则保留所有更改.如果要保留更改,可以将它们rsync回NFS.您甚至可以排除您不关心的文件,或者只是从结果中挑选一些文件.

您可以在我的一个小项目中看到一个相对简单的overlayfs示例:https://github.com/nrdvana/squash-portage/blob/master/squash-portage.sh该脚本还显示了如果您使用的是没有overlayfs的旧内核,如何使用UnionFS.

就我而言,Gentoo用来更新其软件库的rsync命令花了很长时间,因为它有数百万个小磁盘写入.我使用overlayfs将所有更改写入tmpfs,然后我使用mksquashfs构建树的压缩图像.然后我扔掉tmpfs并将压缩图像挂在它的位置.

我的第二个建议是“树外”构建.想法是在一棵树中有源代码和makefile,并告诉automake在一个镜像第一个树的单独树中生成所有中间文件.

如果你很幸运,你的构建工具(automake或诸如此类)可以做到这一点.如果你不幸运,你可能不得不花费一些头痛来修补你的文件.

标签:linux,filesystems,tmpfs,nfs,cache

来源: https://codeday.me/bug/20190810/1635255.html

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐