Как эффективно использовать GNU параллельно

8

Предположим, я хочу найти все совпадения в сжатом текстовом файле:

$ gzcat file.txt.gz | pv --rate -i 5 | grep some-pattern

pv --rateиспользуется здесь для измерения пропускной способности трубы. На моей машине это около 420Mb / s (после распаковки).

Сейчас я пытаюсь сделать параллельный grep, используя GNU параллельно.

$ gzcat documents.json.gz | pv --rate -i 5 | parallel --pipe -j4 --round-robin grep some-pattern

Теперь пропускная способность упала до ~ 260 Мбит / с. И что самое интересное, parallelсам процесс использует много процессора. Больше чем grepпроцессы (но меньше чем gzcat).

РЕДАКТИРОВАТЬ 1 : я пробовал разные размеры блока ( --block), а также разные значения для -N/ -Lпараметров. Ничто не помогает мне в этом пункте.

Что я делаю неправильно?

Денис Баженов
источник

Ответы:

9

Я действительно удивлен, что вы получаете 270 МБ / с, используя GNU Parallel --pipe. Мои тесты обычно максимально 100 МБ / с.

Ваше узкое место наиболее вероятно в GNU Parallel: --pipeне очень эффективно. --pipepartТем не менее, это: Здесь я могу получить порядка 1 ГБ / с на ядро ​​процессора.

К сожалению, есть несколько ограничений на использование --pipepart:

  • Файл должен быть доступным для поиска (т.е. без канала)
  • Вы должны быть в состоянии найти начало записи с помощью --recstart / - recend (т.е. без сжатого файла)
  • Номер строки неизвестен (поэтому вы не можете иметь 4-строчную запись).

Пример:

parallel --pipepart -a bigfile --block 100M grep somepattern
Оле Танге
источник
1
Спасибо. Есть ли причина, почему --pipeэто неэффективно? Я имею в виду, это какая-то фундаментальная проблема или конкретная реализация.
Денис Баженов
2
Да: GNU Parallel написан на perl, и --pipeкаждый байт должен проходить один процесс, который должен немного обработать каждый байт. С --pipepartбольшинством байтов центральный процесс никогда не видит: они обрабатываются порожденными заданиями. Так как узким местом является довольно небольшое количество строк, --pipeя бы приветствовал C / C ++-кодер, который переписал бы часть, которая затем будет выполняться для людей, у которых на пути есть C-компилятор.
Оле Танге
2

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

Для параллельного разделения входных данных требуется больше ресурсов процессора, чем для получения соответствующих строк с помощью grep.

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

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

Исходя из моего опыта - иногда лучше использовать lzma (например, -2) для сжатия / распаковки файлов - он имеет более высокую степень сжатия, чем gzip, поэтому гораздо меньше данных нужно читать с диска, а скорость сопоставима.

Undefine
источник
1
Действительно, это мой случай. Вместо grep используется сильно загруженный процессором процесс Java. Я немного упростил вопрос. И все же параллельное потребление большого количества ресурсов процессора не обеспечивает большой работы для процессов Java.
Денис Баженов
1

Декомпрессия является узким местом здесь. Если декомпрессия не распараллелена внутри, вы не достигнете ее самостоятельно. Если у вас есть более одной подобной работы, то, конечно, запустите их параллельно, но ваш конвейер трудно распараллелить. Разделение одного потока на параллельные потоки почти никогда не стоит и может быть очень болезненным при синхронизации и объединении. Иногда нужно просто признать, что несколько ядер не помогут с каждой выполняемой задачей.

В общем, распараллеливание в оболочке должно в основном происходить на уровне независимых процессов.

Орион
источник
1
Не похоже, что декомпрессия является узким местом в случае использования parallel. Я согласен, что это, конечно, в первом случае (без параллельного), но во втором (с параллельным) узкое место находится на параллельной стороне. Это следует из наблюдения, что пропускная способность значительно падает, как измеряется с помощью pv. Если узкое место находится в декомпрессии, пропускная способность не изменит того, что вы добавляете в конвейер. Полагаю, это очень интуитивное определение пропускной способности - то, что ограничивает пропускную способность больше всего.
Денис Баженов
1
Вполне возможно, что grep настолько быстр, что он заканчивается быстрее, чем parallelможет записать в свой канал. В этом случае большинство grepпроцессов просто ждут, чтобы получить больше, а parallelработают круглосуточно, чтобы мультиплексировать блоки в несколько каналов (которые являются дополнительными операциями ввода-вывода и могут даже блокировать декомпрессию, если буфер заполнен). Вы также пытались играть с --blockпараметром? По умолчанию это 1Mтак, пока один grep не получит 1Mданные, остальные почти наверняка уже завершены. Поэтому мы возвращаемся к тому факту, что нет смысла распараллеливать это.
Орион
1
Да, я пробовал эти варианты с большим и маленьким размером блока. А также разные значения для -N/ -Loptions. Кажется, что параметры по умолчанию очень близки к локальному оптимуму, который я испытываю :)
Денис Баженов
1
Попробуйте рассчитать это с и без pvtime). Таким образом, вы можете увидеть pv, замедляет ли это себя. Если это так, то parallelкопирование данных в каналы - это определенно дополнительные издержки. И в любом случае, я вполне уверен, что grepв этом случае почти в режиме реального времени, особенно если шаблон представляет собой простую строку без большого возврата. Кроме того, parallelбудут чередоваться и портить grepрезультаты.
Орион
1
Я проверю, что pvсамо по себе не вызывает проблем, спасибо за совет.
Денис Баженов