最速の部分文字列検索アルゴリズムは何ですか?


165

わかりました、それで私は馬鹿のように聞こえません私は問題/要件をより明確に述べるつもりです:

  • Needle(パターン)とhaystack(検索するテキスト)はどちらもCスタイルのヌル終了文字列です。長さ情報は提供されません。必要に応じて、計算する必要があります。
  • 関数は、最初の一致へのポインタを返すNULLか、一致が見つからない場合に返す必要があります。
  • 失敗した場合は許可されません。これは、非定数(または大きな定数)のストレージ要件を持つアルゴリズムでは、割り当ての失敗に対するフォールバックケースが必要になることを意味します(フォールバックケアのパフォーマンスは、最悪の場合のパフォーマンスに影響します)。
  • 実装はCで行う必要がありますが、コードなしでアルゴリズム(またはそのようなものへのリンク)を適切に記述してもかまいません。

...そして「最速」とはどういう意味か:

  • 決定論的なO(n)場所n=干し草の長さ。(しかしO(nm)、より堅牢なアルゴリズムと組み合わせて確定的なO(n)結果を得る場合、通常はローリングハッシュなどのアルゴリズムからのアイデアを使用することが可能です)。
  • if (!needle[1])特に最も一般的なケースである可能性が高い非常に短い針では、素朴なブルートフォースアルゴリズムよりもパフォーマンスが低下する(ある程度測定できる;数クロックなどで問題ない)。(無条件に重い前処理のオーバーヘッドは悪く、針の可能性を犠牲にして病理学的針の線形係数を改善しようとしています。)
  • 任意の針と干し草を考えると、他の広く実装されているアルゴリズムと比較して、同等またはそれ以上のパフォーマンス(検索時間が50%以上長くなります)。
  • これらの条件は別として、私は「最速」の定義をオープンエンドのままにしておきます。良い答えは、「最速」を提案しているアプローチを検討する理由を説明する必要があります。

私の現在の実装は、glibcのTwo-Wayの実装よりも(入力に応じて)約10%遅く、8倍速く実行されます。

更新:現在の最適なアルゴリズムは次のとおりです。

  • 長さが1の針には、を使用しますstrchr
  • 長さが2〜4の針の場合、マシンワードを使用して、次のように2〜4バイトを一度に比較します。16ビットまたは32ビットの整数に針をビットシフトでプリロードし、各反復で干し草の山から古いバイトアウト/新しいバイトを循環させます。 。干し草の各バイトは1回だけ読み取られ、0(文字列の終わり)に対するチェックと1つの16ビットまたは32ビットの比較が行われます。
  • 長さが4より大きい針の場合は、ウィンドウの最後のバイトにのみ適用される不良なシフトテーブル(Boyer-Mooreなど)を使用した双方向アルゴリズムを使用します。1 kbテーブルを初期化するオーバーヘッド(多くの中程度の長さの針の正味の損失になる)を回避するために、シフトテーブルのどのエントリが初期化されるかを示すビット配列(32バイト)を保持します。未設定のビットは、針に表示されることのないバイト値に対応します。針の全長シフトが可能です。

私の頭に残っている大きな質問は次のとおりです。

  • 悪いシフトテーブルをより有効に活用する方法はありますか?Boyer-Mooreは、逆方向(右から左)にスキャンすることでそれを最大限に活用しますが、双方向には左から右へのスキャンが必要です。
  • 一般的なケース(メモリ不足や2次のパフォーマンス条件はない)で見つかった実行可能な候補アルゴリズムは、順序付きアルファベットでの双方向および文字列マッチングの2つだけです。しかし、異なるアルゴリズムが最適である、簡単に検出できるケースはありますか?確かに、O(m)m針の長さである)空間アルゴリズムの多くは、m<100そのために使用できます。線形時間のみを必要とする可能性がある針の簡単なテストがある場合は、最悪の2次アルゴリズムであるアルゴリズムを使用することもできます。

ボーナスポイント:

  • 針と干し草の両方が整形式のUTF-8であると想定して、パフォーマンスを向上させることができますか?(バイト長が異なる文字では、整形式であることにより、needleとhaystackの間に文字列の整列要件が課せられ、不一致のヘッドバイトが検出されたときに自動的に2-4バイトのシフトが可能になります。最大の接尾辞の計算、適切な接尾辞のシフトなどにより、すでにさまざまなアルゴリズムが提供されていますか?)

注:私はそこにあるほとんどのアルゴリズムをよく知っていますが、実際のパフォーマンスはそうではありません。ここに良い参照がありますので、人々は私にコメント/回答としてアルゴリズムに関する参照を与え続けません:http : //www-igm.univ-mlv.fr/~lecroq/string/index.html


Algorithms on Stringsにリストされている文字列検索アルゴリズムはかなりあります。このリストから検討したアルゴリズムを説明することもできます。
グレッグヒューギル

61
最後のそのリンクは金です!
Carlos、

4
まだ答えを受け入れていないなんて信じられません。
user541686 2015年

1
@Mehrdad:私が尋ねられた質問に実際に対処する答えはないと言っていましたが、あなたの答えはそうです。あなたが答えた時点で私は先に進み、さらなる改善strstrを後で何かに残したので、私は実際にあなたがリンクした論文を適切に読むために移動していませんが、それは非常に有望に聞こえます。返信がないことをお詫び申し上げます。
R .. GitHub ICE HELPING ICE

回答:


37

ありそうな針と干し草のテストライブラリを構築します。ブルートフォースを含むいくつかの検索アルゴリズムのテストのプロファイルを作成します。あなたのデータで最もよく機能するものを選んでください。

Boyer-Mooreは、悪いサフィックステーブルと悪いキャラクタテーブルを使用しています。

Boyer-Moore-Horspoolは不正な文字テーブルを使用しています。

Knuth-Morris-Prattは部分一致テーブルを使用します。

Rabin-Karpは、実行中のハッシュを使用します。

これらはすべて、比較の程度を下げるためにオーバーヘッドをトレードオフするため、実際のパフォーマンスは針と干し草の両方の平均の長さに依存します。初期オーバーヘッドが大きいほど、入力が長くなるほど良くなります。針が非常に短いと、力ずくで勝つことができます。

編集:

塩基対、英語のフレーズ、または単一の単語を見つけるには、別のアルゴリズムが最適な場合があります。すべての入力に最適なアルゴリズムが1つあれば、それは公表されていたでしょう。

次の小さなテーブルについて考えてください。疑問符ごとに、最適な検索アルゴリズムが異なる場合があります。

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

これは、実際にはグラフであり、各軸の入力範囲は短いものから長いものまであります。このようなグラフに各アルゴリズムをプロットすると、それぞれに異なるシグネチャがあります。一部のアルゴリズムは、パターンの反復が多いため、遺伝子の検索などの用途に影響を与える可能性があります。全体的なパフォーマンスに影響を与える他のいくつかの要因は、同じパターンを複数回検索し、同時に異なるパターンを検索することです。

サンプルセットが必要な場合は、googleやwikipediaなどのサイトをこすり落としてから、すべての結果ページからhtmlを取り除きます。検索サイトの場合、単語を入力してから、提案された検索フレーズの1つを使用します。必要に応じて、いくつかの異なる言語を選択します。Webページを使用すると、すべてのテキストは短いものから中程度のものになるので、十分なページを結合して長いテキストを取得します。パブリックドメインの書籍、法的記録、およびその他の大きなテキスト本文も検索できます。または、辞書から単語を選択してランダムなコンテンツを生成します。ただし、プロファイリングのポイントは、検索するコンテンツのタイプに対してテストすることなので、可能であれば実際のサンプルを使用してください。

短くて漠然としたものを残しました。針については、8文字未満、中程度の64文字、1 k未満の長いと思います。干し草の山の場合、2 ^ 10未満、2 ^ 20未満、2 ^ 30文字までと考えます。


1
テストライブラリについて良い提案はありますか?私がSOに尋ねた前の質問はそれに関連していて、実際の答えはありませんでした。(自分のものを除いて...)それは広範囲であるべきです。strstrのアプリケーションについての私の考えが英語のテキストを検索することであるとしても、他の誰かが塩基対配列の遺伝子を検索しているかもしれません...
R .. GitHub STOP HELPING ICE

3
短い/長いよりも少し複雑です。針の場合、ほとんどのアルゴリズムのパフォーマンスに関連する大きな質問は次のとおりです。周期性はありますか?針にはすべての固有の文字が含まれていますか(繰り返しなし)?またはすべて同じキャラクターですか?干し草の山に針に表示されない文字が多数ありますか?ワーストケースのパフォーマンスを悪用してシステムを不自由にしたい攻撃者によって提供された針に対処しなければならない可能性はありますか?等
R .. GitHub ICE HELPING ICEの停止

31

2011年に公開された私は、Dany Breslauer、Roberto Grossi、およびFilippo Mignosiによる「単純なリアルタイムの一定空間文字列マッチング」アルゴリズムである可能性が非常に高いと考えています。

更新:

2014年、著者はこの改善点を公開しました:最適なパックされた文字列マッチングに向けて


1
わあ、ありがとう。私は紙を読んでいます。それが私が持っているものよりも優れていることが判明した場合、私は間違いなくあなたの答えを受け入れます。
R .. GitHub ICEのヘルプ停止2013

1
@R ..:もちろん!:)とはいえ、アルゴリズムを実装できた場合は、StackOverflowに投稿することを検討してください。私はその実装をどこにも見つけていませんし、研究論文で見つけたアルゴリズムを実装するのは得意ではありません。
user541686 2013

2
これは、すでに使用している「双方向」アルゴリズムの変形なので、これを使用するようにコードを調整するのは実際には簡単かもしれません。ただし、確認のために詳細を読む必要があります。加えた変更が、一般的なケースを大幅に高速化する「不良文字テーブル」の使用と互換性があるかどうかを評価する必要があります。
R .. GitHub ICE HELPING ICEを停止2013

11
そして、あなたはまだ@Mehrdadの答えを受け入れていません!:-)
ライフバランス2015年

3
@DavidWallace:なに?論文のタイトルと著者がいます。リンクが切れても、論文を見つけることができます。アルゴリズムの疑似コードを書いて私に何を期待していますか?どうして私がアルゴリズムを理解したと思いますか?
user541686

23

http://www-igm.univ-mlv.fr/~lecroq/string/index.html あなたが指すリンクが最もよく知られており、調査文字列照合アルゴリズムのいくつかの優れた供給源と要約したものです。

ほとんどの検索問題の解決策には、前処理のオーバーヘッド、時間とスペースの要件に関するトレードオフが含まれます。単一のアルゴリズムがすべての場合に最適または実用的であるとは限りません。

目的が文字列検索の特定のアルゴリズムを設計することである場合は、私が言わなければならない残りの部分を無視してください。一般化された文字列検索サービスルーチンを開発する場合は、以下を試してください。

すでに参照したアルゴリズムの具体的な長所と短所を確認してください。関心のある文字列検索の範囲と範囲をカバーする一連のアルゴリズムを見つけることを目的としてレビューを実施します。次に、分類関数に基づいてフロントエンド検索セレクターを構築し、特定の入力に最適なアルゴリズムをターゲットにします。このようにして、最も効率的なアルゴリズムを使用して作業を行うことができます。これは、アルゴリズムが特定の検索に非常に適しているが、十分に分解されない場合に特に効果的です。たとえば、ブルートフォースはおそらく長さが1の針に最適ですが、針の長さが長くなるとすぐに低下し、その後、sustik-mooreのアルゴリズムが使用されます。(小さなアルファベットよりも)より効率的になる可能性があります。長い針と大きなアルファベットの場合は、KMPまたはBoyer-Mooreアルゴリズムの方が適しています。これらは、可能な戦略を説明するための単なる例です。

マルチアルゴリズムアプローチは新しいアイデアではありません。いくつかの商用の並べ替え/検索パッケージで使用されていると思います(たとえば、メインフレームで一般的に使用されるSYNCSORTは、いくつかの並べ替えアルゴリズムを実装し、ヒューリスティックを使用して、特定の入力に「最適な」ものを選択します)

各検索アルゴリズムにはいくつかのバリエーションがあり、たとえば、このホワイトペーパーで説明しているように、そのパフォーマンスには大きな違いが生じます。

サービスをベンチマークして、追加の検索戦略が必要な領域を分類するか、セレクター機能をより効果的に調整します。このアプローチは迅速でも簡単でもありませんが、うまくいけば非常に良い結果が得られます。


1
応答、特に私が以前に見たことのないSustik-Mooreへのリンクをありがとう。複数のアルゴリズムのアプローチは確かに広く使用されています。Glibcは基本的に、needle_lenが1、<32、または> 32のどちらであるかに応じて、strchr、2ウェイの不良文字シフトテーブルなし、または2ウェイの不良文字シフトテーブルを行います。現在のアプローチは同じですが、シフトテーブルを常に使用する点が異なります。テーブルのどの要素が初期化されているかをマークするために使用されるビットセットの32バイトのメモリセットでこれを行うために必要な1kbのメモリセットを置き換えました。小さな針でもメリットがあります(ただしオーバーヘッドはありません)。
R .. GitHub STOP HELPING ICE

1
それについて考えた後、私はSustik-Mooreの意図されたアプリケーションが何であるかに本当に興味があります。小さなアルファベットでは、大幅なシフトを行うことはできません(アルファベットのすべての文字はほぼ確実に針の端近くに表示されます)。有限オートマトンアプローチは非常に効率的です(小さな状態遷移表)。したがって、Sustik-Mooreが最適である可能性のあるシナリオを想定することはできません...
R .. GitHub ICEの

素晴らしい反応-この特定の答えにスターを付けることができれば、そうします。
Jason S

1
@R .. sustik-mooreアルゴリズムの背後にある理論は、針が比較的大きく、アルファベットが比較的小さい場合(DNAシーケンスの検索など)、平均シフト量が大きくなるはずです。この場合のより大きいとは、同じ入力を与えられた場合に基本的なボイヤー・ムーアアルゴリズムが生成するよりも大きいことを意味します。これが有限オートマトンアプローチまたは他のいくつかのボイヤームーアバリエーション(多数あります)に比べてどれほど効率的であるかは、言いがたいものです。そのため、候補となるアルゴリズムの特定の長所/短所を調査するために時間を費やすことを強調しました。
NealB

1
うーん、ボイエ・ムーアからの悪いキャラクターシフトという意味でシフトを考えていたのは行き詰まっていたと思います。BMのサフィックスシフトが改善されたため、Sustik-MooreはDFAアプローチよりもDNA検索を上回った可能性があります。きちんとしたもの。
R .. GitHub ICEのヘルプ停止

21

このディスカッションで引用されている技術レポートを見て驚いた。私は、上記のSustik-Mooreという名前のアルゴリズムの作成者の1人です。(この用語は論文では使用していません。)

ここで強調したいのは、このアルゴリズムの最も興味深い機能は、各文字が最大で1回検査されることを証明するのが非常に簡単であることです。以前のBoyer-Mooreバージョンでは、各手紙が最大で3回、後で最大で2回調べられることが証明され、それらの証明はより複雑でした(紙の引用を参照)。したがって、このバリアントを提示/研究することには、教訓的な価値もあると思います。

このペーパーでは、理論的な保証を緩和しながら効率性を高めるためのさらなるバリエーションについても説明します。それは短い論文であり、資料は私の意見では平均的な高校卒業生に理解できるはずです。

私たちの主な目標は、このバージョンをさらに改善できる他の人に注目させることでした。文字列検索には非常に多くのバリエーションがあり、私たちだけでは、このアイデアがメリットをもたらす可能性のあるすべてを考えることはできません。(固定テキストと変更パターン、固定パターンの異なるテキスト、前処理の可能/不可能、並列実行、大きなテキストでの一致するサブセットの検索、エラーの許可、ほぼ一致など)


1
たまたまCまたはC ++の実装を知っていますか?これをいくつかのdnaモチーフ検索(完全なモチーフ一致)に使用することを考えています。そうでない場合は、私が実装を自分で開発し、ブーストアルゴリズムを送信して
みる

4
知られていない利用可能な実装では、Sustik-ムーア/ 2BLOCKアルゴリズムは、実際に使用される可能性が低いと思われるなど、要約論文の結果から省略され続けて、「正確な文字列マッチング問題:総合実験的評価」
JDiMatteo

18

最速の部分文字列検索アルゴリズムは、コンテキストに依存します。

  1. アルファベットのサイズ(例:DNA vs英語)
  2. 針の長さ

2010年の論文「厳密な文字列マッチングの問題:包括的な実験的評価」では、51のアルゴリズム(アルファベットのサイズと針の長さが異なる)の実行時間を表に示しているため、コンテキストに最適なアルゴリズムを選択できます。

これらのアルゴリズムはすべて、C実装とテストスイートを備えています。

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


4

本当に良い質問です。ほんの少しだけ追加してください...

  1. 誰かがDNA配列のマッチングについて話していました。しかし、DNAシーケンスの場合、私たちが通常行うことは、干し草用のデータ構造(サフィックス配列、サフィックスツリー、FMインデックスなど)を構築し、それに対して多くの針を一致させることです。これは別の質問です。

  2. 誰かがさまざまなアルゴリズムのベンチマークを行いたいのであれば、それは本当に素晴らしいことです。圧縮とサフィックス配列の構築に関する非常に優れたベンチマークがありますが、文字列マッチングに関するベンチマークは見たことがありません。干し草の山になる可能性のある候補は、SACAベンチマークの可能性があります

  3. 数日前、お勧めのページからBoyer-Moore実装をテストしていました(編集:memmem()のような関数呼び出しが必要ですが、これは標準関数ではないため、実装することにしました)。私のベンチマークプログラムはランダムな干し草を使用しています。そのページのBoyer-Moore実装は、glibcのmemmem()およびMacのstrnstr()よりも数倍速いようです。興味がある場合は、実装がここにあり、ベンチマークコードがここにあります。これは明らかに現実的なベンチマークではありませんが、それは始まりです。


SACAベンチマークの干し草の候補と一緒にテストする良い針がある場合は、他の質問への回答として投稿し、より良い回答が得られない場合は、それを承認済みとしてマークします。
R .. GitHub STOP HELPING ICE

3
memmemとBoyer-Mooreについては、Boyer-Moore(または、Boyer-Mooreの拡張機能の1つ)がランダムデータで最高のパフォーマンスを発揮する可能性が非常に高くなります。ランダムデータは、周期性の確率が非常に低く、部分一致が長くなるため、二次ワーストケースになります。Boyer-MooreとTwo-Wayを組み合わせる方法、またはBoyer-Mooreが「安全に使用できる」ことを効率的に検出する方法を探していますが、これまでのところ成功していません。ところで、比較としてglibcのmemmemは使用しません。基本的にglibcと同じアルゴリズムの実装は、数倍高速です。
R .. GitHub STOP HELPING ICE

言ったように、それは私の実装ではありません。Christian CharrasとThierry Lecroqの功績です。ランダム入力がベンチマークに適さない理由は想像できますが、glibcが理由でアルゴリズムを選択しているのは確かです。また、memmem()が効率的に実装されていないと思います。試してみます。ありがとう。
user172818

4

私はそれが古い質問であることを知っていますが、ほとんどの悪いシフトテーブルは単一の文字です。それがデータセットにとって意味がある場合(特に、それが単語で書かれている場合)、および使用可能なスペースがある場合、単一の文字ではなくNグラムで構成された悪いシフトテーブルを使用することにより、劇的なスピードアップを得ることができます。


3

stdlibを使用しますstrstr

char *foundit = strstr(haystack, needle);

それはとても速く、タイプするのに約5秒しかかかりませんでした。


26
そして、私の質問を読むと、私がそれをしのぐのはとても簡単な時間でした。私はあなたの皮肉が好きなので、-1はスキップします。
R .. GitHub ICE HELPING ICEの停止

3

これは、コア全体から使用されるPythonの検索実装です。コメントは、それが圧縮されたボイヤー・ムーアデルタ1テーブルを使用していることを示しています

私自身、文字列検索でかなり大規模な実験を行いましたが、それは複数の検索文字列のためのものでした。HorspoolBitapのアセンブリ実装は、パターン数が少ない場合のAho-Corasickのようなアルゴリズムに対して独自に実装できることがよくあります。


3

より高速な「単一の一致する文字を検索する」(ala strchr)アルゴリズム。

重要なメモ:

  • これらの関数は、gccコンパイラの組み込みの「(ゼロの数/カウント)」コンパイラ組み込みを使用し__builtin_ctzます。これらの機能は、この操作を実行する命令(x86、ppc、arm)を備えたマシンでのみ高速になる可能性があります。

  • これらの関数は、ターゲットアーキテクチャが32ビットおよび64ビットの非整列ロードを実行できることを前提としています。ターゲットアーキテクチャがこれをサポートしていない場合、読み取りを適切に調整するためにいくつかの起動ロジックを追加する必要があります。

  • これらの機能はプロセッサに依存しません。ターゲットCPUにベクトル命令がある場合は、(はるかに)うまくいく可能性があります。たとえば、strlen以下の関数はSSE3を使用しており、スキャンしたバイトをXORして、以外のバイトを探すように簡単に変更できます0。Mac OS X 10.6(x86_64)を実行する2.66GHz Core 2ラップトップで実行されたベンチマーク:

    • 843.433 MB /秒 strchr
    • 2656.742 MB /秒 findFirstByte64
    • 13094.479 MB /秒 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 OPは、このソリューションに「乗り越えられないバグ」があることをコメントで指摘しています。

要求されたバイトまたはnullターミネーターを超えて読み取ることができます。これらは、マップされていないページまたはページにアクセスすることができ、読み取り権限がありません。文字列関数は、整列されていない限り、大規模な読み取りを使用できません。

これは技術的には真実ですが、コメントでOPによって提案されたメソッドを含め 1バイトより大きいチャンクで動作する事実上すべてのアルゴリズムに適用されます。

典型的なstrchr実装は単純ではありませんが、指定したものよりもかなり効率的です。最も広く使用されているアルゴリズムについては、この終わりを参照してくださいhttp : //graphics.stanford.edu/~seander/bithacks.html#ZeroInWord

また、それ自体がアライメントとはまったく関係ありません。確かに、これは使用中の一般的なアーキテクチャの大部分で説明されている動作を引き起こす可能性がありますが、これはマイクロアーキテクチャの実装の詳細に関係があります-アライメントされていない読み取りが4K境界をまたぐ場合(これも通常)、その読み取りはプログラムを引き起こします次の4Kページ境界がマップされていない場合の終了エラー。

しかし、これは答えで与えられたアルゴリズムの「バグ」ではstrchrありstrlenません。その動作は、関数lengthが検索のサイズを制限する引数を受け入れ、受け入れないためです。検索char bytes[1] = {0x55};は、説明のために、たまたま4K VMページ境界の最後に配置され、次のページがマップされていませんstrchr(bytes, 0xAA)strchr一度に1バイトずつ実装されています)。同じ方法。strchr関連いとこの同上strlen

なければlength、引数、あなたはバイト単位のアルゴリズムを高速化アルゴリズムと背面のアウト切り替える必要があるときに伝える方法がありません。より可能性の高い「バグ」は、「割り当ての過去のサイズ」を読み取ることであり、技術的undefined behaviorにはさまざまなC言語標準に従っており、のようなものによってエラーとしてフラグが立てられますvalgrind

要約すると、バイトチャンクよりも大きいチャンクで動作して高速化するものは、これはコードで応答し、OPによって指摘されたコードですが、バイト精度の読み取りセマンティクスを持つ必要がある場合、length引数がないと「バギー」になる可能性があります。 「最後の読み取り」のコーナーケースを制御します。

この回答のコードは、ターゲットCPUに高速のctzlike命令がある場合に、自然なCPUワードサイズチャンクの最初のバイトをすばやく見つけることができるカーネルです。正しく整列された自然の境界、または何らかの形の境界でのみ動作することを確認するなどを追加するのは簡単lengthです。これにより、高速カーネルからより遅いバイト単位のチェックに切り替えることができます。

OPはコメントでも述べています。

あなたのctz最適化に関しては、それはO(1)テール操作に対してのみ違いを生じます。小さな文字列を使用してパフォーマンスを向上させることができます(たとえばstrchr("abc", 'a');、主要なサイズの文字列を使用すると確実に向上するわけではありません)。

このステートメントが正しいかどうかは、問題のマイクロアーキテクチャに大きく依存します。正規の4ステージRISCパイプラインモデルを使用すると、ほぼ間違いなく真実です。しかし、コアスピードがメモリストリーミングスピードを完全に引き下げる現代のアウトオブオーダースーパースカラーCPUに当てはまるかどうかを判断するのは非常に困難です。この場合、「ストリーミング可能なバイト数」に対して「リタイア可能な命令の数」に大きなギャップがあるため、もっともらしいだけでなく、非常に一般的です。ストリーミング可能なバイトごとにリタイア可能な命令の数」。これが十分に大きければ、ctz+シフト命令は「無料」で実行できます。


「長さが1の針の場合、を使用しますstrchr。」-最速の部分文字列検索アルゴリズムを要求しました。長さ1の部分文字列を見つけることは、特別なケースであり、最適化することもできます。strchr上記のような長さ1()の部分文字列の現在の特別な場合のコードを交換すると、(おそらく、strchr実装方法によっては)処理が速くなります。上記のアルゴリズムは、一般的な単純なstrchr実装よりもほぼ3倍高速です。
ジョン

2
OPは文字列が適切にnullで終了していると言ったので、あなたの議論char bytes[1] = {0x55};は無関係です。事前に長さがわからない任意の単語読み取りアルゴリズムにこれが当てはまるというコメントは非常に関連があります。
セスロバートソン、

1
この問題は、私が引用したバージョンには当てはまりません。これは、境界整列されたポインターでのみ使用するためです。少なくとも、これは正しい実装です。
R .. GitHub ICEのヘルプを停止する

2
@R、「アラインされたポインタ」とは何の関係もありません。仮に、あなたがバイトレベルの粒度でVMの保護をサポートするアーキテクチャを持っていて、各場合mallocの割り当てはいずれかの側に「十分にパディング」だった(ポインタが揃っているかどうか.... VMシステムは、その割り当てのためにバイト粒状保護を施行ささいな32ビットのint自然な配置を想定している)は無意味です-その配置された読み取りが割り当てのサイズを超えて読み取ることはまだ可能です。 ANY割り当てのサイズ過去の読み取りがありますundefined behavior
johne

5
@johne:コメントに+1。概念的には正しいですが、実際には、バイト単位の保護は、保存したり、存在しないようにしたり、存在しないように強制したりするのに非常にコストがかかります。基礎となるストレージがと同等のものから取得されたページ粒度マッピングであることがわかっている場合はmmap、アラインメントで十分です。
R .. GitHub ICE HELPING ICEを停止する'13

3

"fastest strstr"を検索し、興味のあるものがあれば私に尋ねてください。

私の見解では、あなたは自分にあまりにも多くの制限を課しています(はい、私たちは皆、最大線形探索で線形線形が必要です)。しかし、それまでは本物のプログラマーが介入しなければなりません。 2..16パターンより短いBNDMで十分に強化されています)。

簡単な例:

AS-1行の文字列(206908949bytes)にパターン(32バイト)の検索を行う...スキップ・パフォーマンス(大きな--より良い):3041パーセント、6801754のスキップ/イテレーションのRailgun_Quadruplet_7Hasherezade_hits / Railgun_Quadruplet_7Hasherezade_clocks:0/58 Railgun_Quadruplet_7Hasherezadeパフォーマンス:3483キロバイト /時計

パターン(32バイト)から文字列(206908949バイト)への検索を1行で実行しています...スキップパフォーマンス(より大きく):1554%、13307181スキップ/反復Boyer_Moore_Flensburg_hits / Boyer_Moore_Flensburg_clocks:0/83 Boyer_Moore_Flensburgパフォーマンス:2434KB /時計

文字列(206908949bytes)にパターン(32バイト)の検索を行うなど、1行...スキップ・パフォーマンス(大きな--より良い):129パーセント、160239051のスキップ/イテレーション二Way_hits /二Way_clocks:0/816 つ-ウェイパフォーマンス:247KB /クロック

サンメイス、
よろしく


3

質問で述べた双方向アルゴリズム(ちなみにこれは信じられないほどです!)は、一度にマルチバイトの単語を効率的に処理するように改善されています:Optimal Packed String Matching

私は論文全体を読んだわけではありませんが、時間の複雑さの主張はO(1)であるいくつかの新しい特別なCPU命令(SSE 4.2に含まれる)に依存しているようですが、利用できない場合でも、 O(log log w)時間でそれらをシミュレートして、あまり聞こえないwビットワードを探します。


3

たとえば、4つの異なるアルゴリズムを実装できます。M分ごとに(経験的に決定される)、現在の実際のデータに対して4つすべてを実行します。N回の実行で統計を累積します(TBDも同様)。次に、勝者だけを次のM分間使用します。

勝者の統計をログに記録して、勝たないアルゴリズムを新しいアルゴリズムで置き換えることができるようにします。勝者のルーチンに最適化の努力を集中します。ハードウェア、データベース、またはデータソースに変更を加えた後の統計には特に注意してください。可能であれば、その情報を統計ログに含めてください。これにより、ログの日付/タイムスタンプから情報を把握する必要がなくなります。


3

私は最近、さまざまな利用可能なアルゴのパフォーマンスを測定するための素晴らしいツールを発見しました:http ://www.dmi.unict.it/~faro/smart/index.php

あなたはそれが役に立つかもしれません。また、部分文字列検索アルゴリズムについて簡単に説明する必要がある場合は、Knuth-Morris-Prattを使用します。


リンクをありがとう。テストは、典型的なケースのタイミングについては興味深いように見えますが、最悪の場合のタイミングを把握するためではありません。
R .. GitHub ICE HELPING ICEの停止

2

また、パフォーマンスに大きな影響を与える可能性があるため、いくつかのタイプの文字列を使用して多様なベンチマークを設定することもできます。アルゴは、自然言語の検索(およびここでも、異なる形態学のために細かい違いがある可能性があります)、DNA文字列、またはランダム文字列などに基づいて差異を実行します。

アルファベットのサイズは、針のサイズと同様に、多くのアルゴで役割を果たします。たとえば、Horspoolは英語のテキストには優れていますが、アルファベットのサイズが異なるためDNAには適していません。良い接尾辞を導入すると、これが大幅に緩和されます。


0

それが絶対的に最高かどうかはわかりませんが、ボイヤー・ムーアとは良い経験をしました。


Boyer-Mooreの悪いシフトテーブルをTwo-Wayと組み合わせる方法を知っていますか?Glibcはこれの変形を長い針(> 32バイト)に対して行いますが、最後のバイトのみをチェックします。問題は、双方向では針の右部分を左から右に検索する必要があるのに対し、ボイヤー・ムーアの悪いシフトは右から左に検索するときに最も効率的です。双方向で左から右に使用してみました(シフトテーブルによる前進か、通常の双方向の右半分の不一致のどちらか長い方)。ほとんどの場合、通常の双方向に対して5〜10%の速度低下があり、パフォーマンスが向上するケースを見つけることができませんでした。
R .. GitHub ICE HELPING ICEの停止

0

これは質問に直接答えることはできませんが、テキストが非常に大きい場合は、重複するセクション(パターン長による重複)に分割してから、スレッドを使用してセクションを同時に検索してみてください。最速のアルゴリズムに関しては、ボイヤー・ムーア・ホースプールはボイヤー・ムーアの亜種の中で最速ではないにしても最速の1つだと思います。このトピックの「BMH(Boyer–Moore–Horspool)Searchよりも高速なアルゴリズム」で、ボイヤー・ムーアの変種(名前はわかりません)をいくつか投稿しました。


0

現在、S。FaroとOM Kulekciによる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は、これらのプラットフォームでここで言及されている他のすべてを上回っています。また、最新のものでもあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.