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があなたと一緒に高性能を発揮できますように



All Articles