Как выжать максимум из всех ядер процессора без боли и Perl

03 May, 2026

Вы когда-нибудь задумывались, почему ваш мощный сервер с 64 ядрами скучает, пока вы пытаетесь распараллелить простую задачу через xargs -P или GNU Parallel? Я недавно наткнулся на проект forkrun, который заставил меня пересмотреть взгляды на производительность консольных утилит. Автор утверждает, что его инструмент обходит классический GNU Parallel в 400 раз на определенных задачах. Цифры звучат как маркетинговая ловушка, но за ними стоит чертовски интересная инженерная реализация.

В чем проблема со старыми инструментами

Главная беда классики вроде GNU Parallel — это накладные расходы. Чтобы распределить задачи по ядрам, эти утилиты тратят кучу ресурсов на парсинг аргументов, регулярные выражения и межпроцессное взаимодействие (IPC). В итоге получается парадокс: один поток планировщика работает на 100%, пытаясь «прокормить» остальные ядра работой, но не успевает. Чем больше у вас ядер и чем быстрее выполняется сама задача, тем сильнее планировщик становится бутылочным горлышком.

На современных многопроцессорных системах (NUMA) ситуация еще хуже. Память физически привязана к конкретным сокетам. Если данные лежат в памяти одного процессора, а обрабатываются другим, вы получаете огромные задержки на пересылку данных между сокетами. forkrun решает это на уровне архитектуры, делая ставку на локальность данных.

Что умеет forkrun и как им пользоваться

По сути, это замена xargs -P или parallel, написанная на Bash с расширением на C под капотом. Установка выглядит странно, но удобно: вы просто подгружаете скрипт через source. Внутри него зашит бинарный блоб, который компилируется и внедряется прямо в окружение шелла как встроенная команда (builtin).

Вот несколько примеров, которые показывают, насколько это просто:

# Параллелим обычную bash-функцию (parallel так не умеет без костылей)
source <(curl -sL https://raw.githubusercontent.com/jkool702/forkrun/main/frun.bash)

my_func() {
    echo "Обработка $1"
}
frun my_func < inputs.txt

# Если важен порядок вывода, добавляем флаг -k
cat file_list | frun -k sed 's/old/new/'

Самое приятное здесь — автоматизация. Вам не нужно гадать, сколько потоков запустить (-j 8 или -j 16). Утилита использует PID-регулятор, который сам подбирает оптимальный размер батча и количество воркеров в реальном времени, глядя на загрузку системы и скорость поступления данных.

Техническая магия под капотом

Если заглянуть в код, становится понятно, почему это работает так быстро. Автор не стал изобретать велосипед, а использовал возможности ядра Linux по максимуму.

Во-первых, используется системный вызов splice(). Он позволяет перебрасывать данные из stdin в память почти без копирования. Данные попадают в memfd (анонимную память), которая видна всем воркерам.

Во-вторых, это NUMA-awareness. Система понимает, на каком сокете находятся данные, и старается назначить выполнение задачи именно тому ядру, которому эта память «ближе». Это избавляет от лишнего трафика между процессорами.

В-третьих, здесь нет классических блокировок (locks). Воркеры забирают задачи через атомарные операции atomic_fetch_add. Это значит, что ядра не ждут друг друга в очереди за новой порцией работы. Как только воркер освободился, он мгновенно берет следующий кусок.

Результаты тестов

В README приведены бенчмарки на i9-7940x, и они впечатляют. На простых операциях вроде echo или printf forkrun выдает около 22 миллионов строк в секунду, в то время как GNU Parallel задыхается на 50-60 тысячах.

Интересно распределение нагрузки:

  • forkrun: загружает 27 из 28 потоков на 95-99%. Почти все время тратится на полезную работу.
  • GNU Parallel: общее использование CPU около 6%. Одно ядро полностью занято диспетчеризацией, остальные просто ждут команды.

Стоит ли переходить

Проект специфичный, но крайне полезный, если вы занимаетесь обработкой больших объемов текстовых данных, логов или биоинформатикой.

Кому он точно пригодится:

  1. Тем, кто работает на многосокетных серверах и видит, что стандартные утилиты не справляются с нагрузкой.
  2. Дата-инженерам, которым нужно быстро прогнать простые трансформации через sed, awk или grep по терабайтам файлов.
  3. Разработчикам на Bash, которым нужно параллелить внутренние функции скрипта без лишнего оверхеда.

Из минусов я бы отметил жесткую привязку к Linux (из-за memfd и специфичных сисколлов) и Bash версии 4.0+. Если вы сидите на macOS или старой FreeBSD, завести это будет непросто. Также стоит помнить, что это не полноценный фреймворк для распределенных вычислений, а именно локальный ускоритель. Но в своей нише он сейчас выглядит как один из самых быстрых инструментов.

Попробовать стоит хотя бы ради того, чтобы увидеть, как ваша консоль буквально «проглатывает» огромные файлы, которые раньше заставляли её задумываться на минуты.