Сделайте замену параметров в строке, разделенной новой строкой, более эффективной.

Вопрос или проблема

Следующий код должен продемонстрировать и помочь с тестированием неэффективных выражений Сопоставления шаблонов в Замену параметров для строк с разделителями в виде новой строки в переменной var по сравнению с массивом.

Цель состоит в том, чтобы добиться хотя бы производительности на уровне, сравнимой с grep, при фильтрации только результатов git status -s, которые относятся к изменениям индекса (полностью или частично подготовленным).

Фактически, каждое изменение, начинающееся с символа краткого статуса Git, например [MTARDC] (сигнализирует об изменении в индексе), в том числе двойные флаги (сигнализирует о частично подготовленных изменениях в индексе и рабочем дереве), за исключением изменений неслеживаемых или только с неслеживаемыми (начинающихся с [ ?]).

Обратите внимание, что флаги изменений R (переименование) могут сопровождаться несколькими цифрами, см. также примеры в тестовых данных ниже (возможно, даже для обоих, индекса и рабочего дерева, например R104R104).
См.: короткий формат статуса Git

Тестовые данные также содержат имена файлов с потенциально проблемными специальными символами, такими как escape-последовательности, пробел или амперсанд: [\ $*"].

Также обратите внимание, что замена на основе сопоставления шаблона требует отрицания шаблонов по сравнению с RegEx для grep с теми же результатами. Чтобы напечатать результаты, просто прокомментируйте части &>/dev/null.

#! /bin/bash

# Включить расширенное сопоставление шаблонов
shopt -s extglob 

clear
unset -v tmpVar tmpArr

# Заполнить tmpVar и tmpArr для тестирования
for i in {1..3}; do
    tmpVar+=' A addedW1'[$i]$'\n'
    tmpVar+='A  addedI1'[$i]$'\n'
    tmpVar+='AM addedA1'[$i]$'\n'
    tmpVar+=' C copiedW1'[$i]$'\n'
    tmpVar+='C  copiedI1'[$i]$'\n'
    tmpVar+='CR copied A1'[$i]$'\n'
    tmpVar+=' D removedW1'[$i]$'\n'
    tmpVar+='D  removedI1'[$i]$'\n'
    tmpVar+='DM removedA1'[$i]$'\n'
    tmpVar+=' M modifiedW1'[$i]$'\n'
    tmpVar+='M  modifiedW1'[$i]$'\n'
    tmpVar+='MR modifiedA1'[$i]$'\n'
    tmpVar+=' R101 renamedW1'[$i]$'\n'
    tmpVar+='R102  renamedI2'[$i]$'\n'
    tmpVar+='R103D renamedA1'[$i]$'\n'
    tmpVar+=' T typeChangedW1'[$i]$'\n'
    tmpVar+='T  typeChangedI1'[$i]$'\n'
    tmpVar+='TM typeChangedA1'[$i]$'\n'
    tmpVar+='?? exec2.bin'[$i]$'\n'
    tmpVar+='?? file1.txt'[$i]$'\n'
    tmpVar+='?? test.launch2'[$i]$'\n'
    tmpVar+='A  file00 0.bin'[$i]$'\n'
    tmpVar+='A  file11*1.bin'[$i]$'\n'
    tmpVar+='A  file22\03457zwei.bin'[$i]$'\n'
    tmpVar+='A  file33\t3.bin'[$i]$'\n'
    tmpVar+='A  file44$4.bin'[$i]$'\n'
    tmpVar+='A  file55"$(echo EXE)"5.bin'[$i]$'\n'
    tmpVar+='M  exec1.bin'[$i]$'\n'
    tmpVar+=' M test.launch1'[$i]$'\n'
    tmpVar+=' M myproject/src/main/java/util/MyUtil.java'[$i]$'\n'
    tmpVar+='M  myproject/src/test/util/MyUtilTest.java'[$i]$'\n'
    tmpVar+='R104R104 myproject/src/test/util/MyUtil2Test.java'[$i]$'\n'
    tmpVar+=' A invalidAdd'[$i]$'\n'
    tmpVar+='R invalidRename'[$i]$'\n'
    tmpArr+=(" A addedW1[$i]")
    tmpArr+=("A  addedI1[$i]")
    tmpArr+=("AM addedA1[$i]")
    tmpArr+=(" C copiedW1[$i]")
    tmpArr+=("C  copiedI1[$i]")
    tmpArr+=("CR copied A1[$i]")
    tmpArr+=(" D removedW1[$i]")
    tmpArr+=("D  removedI1[$i]")
    tmpArr+=("DM removedA1[$i]")
    tmpArr+=(" M modifiedW1[$i]")
    tmpArr+=("M  modifiedW1[$i]")
    tmpArr+=("MR modifiedA1[$i]")
    tmpArr+=(" R101 renamedW1[$i]")
    tmpArr+=("R102  renamedI2[$i]")
    tmpArr+=("R103D renamedA1[$i]")
    tmpArr+=(" T typeChangedW1[$i]")
    tmpArr+=("T  typeChangedI1[$i]")
    tmpArr+=("TM typeChangedA1[$i]")
    tmpArr+=("?? exec2.bin[$i]")
    tmpArr+=("?? file1.txt[$i]")
    tmpArr+=("?? test.launch2[$i]")
    tmpArr+=("A  file00 0.bin[$i]")
    tmpArr+=("A  file11*1.bin[$i]")
    tmpArr+=("A  file22\03457zwei.bin[$i]")
    tmpArr+=("A  file33\t3.bin[$i]")
    tmpArr+=("A  file44$4.bin[$i]")
    tmpArr+=('A  file55"$(echo EXE)"5.bin['$i']')
    tmpArr+=("M  exec1.bin[$i]")
    tmpArr+=(" M test.launch1[$i]")
    tmpArr+=(" M myproject/src/main/java/util/MyUtil.java[$i]")
    tmpArr+=("M  myproject/src/test/util/MyUtilTest.java[$i]")
    tmpArr+=("R104R104 myproject/src/test/util/MyUtil2Test.java[$i]")
    tmpArr+=(" A invalidAdd[$i]")
    tmpArr+=("R invalidRename[$i]")
done

# Тест производительности фильтрации массива или строки с помощью grep
_IFS="$IFS"; IFS=$'\n'
startTime="$EPOCHREALTIME"
grep '^[MTARDC]' <<<"${tmpArr[*]}" &>/dev/null
stopTime="$EPOCHREALTIME"
IFS="$_IFS"
echo
awk 'BEGIN { printf "ПРОШЕДШЕЕ ВРЕМЯ при фильтрации через grep из МАССИВА: "; print '"$stopTime"' - '"$startTime"' }'

# Тест производительности фильтрации массива с помощью сопоставления шаблонов в замене параметров 
startTime="$EPOCHREALTIME"
printf '%s\n' "${tmpArr[@]/#[? ][?MTARDC]*([0-9]) *}" &>/dev/null
stopTime="$EPOCHREALTIME"
echo
awk 'BEGIN { printf "ПРОШЕДШЕЕ ВРЕМЯ при замене параметра из МАССИВА: "; print '"$stopTime"' - '"$startTime"' }'

# Тест производительности фильтрации строки через сопоставление шаблонов в замене параметров 
startTime="$EPOCHREALTIME"
printf '%s\n' "${tmpVar//[? ][?MTARDC]*([0-9]) *([^$'\n'])?($'\n')}" &>/dev/null
stopTime="$EPOCHREALTIME"
echo
awk 'BEGIN { printf "ПРОШЕДШЕЕ ВРЕМЯ при замене параметра из ПЕРЕМЕННОЙ: "; print '"$stopTime"' - '"$startTime"' }'

# РЕЗУЛЬТАТ:
#ПРОШЕДШЕЕ ВРЕМЯ при фильтрации через grep из МАССИВА: 0.054975
#ПРОШЕДШЕЕ ВРЕМЯ при замене параметра из МАССИВА: 0.00031805
#ПРОШЕДШЕЕ ВРЕМЯ при замене параметра из ПЕРЕМЕННОЙ: 4.546

Как видно, grep хорош, но вариант №2 (фильтрация массива через Замену параметров) намного быстрее, так что для огромных массивов есть хорошая альтернатива grep.

Фильтрация строк переменной через Замену параметров, с другой стороны, ужасно медленна.

В основном из-за того, что соответствующий шаблон не может заканчиваться на * (что удалит все до конца строки от первого совпадения), а нуждается в *([^$'\n'])?($'\n') вместо этого, чтобы соответствовать (и удалить) все в совпадении до следующей новой строки и, в некоторой степени, из-за жадного сопоставления tmpVar//.

Существует ли другой способ или шаблон для обработки строк переменной с Сопоставлением шаблонов, так же как с массивом – без использования проблематичного и замедляющего сопоставления с отрицанием символа новой строки и чтобы приблизиться к скорости примера с массивом?

Короткий ответ на мой начальный вопрос может быть:
“избегайте этого”
.

Что касается замены параметров фильтра для строки, разделенной новой строкой, например в:

_IFS="$IFS"; IFS=$'\n'
tmpArr=(${tmpVar//[? ][MTARDC?]+([^$'\n'])?($'\n')})
IFS="$_IFS"

Я не смог найти жизнеспособной альтернативы этому проблематичному, но, к сожалению, необходимому расширенному СОПОСТАВИТЕЛЮ ШАБЛОНОВ.

Что теперь?

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

При использовании высокопроизводительной машины (здесь: 8-ядерной) или в случаях, когда не нужно заботиться об эффективности, вероятно, не будет важно, какой метод будет выбран, если остаемся ниже нагрузки в 1000 элементов и 100 циклов – возможно, вам стоит прекратить чтение здесь.

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

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

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

Некоторые методы будут работать исключительно хорошо в ограниченной группе случаев использования, тогда как станут практически непригодными для других сценариев – особенно, где участвует огромное количество наборов (по 10 элементов). Другие работают отлично, если их зацикливали много раз с небольшим количеством элементов.

Спойлер:

ЗАМЕНА ПАРАМЕТРОВ через массив с извлечением результатов из нативного массива был единственным подходом, который масштабировался и работал лучше всего для всех видов случаев использования.

Результаты в деталях:

  1. Для 10 или менее элементов результаты между 2 совершенно разными машинами с разными ОС не отличались заметно или почти совпадали.

  2. ЗАМЕНА ПАРАМЕТРОВ (PSUB) не рекомендуется для фильтрации подстрок элемента VAR (${var//[? ]*}), и проявляет себя исключительно плохо, особенно с очень большими строками.

Таким образом, избегайте фильтрации большого количества элементов, разделенных новой строкой, в одной СТРОКЕ ВАР через PSUB. Фильтрация небольших наборов <~50 один раз может быть допустима, но не следует делать нечто подобное с большими строками и/или внутри циклов:

_IFS="$IFS"; IFS=$'\n'
tmpArr=(${tmpVar//[? ][MTARDC?]+([^$'\n'])?($'\n')})
IFS="$_IFS"

Примечание:
РАСШИРЕННОЕ СОПОСТАВЛЕНИЕ ШАБЛОНОВ, как <@*+?!>(...) может значительно замедлить скрипт (это также верно для [[ $var =~ ... ]], по сравнению с [[ $var == *...* ]]).

Сканирование всей строки через РАСШИРЕННОЕ СОПОСТАВЛЕНИЕ ШАБЛОНОВ необходимо здесь, чтобы сопоставить и удалить отдельные элементы из строки, что существенно замедляет этот вид фильтров.

Так что для огромных строк элементов, вероятно, лучше сначала разделить на новые строки, и обработать полученный массив, как указано ниже, или выполнить все это внутри сценария AWK, или использовать GREP (если не зациклено слишком часто), который показал наилучшие результаты для этого типа использования, превосходя все другие методы, когда нагрузки становятся действительно большими (~1М элементов строки).

  1. Когда применяются к массивам, с другой стороны, ЗАМЕНА ПАРАМЕТРОВ может быть одним из лучших доступных вариантов для почти любого сценария.

    Он показал превосходную эффективность, превосходя другие методы/специализированные инструменты, такие как SED, GREP и AWK, особенно для небольших и средних наборов элементов (<100.000), с до 1000 итераций, и даже более очевидно, с еще большим количеством итераций.

    Начиная с 100.000 элементов и для данное число 1000 итераций, AWK и SED начали набирать обороты, и особенно SED стал значительно быстрее с массивами, в то время как GREP остался позади.

    Штрафы вызова для специализированных инструментов, таких как AWK, SED и особенно GREP:

    Они страдают от значительного падения производительности для наборов <=1000 во многих итерациях, начиная с ~50-100 циклов и увеличиваясь в геометрической прогрессии, оставляя медленные машины в значительном невыгодном положении.

    Таким образом, используйте массивы вместо строк с разделителями (новой строки), где это возможно, и применяйте нативное назначение массива, особенно при создании и работе с большими наборами элементов, и рассмотрите возможность использования ЗАМЕНЫ ПАРАМЕТРА при фильтрации этих массивов.

    Создание и фильтрация массива через PSUB/PMATCH – если используется осторожно подобранное простое сопоставление шаблона – а затем снова сохранение результата через IFS=$'\n'/нативное назначение массива, может иметь чрезвычайно положительный эффект на производительность, по сравнению с другими подходами!

Преобразование из строк с разделителем новой строки с ограниченным IFS:

_IFS="$IFS"; IFS=$'\n'
array=("$elements")
IFS="$_IFS"

Или, для сбора/фильтрации удаления из массива, просто делайте (и фильтруйте пустоты позже):

array=("${tmpArr[@]/#[? ]*}")

Это будет выполнять намного лучше, чем выделенный цикл сбора и значительно лучше, чем readarray – особенно, когда создание массива само по себе зациклено. И это может очень эффективно конвертировать существующие строки с элементами, разделенными новой строкой, в гораздо более пригодные для обработки массивы.

Это должно работать очень хорошо до 1.000.000 элементов, по сравнению с любым другим специализированным инструментом, таким как AWK, SED или GREP, или может даже значительно превзойти их, особенно для числа элементов ниже 10.000, и с увеличением количества итераций наблюдается всё большая разница в производительности.

Усилие по созданию новых массивов через нативное array+=(...) минимально; сбор фильтрованных результатов массивов заново и дальнейшая их обработка однозначно превосходит обработку объединённых крупных строк во всех случаях, кроме самых простых и самых простых применений.

Контрастняя, создание больших наборов строк, разделенных новой строкой в выделенном цикле, работает очень плохо и может даже застыть для очень огромных строк>1.000.000 элементов, так что преимущество, которое GREP или SED могут предложить здесь для обработки, по сравнению с использованием массивов, отнято в начале из-за стоимости создания строки в общем.

  1. Для небольших наборов элементов <=1000, выделенный цикл FOR/WHILE read -r с (простой) PATTERN MATCHING (PMATCH) для каждого элемента также может быть хорошим вариантом, который все еще должен значительно превосходить обычно выделенные инструменты, такие как AWK, SED и GREP.

При условии, что используется метод здесь докум или прямое расширение массива/PMATCH:

_IFS="$IFS"; IFS=$'\n'
while read -r; do ...; done <<<"${tmp[@]/#[ ?]*}" 
IFS="$_IFS"

for e in ""${tmp[@]/#[ ?]*}"; do ... done

Не гораздо более дорогая потоковая передача через Pipe, Подпроцесс или Процессная замена, такие как:

for e in "$(printf '%s\n' "${tmp[@]}")"; do ... done

while read -r; do ...; done < <(printf '%s\n' "${tmp[@]}")

Осторожно, на медленных машинах производительность может резко упасть значительно, начиная даже с ~50 элементов при >10 итерациях. Для быстрого оборудования это может быть ~1000 элементов при более чем 100 итерациях, пока вещи не становятся заметно медленными.

Если количество элементов остается ниже этих критических границ, однако, ЦИКЛ/PMATCH показал гораздо лучшее поведение в масштабе внешнего цикла, чем внешние вызовы программ, такие как GREP, лучше всего только с подходом PSUB.

  1. Среди выделенных инструментов AWK и GREP с RegEX обеспечивают наивысшую производительность, с SED/замена значительно медленней, тогда как SED/удаление лучше и немного отстает.

    Ниже ~50 элементов затраты на вызов AWK, SED и GREP существенно увеличивают время выполнения, по сравнению с for/while циклами, ~4 раза или больше.

    Здесь выделенные фильтрационные циклы или PSUB в целом работают лучше.

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

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

    В противном случае, для ~1000 элементов и более AWK, SED и GREP должны показывать превосходную производительность (~10x или лучше), чем явные for/while с PATTERN MATCHING.

    Только для очень больших массивов (>~1.000.000), они могут преодолеть огромное начальное преимущество производительности PSUB.

  2. Для многих итераций/высоких требований к производительности, не придерживайтесь догматично принципа DRY – в Bash скриптинге каждая простая команда, как if, может иметь значительное влияние на время выполнения.

    Если не хватает производительности, обертывание выделенных путей переключения в циклы с отдельными, внешне переключаемыми циклами <постойными блоками кода и ИЗБЕГАЯ ВСЕГО НЕОБЯЗАТЕЛЬНОГО КОДА ВНУТРИ циклов, может оказать значительное влияние на производительность:

Сравнительные времена выполнения цикла/ветки условия для 1.000.000 итераций:

# ВНЕШНИЕ ПЕРЕКЛЮЧАЕМЫЕ ЦИКЛЫ (~13% быстрее)
if [[ $RESTRICT ]]; then
    while [[ $((++i)) -le $loops ]]; do
        # BLOCK1
    done
elif [[ $CASE1 ]]; then
    while [[ $((++i)) -le $loops ]]; do
        # BLOCK2
    done
elif [[ $CASE2 ]]; then
    while [[ $((++i)) -le $loops ]]; do
        # BLOCK3
    done
else
    while [[ $((++i)) -le $loops ]]; do
        # BLOCK1
        # BLOCK2
        # BLOCK3
    done
fi

-> 48,4s

# ПЕРЕКЛЮЧЕНИЕ ВНУТРИ ЦИКЛА (намного более компактный код, но ~13% медленнее)
while [[ $((++i)) -le $loops ]]; do
    if [[ -z $CASE1 ]]; then
        # BLOCK1
        # [[ $RESTRICT ]] && continue
        # BLOCK2
    fi
    if [[ -z $CASE2 ]]; then
        # BLOCK3
    fi
done

-> 61,8s

Некоторые 13% времени выполнения можно было сэкономить, хоть и за счет высокой избыточности кода.

  1. Времена создания тестовых наборов массивы по сравнению с накопленной строкой переменной различаются чрезвычайно, чем больше элементов, тем более значительно.

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

    Массив из 1.000.000 элементов все еще создавался примерно за 4 с, а преобразование его в накопленную строку заняло бы всего 0,8 с, но непосредственное создание строки такого же размера элементов в цикле потребовало бы около 127 с:

Время настройки теста var от/с массива

В ОБЩЕМ:

При фильтрации массивов в больших циклах, использование подходящего выражения ФИЛЬТРУЮЩИХ МАССИВОВ должно быть крайне наиболее эффективным и быстрым решением.

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

Вот некоторые результаты тестов:

Intel W10 ноутбук 4-ядра   | AMD Linux рабочая станция 8-ядра

1x à 10 элементов в 
PSUB        0,000083с       | 0,000082с     лучше всего
FOR/PMATCH  0,00015с        | 0,00013с      следующий лучший 
SED | GREP  0,039с          | 0,0014с       следующий лучший

10x à 10 элементов в 
PSUB        0,00063с        | 0,00066с      лучше всего
FOR/PMATCH  0,0015с         | 0,0012с       следующий лучший
SED | GREP  0,38с           | 0,013с        следующий лучший

100x à 10 элементов в 
PSUB        0,006с          | 0,0052с       лучше всего
FOR/PMATCH  0,016с          | 0,01с         следующий лучший
SED | GREP  4,03с           | 0,1с          следующий лучший

1.000x à 10 элементов в 
PSUB        0,06с           | 0,049с        лучше всего
FOR/PMATCH  0,152с          | 0,1с          следующий лучший
SED | GREP  39,85с          | 1,1с          следующий лучший

10.000x à 10 элементов в 
PSUB        1,1с            | 0,5с          лучше всего
FOR/PMATCH  1,059с          | 1,0с          следующий лучший
SED | GREP  7м8,4с          | 21,4с         следующий лучший

1.000x à 10 элементов в 
PSUB        0,062с          | 0,049с        лучше всего
FOR/PMATCH  0,15с           | 0,10с         следующий лучший 
SED | GREP  40,62с          | 1,10с         следующий лучший

1.000x à 100 элементов в 
PSUB        0,16с           | 0,14с         лучше всего
FOR/PMATCH  0,79с           | 0,62с         следующий лучший
SED | GREP  40,20с          | 1,13с         следующий лучший

1.000x à 1.000 элементов в 
PSUB        2,06с           | 0,64с         лучше всего
FOR/PMATCH  9,85с           | 3,84с         следующий лучший
AWK | GREP  47,03с          | 1,78с         следующий лучший

1.000x à 10.000 элементов в 
PSUB        16,6c           | 7,12c         лучше всего
FOR/PMATCH  74,75c          | 36,47c        следующий лучший
AWK | GREP  64,39c          | 7,35c         следующий лучший

1.000x à 100.000 элементов в 
PSUB        188,89c         | 78,02c        лучше всего
FOR/PMATCH  780,61c         | 387,8с        следующий лучший
AWK | GREP  146,04c         | 51,04c        следующий лучший

1x à 2000 элементов в 
PSUB        0,11c           | ?             лучше всего
FOR/PMATCH  0,19c           | ?             следующий лучший
GREP        84,19c          | ?             следующий лучший

1x à 5000 элементов в 
PSUB        0,28c           | ?             лучше всего
FOR/PMATCH  0,46c           | ?             следующий лучший
SED         213,75c         | ?             следующий лучший

1x à 10000 элементов в 
PSUB        0,63c           | ?             лучше всего
FOR/PMATCH  0,91c           | ?             следующий лучший
SED         440,62c         | ?             следующий лучший

Для очень больших массивов (свыше 1.00.000), возможно, стоит использовать специализированный инструмент, в особенности GREP и AWK:

1x 1.000.000 за ~0,9с с помощью GREP, по сравнению со следующим лучшим ~1,7с с помощью AWK и PSUB.

фильтрация огромных наборов элементов один раз

Для фильтрации одного такого набора всего один раз, SED(на 8-ядерной)/GREP(на 4-ядерной) показывает наилучшую производительность, за ними следуют AWK и PSUB.

Для огромных СТРОК ВАР – в зависимости от сложности используемого RegEx – GREP показывает наилучшую общую производительность,
за ним следуют AWK, затем SED.

PSUB не рассматривается для этого использования, так как он вылезет за пределы.

Производительность специализированных инструментов уменьшается примерно в 2 раза при обработке МАССИВОВ до примерно такой же производительности, как и PSUB, который соответствует или лучше.

Некоторые диаграммы для иллюстрации:

фильтрация x раз 10 элементов
фильтрация x раз 100 элементов
фильтрация x раз 1.000 элементов
фильтрация 10.000 раз 10 элементов

ТЕСТОВАЯ НАСТРОЙКА:

Самый близкий возможный/лучший фильтр Регулярность (^[ ?].*), замена (${var/#[? ]*}) или шаблон сравнения (!= [\ \?]*) был применен для каждого из следующих подходов к фильтрации:

AWK REGEX из МАССИВА
AWK REGEX из VAR
SED REGEX из МАССИВА
SED REGEX УДАЛЕНИЕ из VAR
GREP REGEX из МАССИВА
GREP REGEX из VAR
ЗАМЕНА ПАРАМЕТРОВ из МАССИВА 
ЦИКЛ FOR СО СООТВЕТСТВИЕМ ШАБЛОНА по МАССИВУ 
ЦИКЛ WHILE СООТВЕТСТВИЕ ШАБЛОНУ ПО VAR 
ЦИКЛ FOR СООТВЕТСТВИЕ ШАБЛОНУ ПО VAR 

[PSUB из VAR]
-> пропущено, слишком малая производительность для любых, кроме самых низких требований!

Сценарий теста заключался в фильтрации всех git status -s изменений, начинающихся с [ ?] (неслеживаемый + неподготовленный), из данного набора в 10 элементов изменений, который затем будет умножен в десять раз на (100×1 -> 1000 элементов), по каждому из случаев тестов с более высокими требованиями, и/или снова зациклено в 10 раз чаще каждый раз, начиная с 1 итерации, затем 10, 100, 1k, 10k, 100k, до 1М циклов (где еще возможно).

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

Тесты проводились на 2 разных тестовых машинах: Intel 4-ядерный ноутбук (Cygwin/MinGW) и AMD 8-ядерная рабочая станция (Linux/Bash).

‘И ПОБЕДИТЕЛЬ’:

Безусловно, лучшим по производительности вариантом по всем графикам является нативный метод ЗАМЕНЫ ПАРАМЕТРОВ в удалении из МАССИВА, при условии, что используются только простые выражения сопоставления шаблонов. С другой стороны, расширенные выражения сопоставления шаблонов, такие как [+*@?!](...), могут быстро испортить преимущество производительности.

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

Награды за производительность могут быть огромными.

При многих итерациях малых наборов, он превосходил выделенные инструменты в ~10-20 раз; при множестве оберточных циклов применение этой техники однозначно выиграет у внешних вызовов программ в любом случае.

ПРЕДУПРЕЖДЕНИЯ:

Тесты следует расширить для сравнения более сложных фильтров относительно этих результатов.

.

Ответ или решение

Оптимизация подстановки параметров в строках с разделением по новой строке — задача, которая может значительно повлиять на производительность скриптов на Bash, особенно когда дело касается обработки большого количества данных или частой фильтрации результатов. В данной статье мы рассмотрим, как можно улучшить производительность таких операций на примере фильтрации вывода команды git status -s, чтобы выделить только изменения индекса.

Теоретические основы

Подстановка параметров в Bash — это мощный механизм, который позволяет манипулировать строками и массивами. Однако, этот механизм может быть как эффективным, так и ресурсоемким в зависимости от используемых шаблонов. Судя по всему, использовать расширенные шаблоны (+, *, ?, !) особенно накладно по времени, что предопределяет определенные ограничения и подводные камни в использовании этого метода.

Большие строки, сформированные из большого количества дочерних элементов, могут создавать серьезную нагрузку на процессор. Это особенно актуально при попытке фильтрации строк с использованием подстановки параметров, так как подобный подход может оказаться неэффективным, занимая значительно больше времени, чем при работе с массивами. Изучение альтернативных подходов, включая преобразование данных в массивы перед обработкой, может предложить существенно более высокую производительность.

Примеры

Рассмотрим несколько примеров использования подстановки параметров и связанных с этим проблем. Например, у нас имеется переменная-строка с новой строкой-разделителем, содержащая изменения, зафиксированные командой git status -s. В такой строке подстановка параметров с шаблоном [MTARDC?]+([^$'\n'])?($'\n') оказывается крайне неэффективной для фильтрации, поскольку требует выполнения сложных условий множественных операций поиска и замены.

Сравним это с примером преобразования строки в массив и последующей фильтрацией с использованием более простых и прямолинейных шаблонов:

_IFS="$IFS"; IFS=$'\n'
filtered_array=("${original_array[@]/#[? ][?MTARDC]*([0-9]) *}")
IFS="$_IFS"

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

Практическое применение

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

Одним из примеров является использование встроенных средств Bash, таких как grep, awk, sed, которые могут оказаться более подходящими для решения задач фильтрации в случае больших объемов данных. Например, для фильтрации изменений индекса Git с помощью команды grep на больших массивах данных производительность может возрастать значительным образом:

grep '^[MTARDC]' <<<"${tmpArr[*]}"

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

Итог

В заключение, хотя подстановка параметров в Bash является мощным инструментом, его эффективность во многом зависит от выбранных шаблонов и структуры данных. Рекомендуется использовать массивы вместо строк для больших объемов данных, а для более сложных вариантов фильтрации использовать встроенные утилиты, такие как grep, awk и sed. Этот подход позволит добиться более высокой производительности и снизит нагрузку на систему, что критично в условиях ограниченных ресурсов и больших нагрузок.

Оцените материал
Добавить комментарий

Капча загружается...