Я использую следующие команды оболочки Linux для подсчета количества слов в большом бинарный файл, что занимает слишком много времени (~ 10 секунд);
strings /path/to/<binary_file> | grep -c -E "word1|word2|...|wordN"
Как я могу ускорить процесс?
Я пробовал использовать только команду grep, но она не может подобрать несколько слов, поэтому я должен использовать strings. Я попытался добавить wc вместо -c, но это медленнее.
Кстати, у меня нет параллельной команды в среде Android.
На самом деле я все еще не мог написать C-эквивалент этой комбинации, любая помощь будет оценена.
Эта комбинация может правильно найти все слова в двоичном формате, я вижу это, когда печатаю слова с помощью string.
Прежде всего, у вашей команды есть несколько недостатков, которые могут привести к сбою в определенных ситуациях:
Он не будет работать со словами, состоящими из символов, отличных от ASCII, таких как буквы с диакритическими знаками, поскольку они фильтруются strings. Хотя, возможно, вы не ищете таких слов.
Он будет пропускать слова, содержащие менее 4 символов, если они изолированы. Вы должны использовать strings -n1 для общего решения.
Он будет пропускать слова, если несколько слов принадлежат одной строке текста, потому что grep -c считает строки, а не слова.
Проблемы с реализацией grep в Android (начиная с Android 8.1):
На Android вы должны использовать grep -E 'word1|word2|...|wordN' вместо grep -F -e word1 -e word2 .. -e wordN, что эквивалентно, но обычно значительно быстрее. Это связано с тем, что в Android 8 есть ошибка, из-за которой последний не учитывается должным образом.
На Android я буду использовать не только grep -a, но и grep -za. В Linux GNU grep обрабатывает символы NUL (0) в двоичном файле как конец строк, а опция -z не только бесполезна, но и нежелательна, так как выходные строки также будут заканчиваться NUL вместо новой строки. Но версия для Android ведет себя иначе: символы NUL необходимо явно обрабатывать как символы новой строки, в противном случае то, что следует за ними, игнорируется; случайно строки по-прежнему выводятся с традиционным символом новой строки.
stringsВы, вероятно, получите небольшое увеличение скорости, установив для параметра -n значение strings, равное наименьшему размеру искомого слова. Например, если ни одно из искомых слов не короче 7 символов, используйте strings -n7. Таким образом, вы снизите межпроцессное взаимодействие, и ваш grep не будет беспокоить поиск строк, которые, очевидно, не соответствуют шаблону.
stringsstrings немного дороже и может принести мало пользы (это зависит от количества двоичных символов, которые отфильтрованы - YMMV, см. Мой комментарий в следующем разделе), или даже быть вредным (см. Мои предисловия). Вы можете избавиться от него с помощью:
grep -F -a -o -e word1 -e word 2 ... -e wordN /path/to/binary_file \
| wc -l
Из-за вышеупомянутых проблем с Android grep вот версия Android:
grep -z -a -o -E 'word1|word2|...|wordN' /path/to/binary_file \
| wc -l
Помните, что использование grep | wc обязательно, поскольку grep -c считает не слова, а строки. Вот почему grep -c выглядит быстрее для вас, потому что, как только он нашел слово, grep считает +1 и переходит к следующей строке ввода, возможно, пропуская другие слова в текущей строке.
В зависимости от количества ядер вы также можете добиться хорошего ускорения за счет распараллеливания grep:
( grep -F -a -o -e word1 -e word2 /path/to/binary_file &
grep -F -a -o -e word3 -e word4 /path/to/binary_file
) | wc -l
Из-за вышеупомянутых проблем с Android grep вот версия Android:
( grep -z -a -o -E 'word1|word2' /path/to/binary_file &
grep -z -a -o -E 'word3|word4' /path/to/binary_file
) | wc -l
Здесь я предполагаю, что наиболее интенсивная обработка выполняется strings и grep, и из-за их фильтрации работа wc незначительна. Это может быть не так, в зависимости от схемы поиска. Точно так же, если strings отлично справляется с фильтрацией двоичного файла, вероятно, предпочтительнее оставить его в качестве первой инструкции. YMMV.
tr вместо stringsstrings может отфильтровать много ненужных (не ASCII) символов, и это действительно может помочь grep обрабатывать меньше данных. Вы можете пойти еще дальше, отфильтровав каждый символ, не принадлежащий к словам, которые вы ищете. Например, если вы ищете «word1», «word2» и «word3», вы можете отфильтровать все символы, которые не являются w, o, r, d, 1, 2, 3.
Если у вас есть доступ к инструменту командной строки tr, вы получите преимущества, используя его вместо strings:
tr -c -s 'word123' '\n' < /path/to/binary_file \
| grep -F -o -e word1 -e word2 -e word3 \
| wc -l
Из-за вышеупомянутых проблем с Android grep вот версия Android:
tr -c -s 'word123' '\n' < /path/to/binary_file \
| grep -E -o 'word1|word2|word3' \
| wc -l
(имейте в виду, что tr не работает с многобайтовыми символами, отличными от ASCII, но, поскольку вы используете strings в режиме ASCII, вам это уже не важно)
Вот несколько тестов, выполненных на звуковом файле размером 24 МБ; Платформа - это мой мобильный телефон с восьмиядерным процессором Android 8.1. В зависимости от вашего входного файла, строк поиска и количества ядер вы, очевидно, получите другие результаты, но это даст вам представление о возможных улучшениях скорости.
# Your original command (fixed)
$ time strings -n1 test | grep -E 'A|B|C|D' -o | wc -l
403380
0m18.93s real 0m10.05s user 0m13.77s system
# grep alone
$ time grep -z -a -E 'A|B|C|D' -o test | wc -l
403380
0m07.03s real 0m05.26s user 0m00.04s system
# Parallelized grep (x2)
$ time ( grep -z -a -E 'A|B' -o test &
grep -z -a -E 'C|D' -o test
) | wc -l
403380
0m03.56s real 0m03.12s user 0m00.03s system
# Parallelized grep -F (x4 - one per string to search)
$ time ( grep -z -a -F A -o test &
grep -z -a -F B -o test &
grep -z -a -F C -o test &
grep -z -a -F D -o test
) | wc -l
403380
0m01.04s real 0m01.88s user 0m00.05s system
# tr instead of string
$ time tr -c -s 'ABCD' '\n' < test | grep -E 'A|B|C|D' -o | wc -l
403380
0m01.60s real 0m01.27s user 0m01.41s system
# Parallelized tr + grep (x2)
$ time ( tr -c -s 'AB' '\n' < test | grep -E 'A|B' -o &
tr -c -s 'CD' '\n' < test | grep -E 'C|D' -o
) | wc -l
403380
0m00.95s real 0m01.23s user 0m02.20s system
Как видите, в этих условиях тестирования скорость между версией с strings и последней (с tr и распараллеливанием) увеличивается примерно в 20 раз.
Я попробовал, ваше предложение не может найти некоторых слов, и в качестве производительности оно кажется медленнее. time strings /system/bin/app_process | grep -c -E "Android|android" 41 0m00.10s real 0m00.13s user 0m00.00s system time grep -F -a -o -E "Android|android" /system/bin/app_process | wc -l 43 0m00.11s real 0m00.16s user 0m00.02s system
Ответ обновлен. Что я вижу из вашего тестового примера, так это то, что ваша команда пропускает некоторые слова, а не мои. Кстати, если вы хотите сравнить время, почему бы не попробовать фактический двоичный файл, 10 секунд - это не так много, и результаты будут более актуальными. Вы сделали тест скорости на распараллеливании greps?
Здравствуйте, я пробовал использовать параллельные grep, но в самом grep не хватает некоторых слов. Например, строки | grep находит 6440 слов, но grep находит только 26. Итак, если у вас есть предложения о распараллеливании строк | вариант grep, я могу попробовать. Что касается tr, к сожалению, мне не удалось запустить его, он выдает ошибки об использовании tr.
@Phillip Можете ли вы еще раз проверить, что используемые вами команды точно такие же, как и мои? Я протестировал их все на 100-мегабайтном файле с чисто случайным содержимым, и все дали мне одинаковый результат (правда, на Linux, а не на Android).
Я работаю над Android 7.0, и команда tr выдает ошибки с этими параметрами, я думаю, что это ограниченная команда в Android. В любом случае, как я уже сказал, grep не может найти все слова в одиночку, я должен добавить strings для помощи. Может, попробую что-нибудь вроде: strings <file> | (grep word1 & grep word2 & ... & grep wordN) | wc -l это тоже поиск распараллеливает? Если да, то если я смогу как-то использовать grep -c без wc -l, это будет еще быстрее. Как вы думаете?
Я пробовал следующие команды, и результаты показывают, что самым быстрым является strings <file> | grep -c -E "word1|word2"0bin.net/paste/dNlMQdqtNcp37J4s
Я только что попробовал версию tr для игрушек на Oreo с этими параметрами, и она отлично работает. Не знаю, как насчет нуги. Вы можете попробовать без опции -s, так как это всего лишь оптимизация. Я также проверил свои команды, и все они работают нормально, а ваша с strings, безусловно, самая медленная. Что касается grep -c, я объяснил в своем ответе, почему он быстрее, чем подключение к туалету: потому что он пропускает слова. Какой смысл в использовании неточного метода подсчета?
Извините, я забыл добавить часть < в команду tr. Теперь она сработала и нашла больше слов, чем комбинация strings | grep, но время выполнения увеличилось в 3 раза.
@Phillip Вы были правы в том, что есть некоторые проблемы с реализацией grep в Android. Я изменил свой ответ. Я также добавил несколько тестов, которые могут вам пригодиться.
Я принимаю этот ответ на самом деле, потому что многому у вас научился. Вы также можете добавить параллельный вариант strings для сравнения. Не знаю почему, но на моем телефоне Android 7 команда tr работает медленнее. На самом деле у меня более 30 слов, и из-за ограничения tr или strings я должен сделать словарь, прежде чем начать поиск. Но я действительно считаю, что это поможет улучшить производительность. Спасибо за помощь. Вы самые лучшие!
Спасибо, пожалуйста. Распараллеленный strings легко предсказуем: посмотрите, как распараллеленные версии grep и grep+tr делят общее время на (приблизительно) 2. Важно то, что у вас достаточно свободных ядер для удвоенного количества процессов. Если у вас четырехъядерный процессор, и вы используете свой браузер для просмотра видео, вам придется использовать ядра процессора для этой обработки, и вы не сократите общее время вдвое.
Он не может подобрать слова, потому что вы думали, что
-cсчитает слова вместо строк?