Оптимизированная версия strstr (поиск имеет постоянную длину)

в моей программе C было много вызовов функций strstr. Стандартная библиотека strstr уже быстра, но в моем случае строка поиска всегда имеет длину 5 символов. Я заменил его специальной версией, чтобы получить некоторую скорость:

int strstr5(const char *cs, const char *ct)
{
    while (cs[4]) {

        if (cs[0] == ct[0] && cs[1] == ct[1] && cs[2] == ct[2] && cs[3] == ct[3] && cs[4] == ct[4])
            return 1;

        cs++;
    }

    return 0;
}

функция возвращает целое число, потому что достаточно знать, происходит ли ct в cs. Моя функция проста и быстрее, чем стандартная strstr в этом частном случае, но мне интересно услышать, Есть ли у кого-нибудь улучшения производительности, которые могут быть прикладная. Даже небольшие улучшения приветствуются.

резюме:

  • CS имеет длину >=10, но в противном случае она может варьироваться. Длина известна ранее (не используется в моей функции). Длина cs обычно от 100 до 200.
  • ct имеет длину 5
  • содержание строк может быть что угодно

Edit: Спасибо за все ответы и комментарии. Я должен изучить и проверить идеи, чтобы увидеть, что работает лучше всего. Я начну с идеи мака о суффикс трие.

5 ответов


есть несколько быстро алгоритмы поиска строк. Попробуйте посмотреть на Бойер-Мур (как уже предлагалось Грег Hewgill), Рабин-Карп и KMP алгоритмов.

Если вам нужно искать много небольших шаблонов в одном и том же большом тексте, вы также можете попробовать реализовать суффиксное дерево или суффиксный массив. Но это ИМХО несколько сложнее понять и реализовать правильно.

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

EDIT:

Если вы ищете на один и тот же текст снова и снова (т. е. значение cs всегда / часто одно и то же по вызовам), вы получите большое ускорение, используя суффикс trie (в основном trie суффиксов). Поскольку ваш текст имеет размер 100 или 200 символов, вы можете использовать более простой метод O(n^2) для создания trie, а затем выполнить несколько быстрых поисков на нем. Каждый поиск потребует только 5 сравнений вместо обычных 5*200.

Edit 2:

Как упоминалось в комментарии caf, C's strstr алгоритм зависит от реализаций. glibc использует алгоритм линейного времени, который должен быть более или менее быстрым на практике, как любой из методов, о которых я упоминал. В то время как метод OP асимптотически медленнее (O(N*m) вместо O (n) ), он быстрее, вероятно, из-за того, что как n, так и m (длины шаблона и текста) очень малы, и ему не нужно выполнять какую-либо длительную предварительную обработку в версии glibc.


уменьшение количества сравнений увеличит скорость поиска. Сохраните текущий int строки и сравните его с фиксированным int для поискового термина. Если он соответствует, сравните последний символ.

uint32_t term = ct[0] << 24 | ct[1] << 16 | ct[2] << 8 | ct[3];
uint32_t walk = cs[0] << 24 | cs[1] << 16 | cs[2] << 8 | cs[3];
int i = 0;

do {
  if ( term == walk && ct[4] == cs[4] ) { return i; } // or return cs or 1
  walk = ( walk << 8 ) | cs[4];
  cs += 1;
  i += 1;
} while ( cs[4] ); // assumes original cs was longer than ct
// return failure

добавить проверки для короткого cs.

изменить:

добавлены исправления из комментариев. Спасибо.

Это может быть легко принято для использования 64-битных значений. Вы можете хранить cs[4] и ct[4] в локальных переменных, а не предполагать компилятор сделаю это для тебя. Вы можете добавить 4 в cs и ct перед циклом и использовать cs[0] и ct[0] в цикле.


интерфейс strstr накладывает некоторые ограничения, которые могут быть избиты. Он принимает строки с нулевым завершением, и любой конкурент, который сначала делает "strlen" своей цели, проиграет. Он не требует аргумента "состояние", поэтому затраты на настройку не могут быть амортизированы во многих вызовах с (скажем) той же целью или шаблоном. Ожидается, что он будет работать с широким спектром входных данных, включая очень короткие цели/шаблоны и патологические данные (рассмотрим поиск "ABABAC" в строке "ABABABABAB...С.)" файл libc тоже сейчас платформо-зависимый. В мире x86-64 SSE2 семь лет, а strlen и strchr libc, использующие SSE2, в 6-8 раз быстрее, чем наивные алгоритмы. На платформах Intel, поддерживающих SSE4.2, strstr использует инструкцию PCMPESTRI. Но вы можете победить и это.

Boyer-Moore (и Turbo B-M, и обратное сопоставление Oracle и др.) имеют время настройки, которое в значительной степени выбивает их из работы, даже не считая проблемы с нулевой строкой. Horspool-это ограниченный B-M, который работает хорошо на практике, но не делает крайние случаи хорошо. Лучшее, что я нашел в этом поле, - это BNDM ("обратное Недетерминированное направленное Ациклическое сопоставление слов-графов"), реализация которого меньше его имени: -)

вот несколько фрагментов кода, которые могут быть интересны. интеллектуальный SSE2 бьет наивный SSE4.2, и обрабатывает проблему нулевого завершения. реализация BNDM показывает один из способов учета издержек. Если вы знакомы с Horspool, вы заметите сходство, за исключением того, что BNDM использует битовые маски вместо смещений пропуска. Я собираюсь опубликовать, как решить проблему нулевого Терминатора (эффективно) для алгоритмов суффиксов, таких как Horspool и BNDM.

общим атрибутом всех хороших решений является разделение на разные алгоритмы для разных длин аргументов. Примером является Sanmayce функция"Railgun".


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

общей оптимизацией для поиска строк является использование Бойер-Мур где вы начинаете искать в cs с конец что будет ct. Увидеть ссылку на полное описание алгоритма.


вы не будете бить хорошую реализацию на современном компьютере x86.

новые процессоры Intel имеют инструкцию, которая занимает 16 байтов строки, которую вы изучаете, до 16 байтов строки поиска, и в одной инструкции возвращает, которая является первой байтовой позицией, где строка поиска может быть (или если ее нет). Например, если вы ищете "Hello" в строке "abcdefghijklmnHexyz", первая инструкция скажет вам, что строка " Hello" может начать со смещения 14 (поскольку чтение 16 байт, процессор имеет байты H, e, неизвестно, какие может место "Привет". Следующая инструкция, начинающаяся со смещения 14, сообщает, что строки там нет. И да, он знает о трейлинге нулевых байтов.

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