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

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

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

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

Таким образом, каждое изменение, которое начинается с короткого флага статуса Git, как [MTARDC] (сигнализирующее изменение в staged/index), включая двойные флаги (сигнализирующие частично staged изменения в index и worktree), за исключением изменений untracked или unstaged-only (начинающихся с [ ?]).

Обратите внимание, что флаги изменения R (переименование) могут сопровождаться несколькими цифрами, см. также примеры в тестовых данных ниже (возможно даже для обеих, index и worktree, например 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 "ELAPSED TIME via grep filtering from ARRAY: "; print '"$stopTime"' - '"$startTime"' }'

# Тест производительности фильтрации массива через Сопоставление шаблонов в Замене параметров 
startTime="$EPOCHREALTIME"
printf '%s\n' "${tmpArr[@]/#[? ][?MTARDC]*([0-9]) *}" &>/dev/null
stopTime="$EPOCHREALTIME"
echo
awk 'BEGIN { printf "ELAPSED TIME via parameter substitution from ARRAY: "; print '"$stopTime"' - '"$startTime"' }'

# Тест производительности фильтрации строковой переменной через Сопоставление шаблонов в Замене параметров 
startTime="$EPOCHREALTIME"
printf '%s\n' "${tmpVar//[? ][?MTARDC]*([0-9]) *([^$'\n'])?($'\n')}" &>/dev/null
stopTime="$EPOCHREALTIME"
echo
awk 'BEGIN { printf "ELAPSED TIME via parameter substitution from VAR: "; print '"$stopTime"' - '"$startTime"' }'

# РЕЗУЛЬТАТ:
#ELAPSED TIME via grep filtering from ARRAY: 0.054975
#ELAPSED TIME via parameter substitution from ARRAY: 0.00031805
#ELAPSED TIME via parameter substitution from VAR: 4.546

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

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

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

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

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

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

Теория

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

При переопределении параметров в строках необходимо учитывать наличие специальных символов и избежать чрезмерного использования регулярных выражений, так как это может снизить производительность. Оптимизация требует от нас использования встроенных механизмов Bash и Pattern Matching, которые поддерживают быстрые и менее ресурсоемкие операции по сравнению с внешними вызовами, такими как grep.

Пример

Рассмотренный пример иллюстрирует использование Pattern Matching для фильтрации массивов и строк на Bash. Первый шаг — включение расширенного сопоставления шаблонов:

shopt -s extglob

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

Применение

Для практического применения вашей задачи по фильтрации данных git status -s, можно воспользоваться следующей стратегией:

  1. Используйте массивы: Работа с массивами в Bash позволяет более эффективно управлять данными благодаря внутренним оптимизациям работы с массивами.

  2. Минимизируйте использование регулярных выражений: Хотя регулярные выражения мощны, они могут быть медленны и ресурсозатратны. Работайте с Pattern Matching, избегая использование *([^$'\n'])?($'\n').

  3. Оптимизация для строк: Если все-таки необходимо использовать строки, попробуйте разделить строку на массив строк с использованием IFS (Internal Field Separator) и работать с каждыми элементами как с элементами массива.

Вот как вы можете добиться уменьшения использования регулярных выражений для строк:

_IFS="$IFS"
IFS=$'\n'
for line in $tmpVar; do 
    [[ $line =~ ^[MTARDC] ]] && printf '%s\n' "$line"
done 
IFS="$_IFS"
  1. Обработка специальных символов: Для работы со строками, содержащими специальные символы, такие как пробел, амперсанд или кавычки, всегда обрабатывайте их заранее, правильно экранируя и подготавливая их для обработки.

  2. Используйте встроенные средства Bash: В случаях, когда это возможно, используйте встроенные средства Bash вместо вызова системных утилит, таких как grep, поскольку это может значительно замедлить выполнение, особенно при обработке больших объемов данных.

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

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

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