【问题标题】:Faster forking of large processes on Linux?在 Linux 上更快地分叉大型进程?
【发布时间】:2011-02-13 11:06:56
【问题描述】:

在现代 Linux 上实现与 fork-execve 组合相同效果的最快、最佳方法是什么大型进程

我的问题是进程分叉大约 500MByte,一个简单的基准测试只能从进程中实现大约 50 个分叉/秒(比较最小大小的进程大约 1600 个分叉/秒),这对于预期来说太慢了应用。

一些谷歌搜索发现vfork 已被发明为解决此问题......但也有关于not to use it 的警告。现代 Linux 似乎已经获得了相关的cloneposix_spawn 调用;这些可能有帮助吗? vfork 的现代替代品是什么?

我在 i7 上使用 64 位 Debian Lenny(如果 posix_spawn 有帮助,该项目可以转移到 Squeeze)。

【问题讨论】:

  • 该文件已有 7 年历史。那是几个互联网生命周期。

标签: linux process debian exec fork


【解决方案1】:

在 Linux 上,您可以将 posix_spawn(2)POSIX_SPAWN_USEVFORK 标志一起使用,以避免在从大型进程分叉时复制页表的开销。

请参阅Minimizing Memory Usage for Creating Application Subprocesses,了解posix_spawn(2) 的良好总结、其优点和一些示例。

要利用vfork(2),请确保您在#include <spawn.h> 之前是#define _GNU_SOURCE,然后是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)

【讨论】:

  • 谢谢;这确实回答了关于现代等效 vfork 的问题。事实上,我真的不希望生成的进程共享 VM 页面(我的意思是,如果可以的话,我会创建一个线程)并且使用大页面或早期生成的帮助程序被证明是一个更好的解决方案.
  • 页面仅在调用execve(2) 之前共享,因此生成的进程本身不会与父进程共享任何内存。这基本上避免了复制页表,这首先导致 fork(2) 变慢,因为新的 fork 只是转到 execve(2) 并且实际上不需要父内存的副本。
  • 啊,好的,我明白了;抱歉,不得不重新阅读我原来的问题并回想一下以记住这实际上是我当时正在寻找的。​​span>
  • 你不需要指定POSIX_SPAWN_USEVFORK; glibc 的posix_spawn 有一个启发式算法,如果这样做是安全的,它将自动在幕后使用vfork。 (如果这样做不安全,你真的不想使用它。)
【解决方案2】:

结果:我打算按照此处其他答案的建议走早期生成的辅助子进程路线,但后来我遇到了 this re 使用大页面支持来提高分叉性能。

我自己尝试过使用 libhugetlbfs 让我的所有应用程序的 malloc 分配大页面,我现在得到大约 2400 forks/s 无论进程大小(在我的范围内)反正我有兴趣)。太棒了。

【讨论】:

    【解决方案3】:

    你真的测量过分叉需要多少时间吗?引用page you linked

    Linux 从来没有这个问题;因为Linux内部使用了copy-on-write语义,Linux只在页面发生变化时才复制页面(实际上,还有一些表需要被复制;在大多数情况下,它们的开销并不大)

    因此,分叉的 数量 并不能真正显示开销有多大。您应该衡量分叉消耗的时间,并且(这是一个通用建议)仅由您实际执行的分叉消耗,而不是通过对最大性能进行基准测试。

    但是如果你真的发现分叉一个大进程是很慢的,你可能会产生一个小的辅助进程,管道主进程到它的输入,并从它接收到exec 的命令。小进程会forkexec这些命令。

    posix_spawn()

    据我所知,这个功能是通过fork/exec在桌面系统上实现的。然而,在嵌入式系统中(特别是在那些没有MMU 的系统中),进程是通过系统调用产生的,其接口是posix_spawn 或类似的函数。引用informative section of POSIX standard describing posix_spawn

    • 对于实时环境而言,交换通常太慢。

    • 动态地址转换并非在 POSIX 可能有用的任何地方都可用。

    • 进程太有用了,当它必须在没有地址转换或其他 MMU 服务的情况下运行时,不能简单地选择退出 POSIX。

    因此,POSIX 需要无需地址转换或其他 MMU 服务即可有效实现的进程创建和文件执行原语。

    如果您的目标是减少时间消耗,我认为您不会从桌面上的此功能中受益。

    【讨论】:

    • 是的,我的印象是“Linux 从来没有遇到过这个问题”……直到我实​​际对它进行了基准测试并得到了我上面引用的数字。当您的进程为 500MByte 大时,大概复制这些表(我相信是 VM 页表)需要相当长的时间。
    • +1,我将其实现为单线程非阻塞服务器的“exec”助手。我没有在 fork() / execv() 中阻塞,而是简单地将请求通过管道传递给助手,然后将连接标记为 waiting_for_exec_result,然后在等待数据可发送回客户端时做有用的工作。跨度>
    【解决方案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()。

      简而言之,通过以下方式调用克隆,我们创建了一个 与线程非常相似但仍然可以调用的子进程 执行():

      但是我认为它可能仍然受到 setuid 问题的影响:

      http://ewontfix.com/7/“setuid 和 vfork”

      现在我们将面临最糟糕的情况。 Threads 和 vfork 允许你进入 两个进程共享内存空间的情况 同时运行。现在,如果另一个线程在 父调用 setuid(或任何其他影响特权的函数)?你 最终有两个具有不同权限级别的进程在一个 共享地址空间。这是一件坏事。

      考虑一个多线程服务器守护进程,最初运行 作为 root,使用 posix_spawn,用 vfork 天真地实现, 运行外部命令。不关心此命令是否以 root 身份运行 或具有低权限,因为它是一个固定的命令行,具有固定的 环境,不能做任何有害的事情。 (作为一个愚蠢的例子,让我们 说它作为外部命令运行日期,因为程序员 不知道如何使用 strftime。)

      由于它不关心,它在另一个线程中调用 setuid 没有任何 与运行外部程序同步,目的是 下拉到普通用户并执行用户提供的代码(也许 作为该用户的脚本或 dlopen 获得的模块)。不幸的是,它 刚刚授予该用户在顶部映射新代码的权限 运行 posix_spawn 代码,或更改字符串 posix_spawn 是 在孩子中传递给 exec。哎呀。

      【讨论】:

      • 在没有CLONE_VFORK的情况下使用CLONE_VM安全吗?在我看来,很难做到这一点。如何防止在子进程中弄乱父进程的内存空间?也许只有直接系统调用才有效,但调用 glibc 似乎有问题。在我的机器上,即使没有CLONE_VM,glibc 的clone() 仍然比fork() 快得多。
      猜你喜欢
      • 1970-01-01
      • 2018-07-06
      • 1970-01-01
      • 1970-01-01
      • 2016-01-02
      • 2016-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多