在现代Linux上,从大型进程获得与fork
- execve
组合相同效果的最快,最好的方法是什么?
我的问题是这个过程分叉大约是500MByte,一个简单的基准测试只能从这个过程中获得大约50个分叉/秒(参见最小尺寸过程的~10000分叉/秒),这对于预期来说太慢了应用
有些谷歌搜索出现了vfork
,因为它被发明为解决此问题的方法......但也有关于not to use it的警告。现代Linux似乎已经获得了相关的clone
和posix_spawn
电话;这些可能会有所帮助吗?什么是vfork
的现代替代品?
我在i7上使用64位Debian Lenny(如果posix_spawn
有帮助,该项目可能会转移到Squeeze。)
答案 0 :(得分:32)
在Linux上,您可以将posix_spawn(2)
与POSIX_SPAWN_USEVFORK
标志一起使用,以避免在从大型进程分叉时复制页表的开销。
请参阅 Minimizing Memory Usage for Creating Application Subprocesses ,了解posix_spawn(2)
的完整摘要,优势和一些示例。
要利用vfork(2)
,请确保#define _GNU_SOURCE
之前#include <spawn.h>
,然后posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK)
我可以确认这适用于Debian Lenny,并且在从大型进程分叉时提供了大量的加速。
benchmarking the various spawns over 1000 runs at 100M RSS
user system total real
fspawn (fork/exec): 0.100000 15.460000 40.570000 ( 41.366389)
pspawn (posix_spawn): 0.010000 0.010000 0.540000 ( 0.970577)
答案 1 :(得分:13)
结果:我打算按照其他答案的建议继续推进早期生成的帮助程序子进程路径,但后来我遇到this使用巨大的页面支持来改进fork性能
我自己尝试使用libhugetlbfs来简单地让我所有应用程序的mallocs分配大页面,我现在得到大约2400个forks / s ,无论进程大小(在范围内)无论如何我都很感兴趣。惊人的。
答案 2 :(得分:8)
您是否真的测量了叉车需要多长时间?引用page you linked,
Linux从未遇到过这个问题;因为Linux在内部使用了写时复制语义,Linux只会在页面发生变化时复制页面(实际上,仍有一些表需要复制;在大多数情况下,它们的开销并不重要)
所以叉子的数字并没有真正显示开销有多大。您应该测量由叉子消耗的时间,以及(通常的建议)仅由您实际执行的分支消耗,而不是通过对最大性能进行基准测试。
但是如果你真的发现分支大的进程很慢,你可能会产生一个小的辅助进程,将主进程管道输入到它的输入,并从它接收命令exec
。小流程fork
和exec
这些命令。
据我了解,此功能是通过桌面系统上的fork
/ exec
实现的。但是,在嵌入式系统中(特别是在没有MMU的系统中),进程是通过系统调用生成的,接口是posix_spawn
或类似的函数。引用informative section of POSIX standard describing posix_spawn
:
对于实时环境,交换通常太慢。
在POSIX可能有用的任何地方都无法使用动态地址转换。
只要必须在没有地址转换或其他MMU服务的情况下运行,进程就无法简单地选择POSIX。
因此,POSIX需要流程创建和文件执行原语,无需地址转换或其他MMU服务即可高效实现。
如果您的目标是最大程度地减少时间消耗,我认为您不会从桌面上的此功能中受益。
答案 3 :(得分:4)
如果您提前知道子进程的数量,那么在启动时预分叉应用程序然后通过管道分发execv信息可能是合理的。或者,如果您的程序中存在某种“平静”,则提前分叉一个或两个子过程以便稍后快速周转可能是合理的。这些选项都不会直接解决问题,但如果这两种方法都适合您的应用,那么它可能会让您解决问题。
答案 4 :(得分:3)
我遇到过这篇博文:http://blog.famzah.net/2009/11/20/a-much-faster-popen-and-system-implementation-for-linux/
pid = clone(fn, stack_aligned, CLONE_VM | SIGCHLD, arg);
摘录:
系统调用clone()来救援。使用clone()我们创建一个 子进程具有以下特征:
- 子进程与父进程在同一内存空间中运行。这意味着子进程没有复制内存结构 创建。因此,对任何非堆栈变量的任何更改 父进程可以看到孩子做的。这类似于 线程,因此与fork()完全不同,也非常 危险 - 我们不希望孩子弄乱父母。
- 孩子从创建孩子后立即调用的入口函数开始。这就像线程一样,与fork()不同。
- 子进程有一个单独的堆栈空间,类似于threads和fork(),但与vfork()完全不同。
- 最重要的是:这个类似线程的子进程可以调用exec()。
简而言之,通过以下列方式调用clone,我们创建了一个 子进程,它与一个线程非常相似,但仍然可以调用 EXEC():
但是我认为它仍然可能受到setuid问题的影响:
http://ewontfix.com/7/“setuid and vfork”
现在我们遇到了最糟糕的情况。线程和vfork允许你进入 两个进程共享内存空间的情况 在同一时间运行。现在,如果在另一个线程中会发生什么 parent调用setuid(或任何其他特权影响函数)?您 最终得到两个具有不同权限级别的进程 共享地址空间。这是一件坏事。
例如,考虑一个最初运行的多线程服务器守护程序 作为root,这是使用posix_spawn,用vfork实现天真的 运行外部命令。它不关心此命令是否以root身份运行 或者具有低权限,因为它是固定的固定命令行 环境并不能做任何有害的事情。 (作为一个愚蠢的例子,让我们 说它是作为外部命令运行日期,因为程序员 无法弄清楚如何使用strftime。)
由于它不关心,它在没有任何线程的另一个线程中调用setuid 与意图一起运行外部程序的同步 下拉到普通用户并执行用户提供的代码(也许 一个脚本或dlopen获得的模块)作为该用户。不幸的是,它 只是授予该用户mmap新代码的权限 运行posix_spawn代码,或更改字符串posix_spawn是 传给孩子的执行官。糟糕。