web-dev-qa-db-ja.com

C#/。NETプログラムを最適化するためのヒント

最近、最適化は失われた芸術のようです。すべてのプログラマーが自分のコードからあらゆるオンスの効率を絞り出した時代はありませんでしたか?雪の中を5マイル歩いている間によくそうしますか?

失われた芸術を取り戻すという精神で、C#/。NETコードを最適化するための単純な(またはおそらく複雑な)変更について知っているヒントは何ですか?何を達成しようとしているのかに依存するのは非常に広いので、コンテキストをヒントに提供するのに役立ちます。例えば:

  • 多くの文字列を連結するときは、代わりにStringBuilderを使用します。これに関する警告については、下部のリンクを参照してください。
  • string1.ToLower() == string2.ToLower()のようなことをする代わりに、string.Compareを使用して2つの文字列を比較します

これまでのところ、一般的なコンセンサスが測定されているようです。この種の点を見逃しています。測定では、何が間違っているのか、ボトルネックに陥った場合に何をすべきかがわかりません。一度文字列連結のボトルネックに遭遇しましたが、どうすればよいのかわからなかったので、これらのヒントは役に立ちます。

これを投稿することに対する私のポイントは、一般的なボトルネックが発生する前に、それらを回避する方法を用意することです。誰もが盲目的に従うべきプラグアンドプレイコードである必要はありませんが、パフォーマンスを少なくともある程度考慮する必要があり、注意すべき一般的な落とし穴があることを理解することに関する詳細です。

しかし、ヒントが有用である理由と適用する場所を知ることも役立つかもしれません。 StringBuilderのヒントについては、ずっと前に ここではJon Skeetのサイトにあります で助けを見つけました。

77
Bob

最近、最適化は失われた芸術のようです。

たとえば、顕微鏡の製造が芸術として実践されたのは1日1回でした。光学原理はよく理解されていませんでした。部品の標準化はありませんでした。チューブ、ギア、レンズは、熟練した労働者が手作業で作成する必要がありました。

最近の顕微鏡は、工学分野として生産されています。物理学の根底にある原理は非常によく理解されており、既製の部品が広く利用可能であり、顕微鏡製造エンジニアは、実行するように設計されたタスクに対して機器を最適化する方法について十分な情報に基づいた選択を行うことができます。

パフォーマンス分析は「失われた芸術」であるということは、非常に良いことです。その芸術は芸術として実践された。最適化は、それが何であるかについてアプローチする必要があります:エンジニアリングの問題堅実なエンジニアリングの原則を慎重に適用することで解決可能です。

私は、人々がvbscript/jscript /アクティブサーバーページ/ VB// C#を最適化するために使用できる「ヒントとコツ」のリストを何十回も求められてきました。 「ヒントとテクニック」を強調することは、パフォーマンスにアプローチするためのまったく間違った方法です。その方法は、理解しにくく、推論しにくく、維持しにくいコードにつながります。通常、対応する単純なコードよりも顕著に高速ではありません。

パフォーマンスにアプローチする正しい方法は、他の問題と同様に、エンジニアリングの問題としてアプローチすることです。

  • 意味のある、測定可能な、顧客中心の目標を設定します。
  • テストスイートを構築して、現実的でありながら制御可能で再現可能な条件下で、これらの目標に対するパフォーマンスをテストします。
  • これらのスイートが目標を達成していないことを示している場合は、プロファイラーなどのツールを使用して理由を把握してください。
  • プロファイラーが最悪のパフォーマンスのサブシステムとして特定するものから、全体を最適化します。各変更のパフォーマンスへの影響を明確に理解できるように、すべての変更についてプロファイリングを続けてください。
  • (1)目標を達成してソフトウェアを出荷する、(2)目標を達成可能なものに下方修正する、または(3)目標を達成できなかったためにプロジェクトがキャンセルされるまで、3つのことのいずれかが発生するまで繰り返します。

これは、機能の追加など、他のエンジニアリングの問題を解決するのと同じです-機能に顧客中心の目標を設定し、堅実な実装の進行状況を追跡し、慎重なデバッグ分析で見つかった問題を修正し、繰り返されるまで繰り返します出荷または失敗します。 パフォーマンスは機能です。

複雑で現代的なシステムのパフォーマンス分析には、些細な、または非現実的な状況に狭く適用できるトリックでいっぱいのバッグではなく、しっかりしたエンジニアリングの原則に規律と焦点を合わせる必要があります。ヒントやコツを使って実際のパフォーマンスの問題を解決したことは一度もありません。

106
Eric Lippert

優れたプロファイラーを入手してください。

優れたプロファイラーを使用せずにC#(実際には、任意のコード)を最適化しようとしても気にしないでください。実際には、サンプリングプロファイラとトレースプロファイラの両方を手元に置いておくと劇的に役立ちます。

優れたプロファイラーがなければ、誤った最適化を作成する可能性が高く、最も重要なのは、そもそもパフォーマンスの問題ではないルーチンを最適化することです。

プロファイリングの最初の3つのステップは、常に1)測定、2)測定、そして3)測定です。

45
Reed Copsey

最適化のガイドライン:

  1. あなたがする必要がない限り、それをしないでください
  2. 開発者の代わりに問題に新しいハードウェアを投げる方が安い場合は、それをしないでください
  3. 本番環境と同等の環境で変更を測定できる場合を除き、実行しないでください
  4. CPUの使用方法がわからない限り実行しないでくださいand Memoryプロファイラー
  5. コードが読めなくなったり、維持できなくなったりする場合は、実行しないでください

プロセッサの高速化が進むにつれて、ほとんどのアプリケーションの主なボトルネックはCPUではなく、帯域幅です。オフチップメモリ​​への帯域幅、ディスクへの帯域幅、ネットへの帯域幅です。

遠端から始めましょう:YSlowを使用して、Webサイトがエンドユーザーにとって遅い理由を確認し、戻ってデータベースアクセスを広すぎず(列)、深すぎない(行)に修正します。

CPU使用量を最適化するために何かを行う価値がある非常にまれなケースでは、メモリ使用量に悪影響を与えないように注意してください。開発者がCPUサイクルを節約するためにメモリを使用して結果をキャッシュしようとする「最適化」を見てきました。最終的な効果は、ページとデータベースの結果をキャッシュするために使用可能なメモリを削減し、アプリケーションの実行を大幅に遅くしたことです! (測定に関する規則を参照してください。)

また、「ダム」の最適化されていないアルゴリズムが「賢い」最適化アルゴリズムを破った場合も見ました。コンパイラライターやチップ設計者が、「非効率的な」ループコードを、パイプライン処理を使用してオンチップメモリ​​で完全に実行できる超効率的なコードに変換することで、いかに優れているかを過小評価しないでください。実行中にオンチップメモリ​​にとどまることができなかったという理由だけで、「効率的」と思われる逆方向にカウントするアンラップされた内部ループを使用した「賢い」ツリーベースのアルゴリズムを破ることができます。 (測定に関する規則を参照してください。)

21
Ian Mercer

ORMを使用する場合、N + 1の選択に注意してください。

List<Order> _orders = _repository.GetOrders(DateTime.Now);
foreach(var order in _orders)
{
    Print(order.Customer.Name);
}

顧客が熱心にロードされていない場合、データベースへのラウンドトリップが何度か発生する可能性があります。

16
Aaron
  • マジックナンバーを使用せず、列挙を使用する
  • 値をハードコードしないでください
  • タイプセーフであり、ボクシングとボックス解除を回避するため、可能な限りジェネリックを使用します
  • 絶対に必要な場所でエラーハンドラを使用する
  • 処分、処分、処分。 CLRはデータベース接続を閉じる方法を知らないため、使用後にそれらを閉じて、管理されていないリソースを破棄します
  • 常識を使用してください!
13
SoftwareGeek

メソッドをボトルネックとして特定しても、それに対して何をすべきかわからない場合、本質的に行き詰まっています。

そこで、いくつかのことをリストします。これらはすべて特効薬ではなく、プロファイルコードが必要です。私はあなたがすることができたすることができ、時々助けることができるもののための提案をしています。特に最初の3つは重要です。

  • (または主に)低レベルの型またはそれらの配列を使用して問題を解決してください。
  • 多くの場合、問題は小さいです。特に、低レベルの型(の配列)のみを使用するコードでよりスマートでないアルゴリズムを表現できる場合、スマートで複雑なアルゴリズムを使用しても必ずしも勝つとは限りません。たとえば、n <= 100のInsertionSortとMergeSort、またはTarjanのDominator検出アルゴリズムとビットベクトルを使用して、n <= 100の問題のデータフロー形式を単純に解決します。 (100はもちろんあなたにいくつかのアイデアを与えるためです-profile!)
  • より大きな問題のインスタンスのために他のコードを保持する必要がある場合でも、低レベルのタイプ(多くの場合、サイズが64未満の問題のインスタンス)を使用して解決できる特別なケースを書くことを検討してください。
  • 上記の2つのアイデアに役立つビット演算を学びます。
  • BitArrayは、ディクショナリ、またはさらに悪いことにリストと比較して、あなたの友人になることができます。ただし、実装が最適ではないことに注意してください。より速いバージョンを自分で書くことができます。引数が範囲外などをテストする代わりに、インデックスが範囲外にならないようにアルゴリズムを構成できますが、標準のBitArrayおよび itからチェックを削除することはできません。無料ではありません
  • 低レベル型の配列だけでできることの例として、BitMatrixは、 ulongs の配列として実装できるかなり強力な構造であり、それを走査することさえできます。一定時間内に最下位ビットを取得できるため(「幅優先検索」のキューと比較して-しかし、明らかに順序は異なり、インデックス純粋にあなたがそれらを見つける順序ではなく、アイテムの)。
  • 除算とモジュロは、右側が定数でない限り、本当に遅いです。
  • 浮動小数点演算はnot整数演算よりも一般に遅くなります(「できること」ではなく、「スキップできること」)
  • 分岐は not free です。単純な算術(除算またはモジュロ以外のもの)を使用して回避できる場合、パフォーマンスが向上することがあります。ブランチをループの外側に移動することは、ほとんどの場合良いアイデアです。
9
harold

OK、私のお気に入りをスローする必要があります。タスクが人間の対話に十分な長さである場合は、デバッガーで手動ブレークを使用します。

対プロファイラー、これはあなたに何が起こっているのかを本当に理解するために使用できるコールスタックと変数値を提供します。

これを10〜20回行うと、どの最適化が実際に違いを生む可能性があるかがわかります。

9
Conrad Albrecht

人々は実際に重要なことについて面白いアイデアを持っています。 Stack Overflowには、たとえば++iより多くの「パフォーマンス」i++実際のパフォーマンスチューニングの例 であり、基本的にどの言語でも同じ手順です。コードが単に「高速だから」という特定の方法で書かれている場合、それは推測です。

もちろん、あなたは意図的に愚かなコードを書くことはしませんが、推測がうまくいけば、プロファイラーやプロファイリング技術は必要ありません。

8
Mike Dunlavey

真実は、完全に最適化されたコードなどはありません。ただし、既知のCPUタイプ(およびカウント)の既知のシステム(またはシステムのセット)で、特定の部分のコードに対して最適化できます。 、既知のプラットフォーム(Microsoft? Mono ?)、既知のフレームワーク/ [〜#〜] bcl [〜#〜] バージョン、既知のCLIバージョン、既知のコンパイラバージョン(バグ、仕様変更、調整)、既知の合計および利用可能なメモリ量、既知のアセンブリの起源( [〜#〜] gac [〜#〜] ?disk?remote?)他のプロセスからの既知のバックグラウンドシステムアクティビティ。

現実の世界では、プロファイラーを使用して、重要な部分を見てください。通常、明らかなものはI/Oを含むもの、スレッド化を含むもの(これもバージョン間で大きく変化します)、ループとルックアップを含むものですが、「明らかに悪い」コードが実際問題ではないことに驚くかもしれません。そして、「明らかに良い」コードとは、大きな犯人です。

6
Marc Gravell

コンパイラにwhatを行うよう指示し、howを行うように指示しないでください。例として、foreach (var item in list)for (int i = 0; i < list.Count; i++)よりも優れており、m = list.Max(i => i.value);list.Sort(i => i.value); m = list[list.Count - 1];よりも優れています。

何をしたいのかをシステムに伝えることにより、それを行うための最良の方法を見つけることができます。 LINQは、必要になるまで結果が計算されないため、優れています。最初の結果のみを使用する場合、残りを計算する必要はありません。

最終的に(そしてこれはすべてのプログラミングに当てはまります)、ループを最小化し、ループで行うことを最小化します。さらに重要なのは、ループ内のループの数を最小限にすることです。 O(n)アルゴリズムとO(n ^ 2)アルゴリズムの違いは何ですか?O(n ^ 2)アルゴリズムにはループの内側にループがあります。

5
Gabe

私は実際にコードを最適化しようとはしていませんが、時にはリフレクターのようなものを使ってプログラムをソースに戻します。それから、私が間違っていることと、リフレクターが出力することを比較することは興味深いです。時々、もっと複雑な形でやったことが単純化されたことがわかります。物事を最適化することはできませんが、問題に対するより簡単な解決策を見つけるのに役立ちます。

2
Aaron Havens