Цикл сопоставления с образцом в bash очень медленный

Когда я делаю это с помощью awk, это относительно быстро, даже несмотря на то, что это Row By Agonizing Row (RBAR). Я попытался сделать в Bash более быстрое и элегантное решение, устойчивое к ошибкам, которое потребовало бы только гораздо меньшего количества проходов через файл. Для прохождения первых 1000 строк с помощью этого кода с помощью bash потребуется около 10 секунд. Я могу сделать 25 проходов через все миллионы строк файла с помощью awk примерно за одно и то же время! Почему bash на несколько порядков медленнее?

  while read line
    do
    FIELD_1=`echo "$line" | cut -f1`
    FIELD_2=`echo "$line" | cut -f2`

    if [ "$MAIN_REF" == "$FIELD_1" ]; then
      #echo "$line"
      if [ "$FIELD_2" == "$REF_1" ]; then
         ((REF_1_COUNT++))
      fi

      ((LINE_COUNT++))

      if [ "$LINE_COUNT" == "1000" ]; then
        echo $LINE_COUNT;
      fi
    fi
done < temp/refmatch

Поскольку read медленный - stackoverflow.com/questions/13762625/… Почему бы не написать это на Python? Здесь нет ничего специфичного для bash.

Chase 26.10.2018 05:26

Ваш код создает слишком много процессов, и это сделает его еще медленнее. awk или python - ваш лучший выбор.

codeforester 26.10.2018 05:29
2
2
105
2

Ответы 2

Баш работает медленно. Просто так оно и есть; он предназначен для наблюдения за выполнением определенных инструментов и никогда не оптимизировался для повышения производительности.

Тем не менее, вы можете сделать его менее медленным, избегая очевидных недостатков. Например, read разделит свой ввод на отдельные слова, так что будет быстрее и понятнее написать:

while read -r field1 field2 rest; do
  # Do something with field1 and field2

вместо того

while read line
    do
    FIELD_1=`echo "$line" | cut -f1`
    FIELD_2=`echo "$line" | cut -f2`

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

Если вы используете cut, потому что ваши строки разделены табуляцией, а не просто пробелами, вы можете добиться того же эффекта с read, установив IFS локально:

while IFS=$'\t' read -r field1 field2 rest; do
  # Do something with field1 and field2

Тем не менее, не ожидайте, что это будет быстро. Просто это будет не так мучительно медленно. Лучше исправить свой сценарий awk, чтобы он не требовал нескольких проходов. (Если вы можете сделать это с помощью bash, это можно сделать с помощью awk и, вероятно, с меньшим количеством кода.)

Примечание: я устанавливаю три переменные, а не две, потому что read помещает оставшуюся часть строки в последнюю переменную. Если есть только два поля, никакого вреда не будет; установка переменной в пустую строку - это то, что bash может сделать достаточно быстро.

Как отмечает @codeforester, исходный сценарий bash порождает так много подпроцессов. Вот измененная версия для минимизации накладных расходов:

#!/bin/bash

while IFS=$'\t' read -r FIELD_1 FIELD_2 others; do

  if [[ "$MAIN_REF" == "$FIELD_1" ]]; then
    #echo "$line"
    if [[ "$FIELD_2" == "$REF_1" ]]; then
      let REF_1_COUNT++
    fi

    let LINE_COUNT++
      echo "$LINE_COUNT"

    if [[ "$LINE_COUNT" == "1000" ]]; then
      echo "$LINE_COUNT"
    fi
  fi
done < temp/refmatch

Он работает более чем в 20 раз быстрее, чем исходный, но я боюсь, что это может быть ограничением сценария bash.

Другие вопросы по теме