Бинарный файл с самыми быстрыми строками | grep

Я использую следующие команды оболочки Linux для подсчета количества слов в большом бинарный файл, что занимает слишком много времени (~ 10 секунд);

strings /path/to/<binary_file> | grep -c -E "word1|word2|...|wordN"

Как я могу ускорить процесс?

Я пробовал использовать только команду grep, но она не может подобрать несколько слов, поэтому я должен использовать strings. Я попытался добавить wc вместо -c, но это медленнее. Кстати, у меня нет параллельной команды в среде Android.

На самом деле я все еще не мог написать C-эквивалент этой комбинации, любая помощь будет оценена.

Он не может подобрать слова, потому что вы думали, что -c считает слова вместо строк?

that other guy 07.07.2018 01:32

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

Phillip 07.07.2018 10:23
1
2
319
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Предисловие

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

  • Он не будет работать со словами, состоящими из символов, отличных от 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 не будет беспокоить поиск строк, которые, очевидно, не соответствуют шаблону.

Избавляемся от strings

strings немного дороже и может принести мало пользы (это зависит от количества двоичных символов, которые отфильтрованы - 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 вместо strings

strings может отфильтровать много ненужных (не 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

Phillip 07.07.2018 10:32

Ответ обновлен. Что я вижу из вашего тестового примера, так это то, что ваша команда пропускает некоторые слова, а не мои. Кстати, если вы хотите сравнить время, почему бы не попробовать фактический двоичный файл, 10 секунд - это не так много, и результаты будут более актуальными. Вы сделали тест скорости на распараллеливании greps?

xhienne 07.07.2018 19:20

Здравствуйте, я пробовал использовать параллельные grep, но в самом grep не хватает некоторых слов. Например, строки | grep находит 6440 слов, но grep находит только 26. Итак, если у вас есть предложения о распараллеливании строк | вариант grep, я могу попробовать. Что касается tr, к сожалению, мне не удалось запустить его, он выдает ошибки об использовании tr.

Phillip 07.07.2018 22:52

@Phillip Можете ли вы еще раз проверить, что используемые вами команды точно такие же, как и мои? Я протестировал их все на 100-мегабайтном файле с чисто случайным содержимым, и все дали мне одинаковый результат (правда, на Linux, а не на Android).

xhienne 08.07.2018 00:33

Я работаю над Android 7.0, и команда tr выдает ошибки с этими параметрами, я думаю, что это ограниченная команда в Android. В любом случае, как я уже сказал, grep не может найти все слова в одиночку, я должен добавить strings для помощи. Может, попробую что-нибудь вроде: strings <file> | (grep word1 & grep word2 & ... & grep wordN) | wc -l это тоже поиск распараллеливает? Если да, то если я смогу как-то использовать grep -c без wc -l, это будет еще быстрее. Как вы думаете?

Phillip 08.07.2018 23:47

Я пробовал следующие команды, и результаты показывают, что самым быстрым является strings <file> | grep -c -E "word1|word2"0bin.net/paste/dNlMQdqtNcp37J4s

Phillip 08.07.2018 23:55

Я только что попробовал версию tr для игрушек на Oreo с этими параметрами, и она отлично работает. Не знаю, как насчет нуги. Вы можете попробовать без опции -s, так как это всего лишь оптимизация. Я также проверил свои команды, и все они работают нормально, а ваша с strings, безусловно, самая медленная. Что касается grep -c, я объяснил в своем ответе, почему он быстрее, чем подключение к туалету: потому что он пропускает слова. Какой смысл в использовании неточного метода подсчета?

xhienne 09.07.2018 02:15

Извините, я забыл добавить часть < в команду tr. Теперь она сработала и нашла больше слов, чем комбинация strings | grep, но время выполнения увеличилось в 3 раза.

Phillip 09.07.2018 11:14

@Phillip Вы были правы в том, что есть некоторые проблемы с реализацией grep в Android. Я изменил свой ответ. Я также добавил несколько тестов, которые могут вам пригодиться.

xhienne 10.07.2018 01:31

Я принимаю этот ответ на самом деле, потому что многому у вас научился. Вы также можете добавить параллельный вариант strings для сравнения. Не знаю почему, но на моем телефоне Android 7 команда tr работает медленнее. На самом деле у меня более 30 слов, и из-за ограничения tr или strings я должен сделать словарь, прежде чем начать поиск. Но я действительно считаю, что это поможет улучшить производительность. Спасибо за помощь. Вы самые лучшие!

Phillip 10.07.2018 11:27

Спасибо, пожалуйста. Распараллеленный strings легко предсказуем: посмотрите, как распараллеленные версии grep и grep+tr делят общее время на (приблизительно) 2. Важно то, что у вас достаточно свободных ядер для удвоенного количества процессов. Если у вас четырехъядерный процессор, и вы используете свой браузер для просмотра видео, вам придется использовать ядра процессора для этой обработки, и вы не сократите общее время вдвое.

xhienne 10.07.2018 18:23

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