在 Linux 上更快地分叉大型进程?
- 2024-11-08 09:04:00
- admin 原创
- 48
问题描述:
在现代 Linux 上,实现与大型进程中的fork
-execve
组合相同的效果的最快、最佳方法是什么?
我的问题是,进程分叉大约有 500MByte 大,而简单的基准测试只能从进程中实现大约 50 次分叉/秒(参见最小大小进程的大约 1600 次分叉/秒),这对于预期的应用程序来说太慢了。
谷歌搜索显示vfork
有人发明了这种方法来解决这个问题……但也有警告不要使用它。现代 Linux 似乎已经获得了相关的clone
和posix_spawn
调用;这些可能有帮助吗?现代的替代品是什么vfork
?
我在 i7 上使用 64 位 Debian Lenny(如果posix_spawn
有帮助的话,该项目可以转移到 Squeeze)。
解决方案 1:
在 Linux 上,您可以使用posix_spawn(2)
标志POSIX_SPAWN_USEVFORK
来避免从大型进程分叉时复制页表的开销。
请参阅最小化创建应用程序子进程的内存使用量,以获得其优点和一些示例的良好总结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)
解决方案 2:
结果:我本来要按照这里的其他答案所建议的,采用早期生成的辅助子进程路线,但是后来我发现重新使用大页面支持来提高 fork 性能。
我自己尝试使用libhugetlbfs简单地让我的所有应用程序的 malloc 分配大页面,现在无论进程大小如何,我都能获得大约 2400 个 fork/s (无论如何都在我感兴趣的范围内)。太神奇了。
解决方案 3:
你真的测量过分叉需要多长时间吗?引用你链接的页面,
Linux 从来没有遇到过这个问题;因为 Linux 内部使用了写时复制语义,所以 Linux 只在页面发生变化时才复制页面(实际上,仍然有一些表需要复制;在大多数情况下,它们的开销并不大)
因此,分叉的数量并不能真正显示开销有多大。您应该测量分叉所消耗的时间,并且(这是一般建议)只测量您实际执行的分叉所消耗的时间,而不是通过对最大性能进行基准测试。
但如果你真的发现分叉一个大进程很慢,你可以生成一个小的辅助进程,将主进程连接到它的输入,并从exec
它那里接收命令。小进程将fork
执行exec
这些命令。
posix_spawn()
据我所知,此功能是通过fork
/exec
在桌面系统上实现的。然而,在嵌入式系统中(特别是那些没有板载MMU 的系统),进程是通过系统调用、与之接口posix_spawn
或类似功能生成的。引用POSIX 标准中描述的信息部分posix_spawn
:
对于实时环境来说,交换通常太慢了。
动态地址转换并非在 POSIX 可能有用的所有地方都可用。
进程太有用了,以至于当它必须在没有地址转换或其他 MMU 服务的情况下运行时,不能简单地选择退出 POSIX。
因此,POSIX 需要能够无需地址转换或其他 MMU 服务即可有效实现的进程创建和文件执行原语。
如果您的目标是尽量减少时间消耗,我认为您不会从桌面上的此功能中受益。
解决方案 4:
如果您提前知道子进程的数量,那么在启动时预先分叉应用程序然后通过管道分发 execv 信息可能是合理的。或者,如果您的程序中存在某种“间歇”,那么提前分叉一个或两个子进程以便稍后快速周转可能是合理的。这两种选择都不能直接解决问题,但如果任何一种方法都适合您的应用程序,它可能允许您避开这个问题。
解决方案 5:
我偶然看到了这篇博客文章: 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() 不同。
子进程有一个单独的堆栈空间,这与线程和 fork() 类似,但与 vfork() 完全不同。
最重要的是:这个类似线程的子进程可以调用exec()。
简而言之,通过以下方式调用 clone,我们创建一个与线程非常相似但仍可以调用 exec() 的子进程:
不过我认为它可能仍然受到 setuid 问题的影响:
http://ewontfix.com/7/“setuid和 vfork”
现在我们来看看最糟糕的情况。线程和 vfork 允许您进入这样一种情况:两个进程共享内存空间并同时运行。现在,如果父进程中的另一个线程调用 setuid(或任何其他影响特权的函数),会发生什么?最终您将得到两个具有不同特权级别的进程在共享地址空间中运行。这是一件坏事。
例如,考虑一个多线程服务器守护进程,最初以 root 身份运行,它使用 posix_spawn(用 vfork 简单实现)来运行外部命令。它不关心此命令是以 root 身份运行还是以低权限运行,因为它是具有固定环境的固定命令行,不会做任何有害的事情。(举一个愚蠢的例子,假设它将 date 作为外部命令运行,因为程序员无法弄清楚如何使用 strftime。)
由于它并不关心,因此它在另一个线程中调用 setuid,而不与正在运行的外部程序进行任何同步,目的是降级为普通用户并以该用户的身份执行用户提供的代码(可能是脚本或 dlopen 获得的模块)。不幸的是,它只是授予该用户权限,允许在正在运行的 posix_spawn 代码之上 mmap 新代码,或更改 posix_spawn 传递给子进程中的 exec 的字符串。糟糕。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件