Более быстрое разветвление больших процессов в Linux? - PullRequest
29 голосов
/ 28 апреля 2010

Какой самый быстрый и лучший способ для современного Linux добиться того же эффекта, что и fork - execve комбо из большого процесса ?

Моя проблема заключается в том, что процесс разветвления имеет размер ~ 500 МБайт, а простой тест производительности позволяет получить только около 50 форкс / с из процесса (ср. ~ 1600 форкс / с из процесса минимального размера), что слишком медленно для предполагаемого применение.

Некоторые прибегают к поиску в Google vfork, как будто они были изобретены как решение этой проблемы ... но также предупреждают о не использовать его . Современный Linux, похоже, получил связанные вызовы clone и posix_spawn; это может помочь? Какая современная замена для vfork?

Я использую 64-битный Debian Lenny на i7 (проект может перейти на Squeeze, если posix_spawn поможет).

Ответы [ 5 ]

33 голосов
/ 01 марта 2011

В 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)
13 голосов
/ 20 мая 2010

Результат : Я собирался пойти по пути подпроцесса вспомогательного процесса, как было предложено другими ответами здесь, но затем я наткнулся на это , использующее огромную поддержку страниц для улучшения производительность вилки.

Попробовав сам, используя libhugetlbfs , чтобы просто заставить все mallocs моего приложения выделять огромные страницы, я теперь получаю около 2400 форкс / с независимо от размера процесса (сверх ассортимент меня все равно интересует). Удивительно.

8 голосов
/ 28 апреля 2010

Вы на самом деле измеряли, сколько времени занимают вилки? Цитирование страницы , на которую вы ссылались ,

У Linux никогда не было этой проблемы; Поскольку Linux использовал внутреннюю семантику копирования при записи, Linux копирует страницы только тогда, когда они изменились (на самом деле, есть еще несколько таблиц, которые необходимо скопировать; в большинстве случаев их издержки незначительны)

Таким образом, число вилок на самом деле не показывает, насколько большими будут накладные расходы. Вы должны измерить время , потребляемое вилками, и (что является общим советом), потребляемое только теми вилками, которые вы фактически выполняете, а не путем сравнения максимальной производительности.

Но если вы действительно поймете, что разветвление большого процесса является медленным, вы можете запустить небольшой вспомогательный процесс, направить основной процесс на его вход и получить от него команды на exec. Небольшой процесс будет fork и exec этими командами.

posix_spawn ()

Эта функция, насколько я понимаю, реализована через fork / exec в настольных системах. Однако во встроенных системах (особенно в тех, у которых нет MMU на плате), процессы запускаются через системный вызов, интерфейс для которого - posix_spawn или аналогичная функция. Цитирую информативный раздел стандарта POSIX с описанием posix_spawn:

  • Обычно обмен выполняется слишком медленно для среды реального времени.

  • Динамическая трансляция адресов недоступна везде, где может быть полезен POSIX.

  • Процессы слишком полезны, чтобы просто отключать POSIX всякий раз, когда он должен работать без трансляции адресов или других служб MMU.

Таким образом, POSIX нужны примитивы для создания процессов и выполнения файлов, которые могут быть эффективно реализованы без трансляции адресов или других служб MMU.

Я не думаю, что вы получите выгоду от этой функции на рабочем столе, если ваша цель - минимизировать затраты времени.

4 голосов
/ 28 апреля 2010

Если вы заранее знаете количество подпроцессов, может быть целесообразно предварительно форкнуть ваше приложение при запуске, а затем распространять информацию execv через канал. В качестве альтернативы, если в вашей программе есть какое-то «затишье», возможно, было бы разумно заблаговременно раскошелиться на один или два подпроцесса для более быстрого выполнения в более позднее время. Ни один из этих вариантов не решит проблему напрямую, но если какой-либо из подходов подходит для вашего приложения, он может позволить вам обойти проблему.

3 голосов
/ 11 ноября 2015

Я наткнулся на это сообщение в блоге: 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 and vfork"

Теперь мы дошли до худшего. Потоки и вфорки позволяют попасть в ситуация, когда два процесса совместно используют пространство памяти и работает одновременно. Теперь, что произойдет, если другой поток в родительский вызов setuid (или любая другая функция, влияющая на привилегии)? Вы в конечном итоге два процесса с различными уровнями привилегий, работающих в общее адресное пространство. И это плохо.

Рассмотрим, например, многопоточный серверный демон, запущенный изначально как root, это использует posix_spawn, реализованный наивно с vfork, чтобы запустить внешнюю команду. Не важно, запускается ли эта команда от имени пользователя root или с низкими привилегиями, так как это фиксированная командная строка с фиксированной окружающей среды и не может сделать ничего вредного. (В качестве глупого примера, давайте говорят, что это дата выполнения в качестве внешней команды, потому что программист не могу понять, как использовать strftime.)

Так как ему все равно, он вызывает setuid в другом потоке без каких-либо синхронизация с запуском внешней программы с намерением перейти к обычному пользователю и выполнить предоставленный пользователем код (возможно, скрипт или модуль, полученный dlopen) от имени этого пользователя. К сожалению это только что дал этому пользователю разрешение на отображение нового кода поверх запустить код posix_spawn или изменить строки posix_spawn переход к исполнителю у ребенка. Упс.

...