web-dev-qa-db-ja.com

いくつかの一般的なアルゴリズム最適化の機会は何ですか-数学的またはその他

誰もが知っておくべき一般的なアルゴリズム最適化の機会は何ですか?最近、アプリケーションの一部のコードを修正/レビューしているところ、実行速度がかなり遅いように見えることに気付きました。次のループが原因であることが判明しました。

...
    float s1 = 0.0;
    for (int j = 0; j < size; ++j) {
        float diff = a[j] - b[j]; 
        s1 += (diff*diff * c[j]) + log(1.0/c[j]);
    }
...

これは、と同等です。

j {(aj-bj2* cj + log(1/cj)}

プログラムが実行されるたびに、このループはおそらく10万回以上呼び出されるため、ログと除算を繰り返し呼び出すと、パフォーマンスが非常に大きく低下します。シグマ表現をざっと見ると、些細な修正があることがかなり明確になります。対数の恒等式を十分に覚えていれば、それを見つけることができます。

j {(aj-bj2* cj + log(1/cj)} =

j {(aj-bj2* cj } + ∑j {log(1.0/cj)} =

j {(aj-bj2* cj } + log(1.0 /(Πjcj))

そして、はるかに効率的なスニペットにつながります、

...
    float s1 = 0.0;
    float s2 = 1.0;
    for (int j = 0; j < size; ++j) {
        float diff = a[j] - b[j]; 
        s2 *= c[j];
        s1 += (diff*diff * c[j]);
    }
    s1 += log(1.0/s2);
...

これは非常に大きなスピードアップにつながり、元の実装に移行するはずでした。元の開発者がこの単純な改善を認識していなかったか、「積極的に認識していなかった」ためではなかったと思います。

これは私に、他にどのような、同様の、共通の機会と  見逃したり見落としたりして、どうすればそれらをよりよく見つけることができますか?私は特定のアルゴリズムの複雑なエッジケースにはあまり興味がありませんが、上記のような例では、頻繁に発生する「明白な」概念と考えるかもしれませんが、他の人はそうではないかもしれません。

6
xhs7is82wl

私の2セント:

  1. 可能であれば、プログラムのデータ構造を変更するは、たとえ些細な変更であっても、非常に役立つ可能性があります。スパース行列の表示を隣接テーブルから一般的なスパース行列表現に変更すると、プログラムの平均実行時間が半分になります。

  2. 再帰を取り除きます。これを行うのは難しいですが、有益である可能性があります。ただし、不適切に実行すると、深刻な問題が発生する可能性があり、非再帰的なコードは通常、再帰的なバージョンほど直感的ではありません。

  3. Cache頻繁に使用される値の一部。これは不正行為のように見えますが、非常に有益である可能性があります。すべてのコンテストプログラマーはすでにこれを知っているはずです。 James Blackのコメントで言及されているmemorizationも参照してください。

  4. ショートカット評価を適切に使用してください。これにより、通常はパフォーマンスが大幅に向上することはなく、コードが読み取れなくなる可能性があります。ただし、評価対象の式に非常に重い作業が必要な場合は、これが非常に役立ちます。

  5. 編集:あなたの仕事が計算量が多く、浮動小数点計算を伴う場合(特に近似を伴う場合)、時々式を言い換えます(アルゴリズムを再設計しないでください) 、式を同等のものに変更するだけです)コンピュータが使用する浮動小数点演算のため、プログラムを大幅に高速化できます。多くの例は、数値解析や科学計算の本で見つけることができます。本当に興味のある人にとっては、 すべてのコンピューター科学者が浮動小数点演算について知っておくべきこと は素晴らしい論文です。

4
zw324

実装はごまかす可能性がありますが、捕まえられてはなりません

アプリケーションをプロファイリングして時間を費やしている場所を特定した後、次のステップは、プログラムの残りの部分が問題のコードから何を期待するかを正確に決定することです。多くの場合、内部ループが原因であるため、コードの量は少ないことがよくありますが、重要な作業と無関係な作業の違いは非常に微妙な場合があります。発信者が何に依存しているかがわかったら、同じ結果を生成するための新しい方法をブレインストーミングできます。

たとえば、プロファイリングを実行すると、リストの先頭にstrlen()が表示されます。それは何度も何度も呼ばれています。 strlen()を調べると、すべてのバイトをカウントすることで文字列の長さが検出されていることがわかります。さて、そのどの部分が重要ですか?どうすればごまかすことができますか?発信者は実際に私たちがすべてのバイトに触れることを気にしていますか?おそらくそうではありません。文字列メモリの一部を逆参照してもかまいませんか?そうでないかもしれない。おそらく、結果をメモすることができます。捕まりますか?次に、文字列が変更された場合に、キャッシュされた結果が無効になっていることを確認する必要があります。他にどのようにごまかすことができますか?発信者を調べると、彼らがstrlen(s) > 10を実行していることがわかります。 11でカウントを停止すると、作業が減り、捕まることがなくなります。

他の人が指摘しているように、質問の例は微妙なものです。数学演算をループから引き上げることで不正行為をします。捕まりますか?関連する精度の問題と、中間浮動小数点値が結果にどのように影響するかについてよく考えてください。

ある実際の例では、ミラーリングされたデータベースの起動が非常に遅いことがわかりました。このコードは、コピーの1つを選択し、それが最新であることを確認してから、残りのN個のコピーをすべてロードして最初のコピーと比較することにより、データベースの整合性を確保しました。 N個すべてのコピーを同時に読み取るのに十分なメモリがなかったため、これを簡単に並列化することはできませんでした。どうすればごまかすことができますか?さて、実際の目標は何ですか?コードは実際には読み取りと比較をまったく気にしません。それが気にするのは、すべてのコピーを選択したものと同じにすることです。他のすべてのコピーを読み取る代わりに、代わりに上書きして、既知の適切なコピーですべてを上書きするとどうなりますか?これで、多くの並列IOを実行でき、操作が何倍も速くなります。どうすればキャッチできますか?書き込みを途中で中断できます。または、書き込みの一部のみを実行できます。これらのコーナーケースに対処することは作業の大部分でしたが、高速で並列書き込みは努力する価値がありました。

3
Ben Jackson

計算に使用できるさまざまな代数すべてに対して、代数的等価物が無限に供給されます。有用な特定の拡張リストを書き留めることはできないと思います。

同様に、多くのアルゴリズムの同等性があります。これらは、非常に非線形な方法で計算時間に影響を与える可能性があるため、一般的に価値があります。

また、線形削減の代わりに合計ツリーを使用したり、キャッシュが多い場合に再利用可能な計算結果をキャッシュしたりするなど、コンピューティングハードウェア構造によって動機付けられたさまざまな最適化があることもわかります。

コーディング時、およびコードのボトルネックが実際にどこにあるかを発見したときに、そのような同等性が存在すること(モジュロ可能な精度の変更とさまざまなリソース要求)に注意するのが最善です。

1
Ira Baxter

アルゴリズムによる最適化の機会。これが私がそれらについて一般的に考える方法です:

アルゴリズムの複雑さはO(NlogN)以下ですか?そうであれば、おそらく十分です。

そうでない場合は、他のアルゴリズムを探し始めます。

最終的に非常に大きなデータセットでは、の定数を比例的に変更してもあまり効果はありません(これは基本的に投稿された例が行うことです)。アルゴリズムの複雑さを変更するだけで、漸近的な場合の速度が向上します。

固定サイズのデータ​​セットがある場合は、おそらく改善する価値があります。

ああほとんど忘れてしまった!:最適化する前に必ず測定してください。

1
Mitch Wheat