プログラムの最適化

最適化は、いくつかのレベルで行われます。 一般に、高いレベルは影響が大きく、プロジェクトの後半では変更が難しく、変更する必要がある場合は大幅な変更または完全な書き換えが必要になります。 そのため、最適化は通常、高いレベルから低いレベルへと洗練されていき、初期に得られる利益は大きく、より少ない作業で達成され、後に得られる利益は小さく、より多くの作業を必要とします。 しかし、場合によっては、全体的な性能はプログラムの非常に低レベルな部分の性能に依存し、後期段階での小さな変更や低レベルな詳細の早期考慮が、大きな影響を与えることがあります。 一般的に、プロジェクト全体を通して効率性を考慮することはありますが、大きな最適化は、もし行われるとしても、遅い段階で行うべき改良とみなされることが多いようです。 長期にわたるプロジェクトでは、通常、最適化のサイクルがあり、ある分野を改善すると別の分野での限界が明らかになることがあります。

設計レベル編集

最高のレベルでは、目標、制約、および予想される使用/負荷を考慮して、利用可能なリソースを最大限に活用するために設計を最適化することができます。 システムのアーキテクチャ設計は、その性能に圧倒的に影響する。 たとえば、ネットワーク遅延に依存するシステム(ネットワーク遅延が全体的な性能の主な制約となる)は、ネットワークトリップを最小化するように最適化され、理想的には、複数のラウンドトリップではなく単一のリクエスト(またはプッシュプロトコルのようにリクエストなし)を行うことになるだろう。 コンパイラを設計する場合、高速コンパイルが重要な優先事項であれば、マルチパスコンパイラよりもワンパスコンパイラの方が速い(同じ作業を想定)。しかし、出力コードの速度が目標であれば、遅いマルチパスコンパイラの方が、時間自体はかかるが、目標をより達成する。 プラットフォームやプログラミング言語の選択はこのレベルで行われ、頻繁に変更すると完全な書き換えが必要になりますが、モジュラーシステムでは一部のコンポーネントだけを書き換えることも可能です(例えば、Pythonプログラムはパフォーマンスが重要な部分をC言語で書き換えることができます)。

Algorithms and data structuresEdit

Given a overall design, the good choice of efficient algorithms and data structures, and the efficient implementation of these algorithms and data structures comes next.全体設計を行った後、効率的なアルゴリズムとデータ構造を選択し、これらのアルゴリズムとデータ構造を効率的に実装することが必要です。 設計の後、アルゴリズムとデータ構造の選択は、プログラムの他のどの側面よりも効率に影響を与える。 一般にデータ構造は、データ構造の仮定とその性能の仮定がプログラム全体で使用されるため、アルゴリズムよりも変更が困難ですが、関数定義で抽象データ型を使用し、具体的なデータ構造の定義をいくつかの場所に限定しておくことでこれを最小限に抑えることができます。

アルゴリズムについて、これは主にアルゴリズムが入力において定数 O(1), 対数 O(log n), 線形 O(n), または場合によっては対数線形 O(n log n) (空間と時間の両方) であることを確実にすることから成ります。 二次的な複雑さ O(n2) を持つアルゴリズムはスケールせず、線形アルゴリズムでさえ繰り返し呼び出されると問題を起こし、可能であれば通常定数または対数に置き換えられます。

成長の漸近順位を超えて、定数因子は重要です:漸近的に遅いアルゴリズムは、それらが両方とも小さな入力に直面したとき、漸近的に速いアルゴリズムよりも速いか小さい(より単純なので)かもしれませんが、それは現実に発生した場合があることです。 このトレードオフはサイズによって変化するため、多くの場合、ハイブリッド アルゴリズムが最高のパフォーマンスを提供します。

パフォーマンスを向上させる一般的なテクニックは、作業を回避することです。 良い例は、共通のケースに対する高速パスの使用で、不要な作業を回避することによりパフォーマンスを向上させます。 たとえば、ラテン語のテキストには単純なテキスト レイアウト アルゴリズムを使用し、デーヴァナーガリーなどの複雑なスクリプトの場合のみ複雑なレイアウト アルゴリズムに切り替えます。 また、キャッシュ、特にメモ化も重要な技術で、冗長な計算を避けることができます。 キャッシュの重要性から、しばしばシステム内に多くのレベルのキャッシュが存在し、メモリ使用による問題や、古いキャッシュによる正しさの問題を引き起こすことがある。

Source code levelEdit

一般アルゴリズムとその抽象機械上での実装を超えて、ソースコードレベルの選択は大きな違いを生むことがある。 例えば、初期の C コンパイラでは、while(1)for(;;) よりも無条件ループが遅かったのですが、これは while(1) が 1 を評価し、それが真かどうかをテストする条件ジャンプを持つのに対し、 for (;;) は無条件ジャンプを持つためでした。 このような最適化は、現在では最適化コンパイラによって実行されることがあります。 これは、ソース言語、ターゲット機械語、コンパイラに依存し、理解や予測が難しく、また時間とともに変化します。コンパイラと機械語を理解することで性能を向上させることができる重要な場所です。 ループ・インヴァリアント・コード・モーションや戻り値の最適化は、補助変数の必要性を減らす最適化の一例であり、回りくどい最適化を避けることで、より速いパフォーマンスを実現することも可能です。

ビルド レベル編集

ソース レベルとコンパイル レベルの間で、ディレクティブとビルド フラグを使用して、ソース コードとコンパイラの性能オプションをそれぞれ調整できます。たとえば、プリプロセッサ定義を使用して不要なソフトウェア機能を無効にする、特定のプロセッサ モデルまたはハードウェア機能に対して最適化する、または分岐を予測するなどの機能があります。 BSD の Ports や Gentoo の Portage のようなソースベースのソフトウェア配布システムは、この形式の最適化を利用できます。

Compile levelEdit

最適化コンパイラの使用は、少なくともコンパイラが予測できる範囲で実行可能プログラムが最適化されるようになる傾向があります。

アセンブリレベル編集

最も低いレベルでは、特定のハードウェアプラットフォーム用に設計されたアセンブリ言語を使用してコードを書くと、プログラマがマシン命令のレパートリーをすべて利用する場合に、最も効率的でコンパクトなコードを生成することができます。 組込みシステムで使われる多くのオペレーティングシステムは、伝統的にこのような理由からアセンブラコードで書かれてきました。 非常に小さなプログラムを除いては、時間とコストの関係で、最初から最後までアセンブリで書かれることはほとんどありません。 ほとんどは、高級言語からアセンブリにコンパイルし、そこから手作業で最適化する。 7031>

より近代的な最適化コンパイラーと最近の CPU の複雑化により、コンパイラーが生成するものより効率的なコードを書くことは難しくなり、この「究極の」最適化ステップを必要とするプロジェクトはほとんどありません。 その結果、プログラマやコンパイラは、新しい CPU が提供する効率的な命令や古いモデルの癖を常に利用できるとは限りません。 また、あるプロセッサ用にチューニングされたアセンブリコードが、そのような命令を使用せずに、別のプロセッサで別のチューニングを期待すると、やはり最適でない可能性があります。

Typically today rather than writing in assembly language, programmer will use a disassembler to analyze the output of a compiler and change the high-level source code so it can be compiled more efficiently, or understand why it is inefficient.

Run timeEdit

Just-in-time compilers can produce customized machine code based on run-time data, at cost of compilation overhead.Just-in-time コンパイラは、コンパイラのオーバーヘッドの代償として、ランタイムデータに基づいてカスタマイズされたマシンコードを生成します。 この技術は初期の正規表現エンジンに始まり、Java HotSpot や V8 for JavaScript で広く使われるようになりました。 7031>

Profile-guided optimization は、実行時プロファイルに基づく AOT (ahead-of-time) コンパイル最適化手法であり、動的手法である適応最適化の静的「平均ケース」アナログに類似している。

自己修正コードは、コードを最適化するために、実行時の条件に応じて自分自身を変更できます。 例としては、Out-of-order 実行、投機的実行、命令パイプライン、および分岐予測器などがあります。

プラットフォーム依存および独立した最適化編集

コード最適化は、プラットフォーム依存およびプラットフォーム非依存の技術として大まかに分類することも可能です。 後者がほとんどの、あるいはすべてのプラットフォームで有効なのに対し、プラットフォーム依存の手法は、あるプラットフォームの特定の特性を利用したり、単一のプラットフォーム、あるいは単一のプロセッサに依存するパラメータに依存したりします。 そのため、同じコードを異なるプロセッサ用に書き換えたり、異なるバージョンを作成したりすることが必要になる場合があります。 例えば、コンパイルレベルの最適化の場合、プラットフォームに依存しない手法は、ほとんどのCPUアーキテクチャに同様の影響を与える汎用的な手法(ループアンローリング、関数呼び出しの削減、メモリ効率の良いルーチン、条件の削減など)です。 プラットフォームに依存しない最適化の好例はinner for loopで示されており、inner for loopを持つループは、持たないループやinner while loopを持つループよりも単位時間あたりの計算量が多いことが観察されています。 一般に、これらはプログラムを完成させるのに必要な総命令経路長を短くしたり、処理中の総メモリ使用量を減らしたりするのに役立つ。 一方、プラットフォーム依存の技術は、命令スケジューリング、命令レベルの並列処理、データレベルの並列処理、キャッシュ最適化技術(すなわち、各種プラットフォーム間で異なるパラメータ)を含み、同じアーキテクチャのプロセッサであっても最適な命令スケジューリングは異なる可能性がある