Який найшвидший алгоритм пошуку підрядків?


165

Гаразд, тому я не схожу на ідіот, я буду чіткіше викладати проблему / вимоги:

  • Голка (візерунок) та стог сіна (текст для пошуку) - це рядки з нульовим завершенням у стилі C. Інформація про довжину не надається; при необхідності його необхідно обчислити.
  • Функція повинна повернути вказівник на перший збіг, або NULLякщо не знайдено відповідності.
  • Випадки відмов не допускаються. Це означає, що будь-який алгоритм з непостійними (або великими постійними) вимогами для зберігання повинен мати резервний випадок для відмови розподілу (а продуктивність у догляді за резервним запасом сприяє найгіршій продуктивності).
  • Реалізація повинна бути в C, хоча хороший опис алгоритму (або посилання на такий) без коду теж чудово.

... а також, що я маю на увазі під "найшвидшим":

  • Детерміновано O(n)де n= довжина стога сіна. (Але можливо використовувати ідеї алгоритмів, які зазвичай є O(nm)(наприклад, прокатка хеша), якщо вони поєднуються з більш надійним алгоритмом для отримання детермінованих O(n)результатів).
  • Ніколи не спрацьовує (помірно; пара годинників і if (!needle[1])т.д. добре) гірше, ніж алгоритм наївної грубої сили, особливо на дуже коротких голках, які, мабуть, є найпоширенішим випадком. (Беззастережна велика попередня обробка накладних витрат погана, оскільки намагаються покращити лінійний коефіцієнт для патологічних голок за рахунок ймовірних голок.)
  • З огляду на довільну голку та стоги сіна, порівнянні або кращі показники (не гірші на 50% довший час пошуку) порівняно з будь-яким іншим широко реалізованим алгоритмом.
  • Окрім цих умов, я залишаю визначення «найшвидшого» відкритого типу. Хороша відповідь повинна пояснити, чому ви вважаєте підхід, який ви пропонуєте, "найшвидшим".

Моя поточна реалізація працює приблизно в 10% повільніше і в 8 разів швидше (залежно від вкладених даних), ніж реалізація двосторонньої програми glibc.

Оновлення: Мій поточний оптимальний алгоритм такий:

  • Для голок довжиною 1 використовуйте strchr.
  • Для голок довжиною 2-4 використовуйте машинні слова, щоб порівнювати 2-4 байти одразу так: Попередньо завантажте голку в 16- або 32-бітове ціле число бітизмінами та оберніть старий байт / нові байти з стога сіна при кожній ітерації . Кожен байт копиці сіна читається рівно один раз і проводить перевірку проти 0 (кінець рядка) та одне 16- або 32-бітове порівняння.
  • Для голок довжиною> 4 використовуйте Двосторонній алгоритм з поганою таблицею зсуву (наприклад, Boyer-Moore), який застосовується лише до останнього байта вікна. Щоб уникнути накладних витрат на ініціалізацію таблиці в 1 кб, що було б чистою втратою для багатьох голок середньої довжини, я зберігаю невеликий масив (32 байти), який позначає записи в таблиці зсуву. Біти, які не встановлені, відповідають значенням байтів, які ніколи не з’являються в голці, для яких можливий зсув повної довжини голки.

Основні питання, що залишилися в моїй думці:

  • Чи є спосіб краще використовувати таблицю поганих змін? Бойер-Мур найкраще використовує його, скануючи назад (справа наліво), але для двостороннього потрібне сканування зліва направо.
  • Єдині два алгоритми життєздатних кандидатів, які я знайшов для загального випадку (відсутність пам'яті та умов квадратичної продуктивності), - це двостороння та рядкова відповідність на упорядковані алфавіти . Але чи є легко виявлені випадки, коли різні алгоритми були б оптимальними? Звичайно, багато з O(m)(де mдовжина голки) в алгоритмах простору можуть бути використані для того m<100чи іншого. Можливо також використовувати алгоритми, які є найгіршими квадратичними, якщо є простий тест на голки, які, очевидно, потребують лише лінійного часу.

Бонусні бали за:

  • Чи можете ви покращити продуктивність, якщо припустити, що голка та стоги сіна є добре сформованими UTF-8? (Маючи символи різної довжини байтів, добре сформована гнучкість накладає деякі вимоги до вирівнювання рядків між голкою та стогом сіна та дозволяє автоматично змінювати 2-4 байтові зрушення, коли виникає невідповідний байт голови. Але ці обмеження купують вам багато / що більше, ніж що максимальні обчислення суфіксів, хороші зсуви суфіксу тощо вже дають різні алгоритми?)

Примітка. Я добре знаю більшість алгоритмів, які не існують, лише не наскільки вони добре працюють на практиці. Ось хороша довідка, щоб люди не продовжували давати мені посилання на алгоритми як коментарі / відповіді: http://www-igm.univ-mlv.fr/~lecroq/string/index.html


Існує досить багато алгоритмів пошуку рядків, перелічених у Algorithms on Strings . Ви можете описати, які алгоритми ви розглянули з цього списку.
Грег Хьюгілл

61
Ця посилання в кінці - золото!
Карлос

4
Не можу повірити, ти все ще не прийняв відповідь.
користувач541686

1
@Mehrdad: Я збирався сказати, що немає жодних відповідей, які насправді вирішують питання, як задають, але, здається, ваш. На той момент, коли ви відповіли, я пішов і продовжував подальше вдосконаленняstrstr як щось пізніше, тому я насправді не навчився правильно читати папір, який ви зв'язали, але це звучить дуже багатообіцяюче. Дякую і вибачте за те, що не повернулися до вас.
R .. GitHub СТОП ДОПОМОГАТИ ICE

Відповіді:


37

Створіть тестову бібліотеку ймовірних голок та сіна. Профілюйте тести за кількома алгоритмами пошуку, включаючи грубу силу. Виберіть той, який найкраще відповідає вашим даним.

Boyer-Moore використовує таблицю з поганими символами з хорошою таблицею суфіксів.

Boyer-Moore-Horspool використовує таблицю з поганими символами.

Кнут-Морріс-Пратт використовує часткову таблицю відповідностей.

Рабін-Карп використовує хеши.

Всі вони торгуються накладними витратами для різного порівняння, тому реальна ефективність у світі буде залежати від середньої довжини голки та сіна. Чим більше початкових накладних витрат, тим краще з довшими входами. З дуже короткими голками груба сила може перемогти.

Редагувати:

Інший алгоритм може бути найкращим для пошуку базових пар, англійських фраз або окремих слів. Якби був один найкращий алгоритм для всіх матеріалів, він би був оприлюднений.

Подумайте про таку маленьку таблицю. Кожен знак питання може мати інший найкращий алгоритм пошуку.

                 short needle     long needle
short haystack         ?               ?
long haystack          ?               ?

Це дійсно має бути графік з діапазоном коротших та довших входів на кожній осі. Якщо ви побудували кожен алгоритм на такому графіку, кожен би мав інший підпис. Деякі алгоритми страждають з великою кількістю повторень у шаблоні, що може вплинути на використання, як пошук генів. Деякі інші фактори, що впливають на загальну ефективність, - це пошук одного і того ж шаблону не один раз та пошук різних шаблонів одночасно.

Якщо мені знадобиться набір зразків, я думаю, я б скребку веб-сайту на зразок google або wikipedia, а потім зніміть HTML з усіх сторінок результатів. Для пошукового сайту введіть слово, а потім використовуйте одну із запропонованих пошукових фраз. Виберіть кілька різних мов, якщо це можливо. Використовуючи веб-сторінки, усі тексти будуть короткими до середніми, тому об’єднайте достатньо сторінок, щоб отримати довші тексти. Ви також можете знайти загальнодоступні книги, юридичні записи та інші великі тексти. Або просто генерувати випадковий вміст, вибираючи слова зі словника. Але суть профілювання полягає в тому, щоб перевірити тип контенту, який ви шукаєте, тому використовуйте зразки реального світу, якщо це можливо.

Я залишив короткі та довгі розпливчасті. Щодо голки, я думаю, що короткий, як менше 8 символів, середній, як менше 64 символів, і довгий, ніж менше 1k. Для стога сіна, я думаю, короткий, як менше 2 ^ 10, середній, як під 2 ^ 20, і довгий до 2 ^ 30 символів.


1
Чи є у вас хороші пропозиції щодо тестової бібліотеки? Попереднє запитання, яке я задавав на тему SO, було пов'язане з цим, і я ніколи не отримав реальних відповідей. (крім моєї власної ...) Він повинен бути обширним. Навіть якщо моя ідея програми для strstr шукає англійський текст, хтось інший може шукати гени в послідовностях базових пар ...
R .. GitHub ЗАСТАНІТЬ ДОПОМОГУ ICE

3
Це трохи складніше, ніж короткий / довгий. Для голки основними питаннями, що стосуються виконання більшості алгоритмів, є: Довжина? Чи є періодичність? Чи містить голка всі унікальні символи (без повторів)? Або все той же персонаж? Чи є в копиці сіна велика кількість символів, які ніколи не з’являються у голки? Чи є шанс зіткнутися з голками, наданими зловмисником, який хоче скористатись найгіршими показниками для каліки вашої системи? Etc ..
R .. GitHub СТОПУВАЙТЕ ДОПОМОГО

31

Опублікований у 2011 році, я вважаю, що це може бути "простим узгодженням постійного простору в реальному часі" алгоритмом Дани Бреслауера, Роберто Гроссі та Філіппо Мігносі.

Оновлення:

У 2014 році автори опублікували таке вдосконалення: На шляху до оптимального узгодження рядків .


1
Дякую Я читаю папір. Якщо воно виявиться кращим за те, що я маю, я обов'язково прийму вашу відповідь.
R .. GitHub СТОП ДОПОМОГА ВІД

1
@R ..: Звичайно! :) Якщо говорити про те, якщо вам вдасться реалізувати алгоритм, будь ласка, подумайте про його розміщення в StackOverflow, щоб усі могли отримати від нього користь! Я ніде не знайшов його реалізацій, і я не дуже добре втілюю алгоритми, які я знаходжу в дослідницьких роботах.
користувач541686

2
Це вже варіант "двостороннього" алгоритму, який я вже використовую, тому адаптувати свій код для його використання може бути насправді просто. Мені доведеться прочитати документ більш докладно, щоб бути впевненим, і мені потрібно оцінити, чи сумісність внесених змін з моїм використанням "таблиці поганих символів", що значно пришвидшує поширений випадок.
R .. GitHub СТОП ДОПОМОГА ВІД

11
А ви досі не прийняли відповідь @ Мехрдада! :-)
життєвий баланс

3
@DavidWallace: Що? У ній є заголовки статті та автори. Навіть якщо посилання перерветься, ви можете знайти документи. Що ви очікуєте від мене, написати псевдокод для алгоритму? Чому ви думаєте, що я розумію алгоритм?
користувач541686

23

http://www-igm.univ-mlv.fr/~lecroq/string/index.html ви вказуєте, є чудовим джерелом та підсумком деяких найбільш відомих та досліджених алгоритмів відповідності рядків.

Розв’язання більшості проблем пошуку передбачає компромісні вигоди щодо попередньої обробки накладних витрат, часу та простору. Жоден єдиний алгоритм не буде оптимальним чи практичним у всіх випадках.

Якщо ви ставите перед собою завдання розробити конкретний алгоритм для пошуку рядків, тоді ігноруйте решту, що я маю сказати. Якщо ви хочете розробити узагальнену процедуру пошуку рядків, то спробуйте наступне:

Витратьте деякий час, переглядаючи конкретні сильні та слабкі сторони алгоритмів, на які ви вже згадувались. Проведіть огляд з метою пошуку набору алгоритмів, які охоплюють діапазон та сферу пошукових рядків, які вас цікавлять. Потім, побудуйте селектор пошуку на передньому кінці на основі функції класифікатора, щоб орієнтуватися на найкращий алгоритм для заданих входів. Таким чином ви можете використовувати найефективніший алгоритм для виконання цієї роботи. Це особливо ефективно, коли алгоритм дуже хороший для певного пошуку, але погано погіршує. Наприклад, груба сила, мабуть, найкраща для голок довжиною 1, але швидко деградує, оскільки довжина голки збільшується, після чого алгоритм сустик-мураможе стати більш ефективним (для малих алфавітів), тоді для більш довгих голок і великих алфавітів алгоритми KMP або Boyer-Moore можуть бути кращими. Це лише приклади для ілюстрації можливої ​​стратегії.

Підхід з декількома алгоритмами не нова ідея. Я вважаю, що він використовувався кількома комерційними пакетами сортування / пошуку (наприклад, SYNCSORT, який зазвичай використовується в mainframes, реалізує декілька алгоритмів сортування та використовує евристику для вибору "найкращого" для заданих входів)

Кожен алгоритм пошуку має декілька варіантів, які можуть суттєво відрізнятися від його продуктивності, як, наприклад, це пояснює цей документ .

Визначте свою послугу, щоб класифікувати сфери, де потрібні додаткові стратегії пошуку, або більш ефективно налаштувати функцію вибору. Такий підхід не є швидким чи простим, але якщо зробити це добре, то це може дати дуже хороші результати.


1
Дякую за відгук, особливо за посилання на Сустік-Мур, якого я раніше не бачив. Підхід з декількома алгоритмами, безумовно, широко використовується. Glibc в основному робить strchr, Двосторонній без поганих таблиць переміщення символів, або Двосторонній з таблицею зсуву поганих символів, залежно від того, чи needle_len дорівнює 1, <32 або> 32. Мій поточний підхід той самий, за винятком того, що я завжди використовую таблицю зрушень; Я замінив 1кб мемсет, необхідний для цього, 32-байтним набором на бітовому наборі, який використовується для позначення, які елементи таблиці були ініціалізовані, і я отримую вигоду (але не накладні витрати) навіть на крихітні голки.
R .. GitHub ЗАСТОСУЄТЬСЯ ДОПОМОГА

1
Задумавшись над цим, мені дуже цікаво, що призначений додаток для Сустік-Мура. З невеликими алфавітами ви ніколи не зможете зробити суттєвих зрушень (усі символи алфавіту майже напевно з’являються біля кінця голки), а кінцеві автоматичні підходи є дуже ефективними (мала таблиця переходу стану). Тож я не можу передбачити жодного сценарію, де Сустік-Мур міг бути оптимальним ...
R .. GitHub

чудова відповідь - якби я міг зірнути цю конкретну відповідь, я би.
Jason S

1
@R .. Теорія алгоритму сустик-мура полягає в тому, що він повинен давати вам більше середніх величин зсуву, коли голка порівняно велика і алфавіт відносно невеликий (наприклад, пошук послідовностей ДНК). Більший у цьому випадку просто означає, що більший, ніж основний алгоритм Бойєра-Мура, який би дав ті самі входи. Наскільки ефективніше це відносно обмеженого підходу до автоматичного режиму чи до якогось іншого варіанту Боєра-Мура (яких існує багато), важко сказати. Тому я наголосив витратити деякий час на дослідження конкретних сильних / слабких сторін алгоритмів вашого кандидата.
NealB

1
Гм, я думаю, я застряг у думці про зрушення саме в тому, що поганий характер змістився від Бойєра-Мура. З покращенням рівня BM хороший зсув суфіксу, але Сустік-Мур, можливо, може перевершити підходи DFA до пошуку ДНК. Акуратні речі.
R .. GitHub ЗАСТАНОВИТИ ДІЯ

21

Я був здивований, побачивши наш технічний звіт, цитований у цій дискусії; Я один з авторів алгоритму, який був названий Сустік-Муром вище. (Ми не використовували цей термін у нашій роботі.)

Я хотів тут підкреслити, що для мене найцікавішою особливістю алгоритму є те, що доволі просто довести, що кожна буква вивчається максимум одразу. Для більш ранніх версій Бойєра-Мура вони довели, що кожний лист перевіряється не більше 3, а пізніше 2 рази, і ці докази були більш залученими (див. Цитати в статті). Тому я також бачу дидактичне значення у представленні / вивченні цього варіанту.

У статті ми також описуємо подальші зміни, спрямовані на ефективність, зменшуючи теоретичні гарантії. Це коротка праця, і матеріал, на мою думку, повинен бути зрозумілим пересічному випускнику середньої школи.

Нашою основною метою було донести цю версію до відома інших, хто може її вдосконалити. Пошук рядків має дуже багато варіантів, і ми самі не можемо придумати все, де ця ідея може принести користь. (Виправлений текст і зміна шаблону, фіксований шаблон іншого тексту, попередня обробка можлива / неможлива, паралельне виконання, пошук відповідних підмножин у великих текстах, допускати помилки, поруч зі збігом тощо тощо)


1
Чи знаєте ви про доступну реалізацію C або C ++? Я думаю використовувати це для пошуку ДНК мотивів (точні збіги мотивів). Якщо ні, можливо, я спробую сам розробити реалізацію та подати алгоритм підвищення
JDiMatteo,

4
Не маючи відомої доступної реалізації, алгоритм Sustik-Moore / 2BLOCK, мабуть, не буде використаний на практиці і продовжує залишатись у результатах у зведених статтях, таких як "Точна відповідність рядків: комплексна експериментальна оцінка"
JDiMatteo

18

Алгоритм пошуку найшвидшого підрядка залежить від контексту:

  1. розмір алфавіту (наприклад, ДНК проти англійської мови)
  2. довжина голки

Документ 2010 року "Точна відповідність рядків: проблема всебічної експериментальної оцінки" наведено таблиці з режимами виконання 51 алгоритму (з різними розмірами алфавіту та довжиною голки), так що ви можете вибрати найкращий алгоритм для вашого контексту.

Усі ці алгоритми мають реалізацію C, а також тестовий набір:

http://www.dmi.unict.it/~faro/smart/algorithms.php


4

Справді гарне запитання. Просто додайте трохи крихітних шматочків ...

  1. Хтось говорив про відповідність послідовності ДНК. Але для послідовності ДНК, що ми зазвичай робимо, це побудувати структуру даних (наприклад, суфіксний масив, суфіксне дерево або FM-індекс) для стога сіна та зіставити проти нього багато голок. Це вже інше питання.

  2. Було б дійсно чудово, якби хтось хотів порівняти різні алгоритми. Існують дуже хороші орієнтири щодо стиснення та побудови масивів суфіксів, але я не бачив орієнтиру щодо відповідності рядків. Потенційні кандидати на стоги сіна можуть бути на основі тесту SACA .

  3. Кілька днів тому я тестував реалізацію Boyer-Moore зі сторінки, яку ви рекомендували (EDIT: мені потрібен виклик функції, як memmem (), але це не стандартна функція, тому я вирішив її реалізувати). Моя програма бенчмаркінгу використовує випадкові стоги сіна. Здається, що реалізація Boyer-Moore на цій сторінці в рази швидша, ніж пам'ять glibc () та Mac strnstr (). У випадку, якщо вас цікавить, реалізація тут, і код бенчмаркінгу тут . Це, безумовно, не є реалістичним орієнтиром, але це початок.


Якщо у вас є кілька хороших голок для тестування разом із кандидатами з сіна від еталону SACA, опублікуйте їх як відповідь на моє інше запитання, і, не отримавши кращої відповіді, я відзначу це прийнятим.
R .. GitHub ЗАСТОСУЄТЬСЯ ДОПОМОГАТИ

3
Що стосується вашої пам’ятки та Boyer-Moore, то дуже ймовірно, що Boyer-Moore (а точніше одне з удосконалень Boyer-Moore) найкраще буде працювати на випадкових даних. Випадкові дані мають надзвичайно низьку ймовірність періодичності та довгі часткові збіги, які призводять до квадратичного найгіршого випадку. Я шукаю спосіб поєднати Бойєра-Мура та Двостороннього або ефективно визначити, коли Boyer-Moore "безпечний у використанні", але поки що я не мав жодного успіху. До речі, я б не використав пам'ятку glibc як порівняння. Моя реалізація того, що в основному того ж алгоритму, як і glibc, в кілька разів швидше.
R .. GitHub СТОП ДОПОМОГАЙТЕ

Як я вже сказав, це не моя реалізація. Кредит Крістіан Чаррас і Тьєррі Лекрок. Я можу собі уявити, чому випадковий вклад поганий для тестування, і я впевнений, що glibc вибирає алгоритми з причин. Я також здогадуюсь, що memmem () не реалізується ефективно. Я спробую. Дякую.
користувач172818

4

Я знаю, що це давнє запитання, але більшість поганих таблиць змін є однозначними. Якщо це має сенс для вашого набору даних (наприклад, особливо якщо це написані слова), і якщо у вас є вільний простір, ви можете отримати драматичне прискорення, скориставшись поганою таблицею зсуву з n-грамів, а не з окремих символів.


3

Використовуйте stdlib strstr:

char *foundit = strstr(haystack, needle);

Це було дуже швидко, набравши мені близько 5 секунд.


26
І якби ти прочитав моє запитання, то побачив би, що мені було досить просто перевершити його. Мені подобається твій сарказм, хоча я пропускаю -1.
R .. GitHub СТОП ДОПОМОГАТИ ЛІС

3

Ось реалізація пошуку Python , яка використовується з усього ядра. У коментарях зазначається, що він використовує стиснуту дельту 1 "boyer-moore" 1 .

Я робив досить обширний експеримент із пошуком рядків сам, але це було для кількох рядків пошуку. Реалізації Horspool та Bitap для складання часто можуть утримуватися проти таких алгоритмів, як Aho-Corasick .


3

Більш швидкий strchrалгоритм "Пошук одного відповідного символу" (ala ).

Важливі примітки:

  • Ці функції використовують " gccкомпілятор " число / кількість (провідних | трейлінг) нулів "," внутрішніх " __builtin_ctz. Ці функції, швидше за все, будуть швидкими лише на машинах, які мають інструкції, які виконують цю операцію (наприклад, x86, ppc, arm).

  • Ці функції передбачають, що цільова архітектура може виконувати 32 і 64 бітові нерівні навантаження. Якщо ваша цільова архітектура цього не підтримує, вам потрібно буде додати деяку логіку запуску, щоб правильно вирівняти показання.

  • Ці функції є нейтральними для процесора. Якщо цільовий процесор має векторні інструкції, ви можете зробити це (набагато) краще. Наприклад, strlenнаведена нижче функція використовує SSE3 і може бути тривіально модифікована на XOR відсканованих байтів, щоб шукати байт, відмінний від 0. Орієнтовні показники виконані на ноутбуці Core 2 з частотою 2,66 ГГц під керуванням Mac OS X 10.6 (x86_64):

    • 843,433 Мб / с для strchr
    • 2656,742 Мб / с для findFirstByte64
    • 13094,479 Мб / с для strlen

... 32-розрядна версія:

#ifdef __BIG_ENDIAN__
#define findFirstZeroByte32(x) ({ uint32_t _x = (x); _x = ~(((_x & 0x7F7F7F7Fu) + 0x7F7F7F7Fu) | _x | 0x7F7F7F7Fu); (_x == 0u)   ? 0 : (__builtin_clz(_x) >> 3) + 1; })
#else
#define findFirstZeroByte32(x) ({ uint32_t _x = (x); _x = ~(((_x & 0x7F7F7F7Fu) + 0x7F7F7F7Fu) | _x | 0x7F7F7F7Fu);                    (__builtin_ctz(_x) + 1) >> 3; })
#endif

unsigned char *findFirstByte32(unsigned char *ptr, unsigned char byte) {
  uint32_t *ptr32 = (uint32_t *)ptr, firstByte32 = 0u, byteMask32 = (byte) | (byte << 8);
  byteMask32 |= byteMask32 << 16;
  while((firstByte32 = findFirstZeroByte32((*ptr32) ^ byteMask32)) == 0) { ptr32++; }
  return(ptr + ((((unsigned char *)ptr32) - ptr) + firstByte32 - 1));
}

... і 64-розрядна версія:

#ifdef __BIG_ENDIAN__
#define findFirstZeroByte64(x) ({ uint64_t _x = (x); _x = ~(((_x & 0x7F7F7F7F7f7f7f7full) + 0x7F7F7F7F7f7f7f7full) | _x | 0x7F7F7F7F7f7f7f7full); (_x == 0ull) ? 0 : (__builtin_clzll(_x) >> 3) + 1; })
#else
#define findFirstZeroByte64(x) ({ uint64_t _x = (x); _x = ~(((_x & 0x7F7F7F7F7f7f7f7full) + 0x7F7F7F7F7f7f7f7full) | _x | 0x7F7F7F7F7f7f7f7full);                    (__builtin_ctzll(_x) + 1) >> 3; })
#endif

unsigned char *findFirstByte64(unsigned char *ptr, unsigned char byte) {
  uint64_t *ptr64 = (uint64_t *)ptr, firstByte64 = 0u, byteMask64 = (byte) | (byte << 8);
  byteMask64 |= byteMask64 << 16;
  byteMask64 |= byteMask64 << 32;
  while((firstByte64 = findFirstZeroByte64((*ptr64) ^ byteMask64)) == 0) { ptr64++; }
  return(ptr + ((((unsigned char *)ptr64) - ptr) + firstByte64 - 1));
}

Редагувати 2011/06/04 ОП в коментарях зазначає, що це рішення має "непереборну помилку":

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

Це технічно вірно, але стосується практично будь-якого алгоритму, який працює на фрагменти, розміри яких перевищують один байт, включаючи метод, запропонований ОП у коментарях:

Типова strchrреалізація не наївна, але зовсім трохи ефективніша, ніж те, що ви дали. Дивіться кінець цього для найбільш широко використовуваного алгоритму: http://graphics.stanford.edu/~seander/bithacks.html#ZeroInWord

Це також насправді не має нічого спільного з вирівнюванням . Щоправда, це потенційно може спричинити поведінку, обговорювану у більшості поширених архітектур, що використовуються, але це має більше спільного з деталями впровадження мікроархітектури - якщо нерівне зчитування перетинає межу 4К (знову ж таки типово), то це читання спричинить програму припинення помилки, якщо наступна межа 4K сторінки не розміщена.

Але це не "помилка" в алгоритмі, наведеному у відповіді - така поведінка полягає в тому, що функції люблять strchrі strlenне приймають lengthаргумент, що обмежує розмір пошуку. Пошук char bytes[1] = {0x55};, який для цілей нашої дискусії так само відбувається, розміщується в самому кінці межі 4K VM сторінки, і наступна сторінка є немапарованою, при цьому strchr(bytes, 0xAA)(де strchrреалізується байт за часом) буде збій саме той Точно так же. Діто для strchrспорідненого кузена strlen.

Без lengthаргументу не можна сказати, коли слід вимкнутись із високошвидкісного алгоритму і повернутися до алгоритму байтових байтів. Набагато більш імовірним "помилкою" було б прочитати "минулий розмір виділення", що технічно призводить undefined behaviorдо різних стандартів мови С і буде позначено як помилка чимось подібним valgrind.

Підсумовуючи це, все, що працює на більших, ніж байтових фрагментах, йде швидше, оскільки це код відповідей робить і код, на який вказує ОП, але повинен мати байт-точну семантику зчитування, ймовірно, буде "баггі", якщо lengthаргументу немає керуйте кутовими справами "останнього читання".

Код у цій відповіді є ядром для того, що зможете швидко знайти перший байт у натуральному відрізку розміру слова CPU, якщо цільовий процесор має швидку ctzінструкцію. Додавати такі речі, як переконання, що він працює лише за правильно вирівняними природними межами або певною формоюlength зв'язаного, що дозволить вам вийти з ядра високої швидкості та перейти до повільнішої перевірки байтів за байтом.

У коментарях ОП також зазначає:

Що стосується вашої оптимізації ctz, вона має значення лише для роботи хвоста O (1). Це може покращити продуктивність за допомогою крихітних струн (наприклад, strchr("abc", 'a');але, звичайно, не для струн будь-якого великого розміру).

Від того, чи є це твердження правдивим, чи багато залежить від мікроархітектури, про яку йдеться. Використовуючи канонічну 4-х ступеневу модель конвеєра RISC, це майже напевно вірно. Але вкрай важко сказати, чи правда це для сучасного супер-скалярного процесора, який не виходить із ладу, де основна швидкість може абсолютно карликувати швидкість потокової пам'яті. У цьому випадку це не тільки правдоподібно, але й досить поширено, оскільки існує великий розрив у "кількості вказівок, які можна вилучити" щодо "кількості байтів, які можна передавати", щоб у вас був " кількість інструкцій, які можна вилучити для кожного байту, який може бути потоковий ". Якщо це досить велике, інструкцію ctz+ shift можна виконати "безкоштовно".


"Для голок довжиною 1 використовуйте strchr." - Ви попросили найшвидший алгоритм пошуку підрядків. Пошук підрядка довжиною 1 - це лише особливий випадок, який також можна оптимізувати. Якщо ви заміните свій поточний спеціальний код справи для підрядків довжиною 1 ( strchr) чимось на зразок вище, все (можливо, залежно від способу strchrреалізації) піде швидше. Наведений вище алгоритм майже в 3 рази швидший, ніж типова наївна strchrреалізація.
johne

2
ОП сказав, що рядок було належним чином скасовано, тому ваше обговорення щодо char bytes[1] = {0x55};цього не має значення. Дуже актуальним є ваш коментар щодо того, що це справедливо для будь-якого алгоритму зчитування слів, який заздалегідь не знає довжини.
Сет Робертсон

1
Проблема не стосується версії, яку я цитував, оскільки ви використовуєте її лише за вирівняними покажчиками - принаймні, так роблять правильні реалізації.
R .. GitHub ЗАСТАНОВИТИ ДІЯ

2
@R, це не має нічого спільного з "вирівняними покажчиками". Гіпотетично, якщо у вас була архітектура, яка підтримувала захист VM з деталізацією рівня байтів, і кожне mallocрозподілення було "достатньо підкладене" з обох боків, і система VM забезпечила байтовий захист для цього розподілу .... вирівнюється чи ні вказівник ( припустимо, що тривіальне 32-бітове intприродне вирівнювання) є суперечливим - все одно можливо, що вирівняне зчитування прочитане минулого розміру виділення. БУДЬ-ЯК прочитане минуле розміру виділення undefined behavior.
johne

5
@johne: +1 для коментарів. Концептуально ви маєте рацію, але реальність полягає в тому, що захист від байдарної зернистості настільки дорогий як для зберігання, так і для застосування, щоб їх не було і ніколи не буде. Якщо ви знаєте, що основним сховищем є відображення деталізації деталей сторінки, отримане з еквіваленту mmap, то вирівнювання достатньо.
R .. GitHub СТОП ДОПОМОГАЄТЬСЯ

3

Просто шукайте "найшвидший strstr", і якщо ви бачите щось цікаве, просто запитайте мене.

На мій погляд, ви накладаєте занадто багато обмежень на себе (так, ми всі хочемо сублінійних лінійних у max search), однак для цього потрібен справжній програміст, до цього часу я думаю, що хеш-підхід є просто вишуканим рішенням кінцівки ( добре підсилений BNDM для коротших 2..16 візерунків).

Просто короткий приклад:

Ведення пошуку шаблону (32 байт) в рядку (206908949bytes) як-однорядковий ... Skip-Performance (більше-The-краще): 3041%, 6801754 скаче / ітерація Railgun_Quadruplet_7Hasherezade_hits / Railgun_Quadruplet_7Hasherezade_clocks: 0/58 Railgun_Quadruplet_7Hasherezade продуктивність: 3483KB / годинник

Ведення пошуку шаблон (32 байта) в рядок (206908949bytes) як-однієї лінії ... Скіп-Performance (більше-The-краще): 1554%, 13307181 скіпів / ітераціях Boyer_Moore_Flensburg_hits / Boyer_Moore_Flensburg_clocks: 0/83 Boyer_Moore_Flensburg продуктивність: 2434KB / годинник

Ведення пошуку шаблону (32 байт) в рядку (206908949bytes) , як-он-лайн ... Скіп-Performance (більше-The-краще): 129%, 160 239 051 SKIPS / ітерації Дві Way_hits / Дві Way_clocks: 0/816 Two -WAY продуктивність: 247KB / годинник

Санмайсе,
З повагою


3

Двосторонній алгоритм, який ви згадуєте у своєму запитанні (який, до речі, неймовірний!), Нещодавно вдосконалився, щоб одночасно ефективно працювати над багатобайтовими словами: Оптимальне збігання рядків .

Я не прочитав цілий документ, але, схоже, вони покладаються на пару нових спеціальних інструкцій процесора (включені до напр., SSE 4.2), які є O (1) для їхньої складності, але якщо вони відсутні, вони можуть імітуйте їх у час O (журнал журналу w) для w-бітових слів, що звучить не дуже погано.


3

Можна реалізувати, скажімо, 4 різні алгоритми. Кожні М хвилин (визначається емпірично) виконуйте всі 4 за поточними реальними даними. Накопичуйте статистику за N пробігів (також TBD). Тоді використовуйте лише переможця протягом наступних M хвилин.

Журнал статистики на Wins, щоб ви могли замінити алгоритми, які ніколи не виграють, новими. Сконцентруйте зусилля з оптимізації на найбільш виграшному розпорядку. Зверніть особливу увагу на статистику після будь-яких змін обладнання, бази даних або джерела даних. Якщо це можливо, включіть цю інформацію до журналу статистики, тому вам не доведеться розбирати її з дати / часової позначки журналу.


3

Нещодавно я виявив чудовий інструмент для вимірювання продуктивності різних доступних альгів: http://www.dmi.unict.it/~faro/smart/index.php

Ви можете вважати його корисним. Крім того, якщо мені доведеться швидко зателефонувати за алгоритмом пошуку підрядків, я б поїхав із Knuth-Morris-Pratt.


Дякуємо за посилання Тести виглядають цікавими для типових випадків, але не для того, щоб сприймати найгірші випадки.
R .. GitHub СТОП ДОПОМОГАТИ

2

Можливо, ви також хочете мати різноманітні орієнтири з декількома типами рядків, оскільки це може мати великий вплив на продуктивність. Альго буде виконувати диференційованість на основі пошуку природної мови (і навіть тут все ще можуть бути тонкозернисті відмінності через різні морфології), ДНК-рядки або випадкові рядки тощо.

Розмір алфавіту буде грати роль у багатьох альго, як і розмір голки. Наприклад, Horspool добре справляється з англійським текстом, але погано працює на ДНК через різний розмір алфавіту, що ускладнює життя правилам поганих символів. Введення доброго суфікса значно полегшує це.


0

Я не знаю, чи це абсолютно найкраще, але я мав хороший досвід роботи з Бойєром-Муром .


Чи знаєте ви спосіб поєднання таблиці поганих змін Бойєра-Мура з двостороннім? Glibc робить варіант цього для довгих голок (> 32 байти), але перевіряє лише останній байт. Проблема полягає в тому, що Двосторонній пошук повинен шукати праву частину голки зліва направо, тоді як поганий зсув Боєра-Мура є найбільш ефективним при пошуку справа наліво. Я спробував використовувати його зліва направо в Двосторонній (заздалегідь за столом зсуву або звичайним невідповідністю двосторонньої правої половини, залежно від того, що довше), але в більшості випадків я отримав 5-10% уповільнення порівняно зі звичайним Двостороннім. не вдалося знайти жодного випадку, коли це покращило продуктивність.
R .. GitHub ЗАСТАНОВИТИ ДІЯ

0

Це не відповідає безпосередньо на питання, але якщо текст дуже великий, як щодо поділу його на розділи, що перекриваються (перекриття довжиною візерунка), а потім одночасно шукати розділи за допомогою ниток. Що стосується найшвидшого алгоритму, то, думаю, Бойєр-Мур-Горспул є одним із найшвидших, якщо не найшвидшим серед варіантів Бойєра-Мура. Я розмістив пару варіантів Бойєра-Мура (не знаю їх імені) у цій темі Алгоритм швидше, ніж BMH (пошук Боєра – Мура – ​​Хорспула) .


0

Найшвидший на даний момент EPSM - С. Фаро та О. М. Кулекчі. Див. Http://www.dmi.unict.it/~faro/smart/algorithms.php?algorithm=EPSM&code=epsm

"Точне збігання рядків", оптимізоване для SIMD SSE4.2 (x86_64 та aarch64). Він стабільний і найкращий для всіх розмірів.

Веб-сайт, на який я посилався, порівнює 199 алгоритмів швидкого пошуку рядків, причому звичайні (BM, KMP, BMH) досить повільні. EPSM перевершує всі інші згадувані тут на цих платформах. Це також останнє.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.