C ++でパフォーマンスを改善する複雑さ、またはそれを行わない方法について

画像
かつて、何年も前に、あるクライアントが私のところに来て、一つのすばらしいプロジェクトを理解し、仕事のスピードを上げるための指示を涙を流して懇願しました

要するに、タスクはこれでした-HTMLページを削除し、データベース(MySQL)で折りたたみを組み立てるC ++の特定のロボットがあります。 多くの機能とLAMP上のWebを使用していますが、これはストーリーとは関係ありません。

前のチームは、クラウド内の4コアXeonを管理して、 毎秒2ページという素晴らしい収集速度を実現し、コレクターと同じ種類の別のサーバー上のデータベースの両方のCPU使用率100%にしました。



ちなみに、バンガロールの「有能なプロのチーム」は、それができないことに気付いて降伏し、散らばっていました。 ビーズのほか」(C)。

PHPとデータベーススキーマで物事を整理することの複雑さについては改めて説明しますが、ここまで来たスキルの例を1つだけ紹介します。

剖検への行き方


画像
このような深刻なデータベースの負荷は、そもそも私に興味がありました。 詳細なロギングをオンにします。 すべての場所で自分の髪の毛を引き出し始めます 。ここにあります。

もちろん、インターフェイスからのタスクはデータベース内で形成され、ロボットは1秒間に50回尋問しました。新しいタスクは表示されませんでしたか? さらに、データは、ロボットではなくインターフェイスに便利な方法で自然にレイアウトされます。 結果は、リクエストで3つの内部結合になります。

間隔をすぐに「1秒間に1回」増やします。 クレイジーリクエストを削除します。つまり、3つのフィールドの新しいプレートを追加し、Webからテーブルにトリガーを書き込むと、自動的に入力され、シンプルに変更されます。
select * from new_table where status = Pending 

新しい状況-コレクターはまだ100%ビジーで、データベースは2%ビジーで、現在は1秒あたり4ページです。

プロファイラーをピックアップ


画像
そして突然、ランタイムの80%がすばらしいEnterCriticalSectionメソッドLeaveCriticalSectionメソッドで占められていることがわかりました 。 そして、それらは(予測的に)有名な会社の標準アロケーターから呼び出されます。

夕方はだらしないようになりますが、多くの仕事があり、心から書き直さなければならないことを理解しています。

そしてもちろん、私の前任者は、SAXが非常に複雑であるため、 一連の正規表現でHTML バイドロダー解析することを好みました

知り合いになりましょう-私にとって何が改善されましたか?

思考光線による時期尚早な最適化の危険性


画像
データベースが100%読み込まれたことを見て、処理のために新しいURLをリストに挿入するのは遅いと固く確信しました。

この特定のコードを最適化することで、それらを導いたものを理解することさえ難しいと感じています。 しかし、アプローチ自体! 理論的には、ここで減速していますが、まだ減速しましょう。

このために、彼らはそのようなトリックを思いつきました:

  1. 非同期挿入要求キュー
  2. メモリー内の渡されたすべてのURLを記憶する巨大なロックを備えた、メモリー内の巨大なHashMap。 また、サーバーであるため、このような最適化の後、定期的に再起動する必要がありました。 キャッシュのクリーニングを完了していません。
  3. たとえば、多くの魔法の定数-データベースから次のURLのバッチを処理するために、400を超えるエントリは取得されません。 なぜ400? そして拾いました。
  4. データベース内の「作家」の数は多く、それぞれがサイクルの中で自分の役割を果たそうとしましたが、彼は突然幸運でした。

そしてもちろん、他の多くの真珠も入手できました。

一般に、コードの進化を観察することは非常に有益でした。 ストレージのメリットを拒否することはありません。すべてが慎重にコメント化されています。 このように

 void GodClass::PlaceToDB(const Foo* bar, ...) { /*      1,  */ /*      2 -     ,  */ /*      3 -  ,      ,  */ .... /*    N-1,        ,  */ //   -   } 


私は何をしましたか


画像
もちろん、すべてのトリックはすぐに捨てられ、同期挿入を返し、重複をカットするために制約をデータベースに掛けました(巨大なロックと自己記述ハッシュマップで踊るのではなく)。

また、自動インクリメントフィールドを削除し、それらの代わりにUUIDを挿入しました(新しい値を計算するために暗黙のロックテーブルが忍び寄る場合があります)。 同時に、テーブルを大幅に削減し、1行あたり20Kに削減しました。データベースが沈んでいるのは驚くことではありません。

また、魔法の定数も削除しました。それらの代わりに、共通のタスクキューとキューを満たすための別のスレッドを持つ通常のスレッドプールを作成し、空になったりオーバーフローしたりしないようにしました。

結果は毎秒15ページです。

ただし、再プロファイリングでは画期的な改善は見られませんでした。 もちろん、アーキテクチャの改善による7倍の加速もパンですが、十分ではありません。 結局のところ、元のジャムはすべて残っていたので、最適化されたピースのみを削除して死にました。

メガバイト構造化ファイルを解析するための正規表現が悪い


画像
私は自分の前に何が行われたのかを研究し続け、私は知らない著者のアプローチを楽しんでいます。

めでか!

トラクターの恵みにより、連中はこのようなデータを取得する問題を解決しました(それぞれに独自の正規表現のセットがあります)。


驚くべきことに、このアプローチでは、毎秒少なくとも2ページを噛みました。

チューニング後に式自体を引用していないことは明らかです-これは読みにくい波線の巨大なシートです。

それだけではありません-もちろん、正しいブーストライブラリが使用され、すべての操作はstd :: stringで実行されました( 正しく-しかし、HTMLを置く場所はどこですか?Char *は概念ではありません! これは、非常に多くのメモリ割り当てが発生する場所です。

char *とSAXスタイルのシンプルなHTMLパーサーを使用し、必要な数字を覚えて、URLを並行して引き出します。 仕事の2日間、そして見よ。

結果は1秒あたり200ページです。

すでに受け入れられますが、十分ではありません。 わずか100回。

シェルへの別のアプローチ


画像
新しいプロファイリングの結果に目を向けます。 より良くなりましたが、まだ多くの割り当てがあり、何らかの理由でBoost to_lower()が最初に出ました。

最初に目を引くのは、Javaからシームレスに描画される強力なURLクラスです。 ええ、そうです-C ++であるため、とにかく高速になりますアロケーターは異なると思います 。 したがって、コピーとサブストリング()の束がヒンズー教のすべてです。 そしてもちろん、to_lowerをURL ::ホストに直接適用します-それはすべての比較と言及で必要であり、確かに後押しします。

to_lower()の過度の使用を削除し、std :: stringの代わりに再配布せずにURLをchar *に書き換えます。 同時に、数サイクルを最適化しています。

結果は1秒あたり300ページです。

これで150回の加速が達成されましたが、加速の余地はまだありました。 そして彼は2週間以上を殺しました。

結論


画像
いつもの結論-ジャンルの古典。 パフォーマンスを評価するときにツールを使用します。頭から発明しないでください。 太陽を手動で設定する代わりに、より広い(またはより広い)既製のライブラリを使用します。

そして、 Saint Connectiusがあなたと一緒に高性能を発揮できますように

Source: https://habr.com/ru/post/J157997/


All Articles