Более быстрая обработка больших процессов в Linux?

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

Моя проблема заключается в том, что процесс forking составляет ~ 500 Мбайт большой, а простой тестовый тест достигает всего 50 вилок/с процесса (cf ~ 1600 forks/s из процесса минимального размера), который слишком медленный для предполагаемого приложение.

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

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

Ответ 1

Результат. Я собирался спуститься по первому порожденному подпроцессу вспомогательного подпроцесса, как предложено здесь другими ответами, но затем я наткнулся на this re, используя огромную поддержку страниц для повышения производительности fork.

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

Ответ 2

В 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)

Ответ 3

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

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

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

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

posix_spawn()

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

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

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

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

Таким образом, 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().

Вкратце, вызывая клон следующим образом, мы создаем дочерний процесс, который очень похож на поток, но все же может вызвать Exec():

Однако я думаю, что он все еще может быть затронут проблемой setuid:

http://ewontfix.com/7/ "setuid и vfork"

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

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

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