コンピュータ科学において、プログラム最適化、コード最適化、またはソフトウェア最適化とは、ソフトウェアシステムの何らかの側面をより効率的に動作させたり、より少ないリソースを使用させたりするために、ソフトウェアシステムを変更するプロセスです。[ 1 ]一般的に、コンピュータプログラムは、より高速に実行できるように、またはより少ないメモリストレージやその他のリソースで動作できるように、またはより少ない電力で動作できるように最適化できます。
「最適化」という用語は「optimum」に由来しますが、[ 2 ]実際に真に最適なシステムを実現することは稀であり、これは超最適化と呼ばれます。[ 3 ]最適化は通常、システムを普遍的に最適にするのではなく、特定の品質指標に関してシステムを改善することに焦点を当てます。これはしばしばトレードオフにつながり、ある指標を向上させると、別の指標が犠牲になる場合があります。よく引用される例の 1 つは空間と時間のトレードオフで、プログラムの実行時間を短縮するとメモリ消費量が増加する可能性があります。逆に、メモリが限られているシナリオでは、エンジニアはスペースを節約するために、より遅いアルゴリズムを優先する場合があります。すべての状況で優れた単一の設計はめったにないため、プログラマは目の前のアプリケーションに最も関連のある属性を優先する必要があります。ソフトウェアの指標には、スループット、レイテンシ、揮発性メモリ使用量、永続ストレージ、インターネット使用量、エネルギー消費、ハードウェアの摩耗などがあります。最も一般的な指標は速度です。
さらに、絶対的な最適化を達成するには、得られるメリットに比べて不釣り合いなほどの労力が必要となる場合が多い。そのため、十分な改善が達成されると、最適化プロセスは通常、減速する。幸いなことに、最適化プロセスの初期段階で大きな成果が得られることが多いため、収穫逓減の法則に達する前に停止することが現実的である。
最適化は複数のレベルで実施できます。一般的に、上位レベルほど影響が大きく、プロジェクトの後半で変更するのが難しく、変更が必要な場合は大幅な変更または完全な書き直しが必要になります。そのため、最適化は通常、上位レベルから下位レベルへと段階的に進めることができ、初期段階ではより大きな効果が得られ、作業量も少なくて済みますが、後期段階では効果は小さくなり、より多くの作業が必要になります。ただし、場合によっては、全体的なパフォーマンスはプログラムの非常に低レベルな部分のパフォーマンスに依存しており、後期段階での小さな変更や、低レベルの詳細を早期に検討することで、大きな効果が得られることがあります。通常、プロジェクト全体を通して効率性は考慮されますが(ただし、その程度は大きく異なります)、大規模な最適化は、実施するとしても、多くの場合、後期段階で行うべき改良作業とみなされます。長期にわたるプロジェクトでは、通常、最適化のサイクルがあり、ある領域を改善すると別の領域の限界が明らかになり、パフォーマンスが許容範囲内になったり、効果が小さすぎたり、コストがかかりすぎたりすると、これらのサイクルは打ち切られます。
パフォーマンスはプログラムの仕様の一部であるため、使い物にならないほど遅いプログラムは目的に適していません。60 Hz (フレーム/秒) のビデオゲームは許容範囲内ですが、6 フレーム/秒ではカクカクしすぎて許容できません。そのため、システムが十分なパフォーマンスを発揮できるように、パフォーマンスは最初から考慮する必要があります。最終システムが (最適化によって) 許容できるパフォーマンスを達成できるという確信を持つためには、初期のプロトタイプがおおよそ許容できるパフォーマンスを備えている必要があります。最適化はいつでも後でできるという考えから、この点が省略されることがありますが、その結果、プロトタイプシステムがはるかに遅くなり(多くの場合、桁違いに遅い) 、最終的にアーキテクチャ的にパフォーマンス目標を達成できないために失敗に終わるシステム ( Intel 432 (1981)など) や、許容できるパフォーマンスを達成するまでに何年もかかるシステム (Java (1995) など、HotSpot (1999) でようやく許容できるパフォーマンスを達成) が生じます。プロトタイプと製品システムの間でパフォーマンスがどの程度変化するか、また最適化にどれだけ適しているかは、不確実性とリスクの大きな原因となる可能性があります。
最高レベルでは、設計は、目標、制約、および想定される使用/負荷を考慮して、利用可能なリソースを最大限に活用するように最適化される可能性があります。システムのアーキテクチャ設計は、そのパフォーマンスに圧倒的な影響を与えます。たとえば、ネットワーク遅延が制約となるシステム(ネットワーク遅延が全体的なパフォーマンスの主な制約となるシステム)は、ネットワークの往復回数を最小限に抑えるように最適化され、理想的には、複数回の往復ではなく、単一のリクエスト(またはプッシュプロトコルのようにリクエストなし)を行うことになります。設計の選択は目標によって異なります。コンパイラを設計する場合、高速コンパイルが最優先事項であれば、1パスコンパイラはマルチパスコンパイラよりも高速です(同じ作業を想定した場合)。しかし、出力コードの速度が目標であれば、マルチパスコンパイラの方が時間がかかりますが、その目標はよりよく達成されます。プラットフォームとプログラミング言語の選択はこのレベルで行われ、頻繁に変更する場合は完全な書き直しが必要になりますが、モジュール式のシステムであれば一部のコンポーネントのみを書き直すことが可能です。たとえば、Python プログラムの場合、パフォーマンスが重要な部分を C 言語で書き直すことができます。分散システムでは、アーキテクチャ(クライアント/サーバー、ピアツーピアなど)の選択は設計レベルで行われ、特にすべてのコンポーネントを同期して置き換えることができない場合(たとえば、古いクライアントなど)は、変更が困難になる場合があります。
全体的な設計が整えば、効率的なアルゴリズムとデータ構造を適切に選択し、これらのアルゴリズムとデータ構造を効率的に実装することが次に重要になります。設計後、アルゴリズムとデータ構造の選択は、プログラムの他のどの側面よりも効率に影響を与えます。一般的に、データ構造はアルゴリズムよりも変更が困難です。これは、データ構造の仮定とそのパフォーマンスに関する仮定がプログラム全体で使用されているためです。ただし、関数定義で抽象データ型を使用し、具体的なデータ構造の定義を少数の箇所に限定することで、この困難さを最小限に抑えることができます。データベースにマッピングされたデータ構造の変更には、スキーマの移行やその他の複雑なソフトウェアまたはインフラストラクチャの変更が必要になる場合があります。[ 4 ]
アルゴリズムに関しては、主に、アルゴリズムが入力(空間と時間の両方)に対して定数O(1)、対数O(log n )、線形O( n )、または場合によっては対数線形O( n log n )となるようにすることが求められます。2次複雑度O( n 2 )のアルゴリズムはスケーリングに適さず、線形アルゴリズムでさえ繰り返し呼び出されると問題が発生するため、可能であれば定数または対数アルゴリズムに置き換えられるのが一般的です。
漸近的な成長順序を超えて、定数係数が重要になります。漸近的に遅いアルゴリズムは、漸近的に速いアルゴリズムよりも、入力が小さい場合(現実にはこのようなケースが起こり得ます)、高速であったり、より小型であったり(より単純であるため)になる可能性があります。多くの場合、このトレードオフはサイズによって変化するため、ハイブリッドアルゴリズムが最良のパフォーマンスを発揮します。
パフォーマンスを向上させる一般的な手法は、処理を回避することです。良い例としては、一般的なケースでは高速パスを使用し、不要な処理を回避することでパフォーマンスを向上させることが挙げられます。例えば、ラテン文字にはシンプルなテキストレイアウトアルゴリズムを使用し、デーヴァナーガリー文字のような複雑な文字体系の場合にのみ複雑なレイアウトアルゴリズムに切り替えるといった方法です。もう一つの重要な手法は、キャッシュ、特にメモ化です。これは冗長な計算を回避するものです。キャッシュの重要性から、システムにはしばしば複数のレベルのキャッシュが存在しますが、これはメモリ使用量の問題や、古いキャッシュによる正確性の問題を引き起こす可能性があります。
一般的なアルゴリズムや抽象マシン上での実装を超えて、具体的なソースコードレベルの選択が大きな違いを生むことがあります。たとえば、初期のCコンパイラでは、は無条件ループよりも遅くなりました。これは、が評価されてから条件付きジャンプがあり、それが真かどうかをテストするのに対し、は無条件ジャンプがあったためです。このような最適化は、現在では最適化コンパイラによって実行できます。これはソース言語、ターゲットマシン言語、コンパイラに依存し、理解や予測が難しく、時間とともに変化する可能性があります。これは、コンパイラとマシンコードの理解がパフォーマンスを向上させる鍵となる部分です。ループ不変コード移動と戻り値最適化は、補助変数の必要性を減らし、回りくどい最適化を回避することでパフォーマンスを向上させる最適化の例です。while(true)for(;;)while(true)truefor(;;)
ソースコードレベルとコンパイルレベルの間では、ディレクティブとビルドフラグを使用して、それぞれソースコードとコンパイラのパフォーマンスオプションを調整できます。例えば、プリプロセッサ定義を使用して不要なソフトウェア機能を無効にしたり、特定のプロセッサモデルやハードウェア機能に合わせて最適化したり、分岐を予測したりすることができます。BSDのPortsやGentooのPortageといったソースベースのソフトウェア配布システムは、この種の最適化を活用できます。
最適化機能を有効にした最適化コンパイラを使用すると、実行可能プログラムが少なくともコンパイラが合理的に実行できる範囲で最適化されることが保証されます。詳細については、「最適化コンパイラ」を参照してください。
最も基本的なレベルでは、特定のハードウェアプラットフォーム向けに設計されたアセンブリ言語を使用してコードを記述することで、プログラマがマシン命令の全レパートリーを活用すれば、最も効率的でコンパクトなコードを作成できます。組み込みシステムで使用される多くのオペレーティングシステムは、従来、この理由からアセンブリ言語で記述されてきました。時間とコストがかかるため、(非常に小さなプログラムを除いて)プログラムが最初から最後までアセンブリ言語で記述されることはほとんどありません。ほとんどのプログラムは、高水準言語からアセンブリ言語にコンパイルされ、そこから手動で最適化されます。効率とサイズがそれほど重要でない場合は、大きな部分を高水準言語で記述することもあります。
より高度な最適化コンパイラと近年のCPUの複雑化に伴い、コンパイラが生成するコードよりも効率的なコードを書くことは難しくなっており、この「究極の」最適化ステップを必要とするプロジェクトはほとんどない。
今日書かれているコードの多くは、できるだけ多くのマシンで動作することを想定して書かれています。そのため、プログラマーやコンパイラーは、新しいCPUが提供するより効率的な命令や、古いモデルの特異性を必ずしも活用しているとは限りません。さらに、そのような命令を使用せずに特定のプロセッサ向けに最適化されたアセンブリコードは、別のプロセッサでは最適とは言えず、コードの異なるチューニングが必要になる場合があります。
今日では、プログラマーはアセンブリ言語で記述する代わりに、逆アセンブラを使用してコンパイラの出力を分析し、より効率的にコンパイルできるように、あるいは非効率な理由を理解するために、高レベルのソースコードを変更するのが一般的です。
ジャストインタイムコンパイラは、コンパイルオーバーヘッドが発生するものの、実行時データに基づいてカスタマイズされたマシンコードを生成できます。この技術は初期の正規表現エンジンにまで遡り、Java HotSpotやJavaScriptのV8で広く普及しました。場合によっては、適応型最適化によって、実際の入力やその他の要因に応じてパラメータを動的に調整することで、静的コンパイラの能力を超える実行時最適化を実現できることがあります。
プロファイル誘導型最適化は、実行時プロファイルに基づく事前コンパイル(AOT)最適化手法であり、適応型最適化という動的手法の静的な「平均ケース」版に似ています。
自己修正コードは、実行時の状況に応じて自身を変化させることでコードを最適化できる。これはアセンブリ言語プログラムでより一般的だった。
CPU設計によっては、実行時に最適化を実行できるものがあります。例えば、アウトオブオーダー実行、投機的実行、命令パイプライン、分岐予測などが挙げられます。コンパイラは、命令スケジューリングなどを通じて、プログラムがこれらのCPU機能を活用できるように支援します。
コード最適化は、プラットフォーム依存の手法とプラットフォーム非依存の手法に大別することもできます。後者はほとんどまたはすべてのプラットフォームで有効ですが、プラットフォーム依存の手法は、特定のプラットフォームの特性を利用したり、単一のプラットフォームまたは単一のプロセッサに依存するパラメータに依存したりします。そのため、異なるプロセッサ用に同じコードの異なるバージョンを作成または生成する必要が生じる場合があります。たとえば、コンパイルレベルの最適化の場合、プラットフォーム非依存の手法は、ほとんどのCPUアーキテクチャに同様の影響を与える汎用的な手法(ループ展開、関数呼び出しの削減、メモリ効率の良いルーチン、条件の削減など)です。プラットフォーム非依存の最適化の優れた例として、内部forループが挙げられます。内部forループを持つループは、内部forループを持たないループや内部whileループを持つループよりも単位時間あたりの計算量が多いことが観察されています。[ 5 ]一般的に、これらはプログラムを完了するために必要な命令パスの総長を削減したり、処理中のメモリ使用量を削減したりするのに役立ちます。一方、プラットフォーム依存の手法には、命令スケジューリング、命令レベル並列性、データレベル並列性、キャッシュ最適化手法(つまり、さまざまなプラットフォーム間で異なるパラメータ)が含まれ、最適な命令スケジューリングは、同じアーキテクチャの異なるプロセッサ間でも異なる可能性があります。
計算タスクは、効率の異なるいくつかの方法で実行できます。同等の機能を持つより効率的なバージョンは、強度削減と呼ばれます。たとえば、 1からNまでのすべての整数の合計を取得することを目的とした次のCコードスニペットを考えてみましょう。
int sum = 0 ; for ( int i = 1 ; i <= N ; ++ i ) { sum += i ; } printf ( "sum: %d \n " , sum );このコードは(算術オーバーフローが発生しないと仮定すると)次のような数式を使って書き換えることができます。
int sum = N * ( 1 + N ) / 2 ; printf ( "合計: %d \n " , sum );最適化とは、同じ機能を維持しながら、より計算効率の高い方法(アルゴリズム)を選択することです。最適化コンパイラによって自動的に実行される場合もあります。これらの手法については、 「アルゴリズム効率」の項を参照してください。ただし、不要な機能を削除することで、パフォーマンスを大幅に向上させることも多くの場合可能です。
最適化は必ずしも明白で直感的なプロセスではありません。上記の例では、Nが十分に小さく、特定のハードウェアが乗算や除算よりも加算やループ演算をはるかに高速に実行できる場合、「最適化された」バージョンが実際には元のバージョンよりも遅くなる可能性があります。
しかし、場合によっては、最適化はより高度なアルゴリズムの使用、特殊なケースや特別な「テクニック」の活用、複雑なトレードオフの実行に依存します。「完全に最適化された」プログラムは理解しにくくなる可能性があり、そのため最適化されていないバージョンよりも多くの欠陥を含む可能性があります。明らかなアンチパターンを排除する以外にも、コードレベルの最適化の中には保守性を低下させるものもあります。
最適化は通常、実行時間、メモリ使用量、ディスク容量、帯域幅、消費電力、その他のリソースなど、パフォーマンスの1つか2つの側面を改善することに重点を置きます。これは通常、トレードオフを必要とします。つまり、ある要素を最適化すると、他の要素が犠牲になるということです。たとえば、キャッシュのサイズを大きくすると実行時パフォーマンスは向上しますが、メモリ消費量も増加します。その他によくあるトレードオフとしては、コードの明瞭さと簡潔さが挙げられます。
最適化を行うプログラマーは、一部の操作の効率を向上させるために、他の操作の効率を低下させるという選択を迫られる場合があります。こうしたトレードオフは、技術的な側面だけでなく、競合他社が発表したベンチマーク結果を上回って商業的な成功を収めようとする場合など、非技術的な側面から生じることもあります。しかし、そのためには、ソフトウェアの通常の利用効率を低下させるという負担が伴う可能性があります。このような変更は、冗談めかして「ペシミゼーション(性能低下)」と呼ばれることもあります。
最適化には、システム内のボトルネック、つまりパフォーマンスを制限する要因となるコンポーネントを見つけることが含まれます。コードの観点から言えば、これは多くの場合、ホットスポット、つまり必要なリソースを最も多く消費するコードの重要な部分になりますが、 I/Oレイテンシやネットワーク帯域幅など、他の要因である場合もあります。
コンピュータサイエンスでは、リソース消費はしばしばべき乗則分布に従うため、リソースの 80% が通常 20% の操作によって使用されることを観察することで、パレートの法則をリソース最適化に適用できます。 [ 6 ]ソフトウェアエンジニアリングでは、コンピュータプログラムの実行時間の 90% がコードの 10% の実行に費やされるという近似の方が適切な場合が多いです (この文脈では 90/10 の法則として知られています)。
より複雑なアルゴリズムとデータ構造は多数のアイテムに対して優れたパフォーマンスを発揮する一方、単純なアルゴリズムは少量のデータに適しています。より複雑なアルゴリズムのセットアップ、初期化時間、定数係数は利点を上回る可能性があり、そのためハイブリッドアルゴリズムまたは適応型アルゴリズムは単一のアルゴリズムよりも高速になる可能性があります。パフォーマンスプロファイラを使用すると、どの機能がどの条件に適しているかについての決定を絞り込むことができます。[ 7 ]
したがって、パフォーマンス プロファイリングは、ボトルネックの検出だけでなく、最適化のガイダンスのためのさまざまな方法を提供します。経験的アルゴリズムとは、開発者が理解して人間が計画する最適化につながる可能性のあるアルゴリズムの動作を研究するために、通常はパフォーマンス プロファイリングなどの経験的方法を使用する実践です。 プロファイル ガイド付き最適化とは、最適化コンパイラまたはインタプリタへの入力としてプロファイリング データを機械が使用することです。一部のプログラミング言語は、プロファイル ガイド付き最適化のためのツールに関連付けられています。[ 8 ]一部のパフォーマンス プロファイリング手法は、キャッシュの使用率に基づく強化を重視しています。[ 9 ]パフォーマンス プロファイリングのその他の利点には、リソース管理の改善やユーザー エクスペリエンスの向上などがあります。
場合によっては、メモリを増やすことでプログラムの実行速度を向上させることができます。例えば、フィルタリングプログラムは通常、各行を読み込み、その行をすぐにフィルタリングして出力します。これは1行分のメモリしか使用しませんが、ディスク読み込みの遅延により、パフォーマンスは通常低下します。結果をキャッシュすることも同様に効果的ですが、より多くのメモリが必要になります。
一般的に、最適化とは、全体的に最適なアルゴリズムとデータ構造を選択することです。[ 10 ]多くの場合、アルゴリズムの改善は、数パーセント以上のパフォーマンス向上をもたらすマイクロ最適化とは異なり、数桁のパフォーマンス向上をもたらします。[ 11 ]開発サイクルの終わりまで最適化を待つと、アルゴリズムの変更に大幅な書き換えが必要になる可能性があります。
マイクロ最適化は、しばしば可読性を低下させ、プログラムやシステムを複雑化させる可能性があります。その結果、プログラムの保守やデバッグがより困難になることがあります。
ドナルド・クヌースは最適化に関して以下の2つの発言をした。
「私たちは、例えば97%の時間、小さな効率性については忘れるべきです。時期尚早な最適化はあらゆる悪の根源です。しかし、重要な3%の機会を逃してはなりません」[ 12 ]
(彼は数年後、この引用をトニー・ホーアのものとしているが[ 13 ] 、ホーア自身はこのフレーズを考案したことを否定しているため、これは誤りかもしれない[ 14 ])
「確立された工学分野では、容易に達成できる12%の改善は決して些細なものとは見なされず、ソフトウェア工学においても同じ見解が主流となるべきだと私は信じています」[ 12 ]
「時期尚早な最適化」は、あらゆる状況、あらゆる目的におけるあらゆる最適化に対する反対のスローガンとしてよく使われる。[ 15 ] [ 16 ] [ 17 ] [ 18 ]クリーンコードは、よりシンプルで効率的なコードよりも複雑なコードを生み出すことが多い。[ 19 ]
最適化対象を決定する際には、パフォーマンス分析を行わずにコードを見ただけでは必ずしも明らかではない、特定の部分に費やされた実際の時間に基づいて、アムダールの法則を使用して各部分の優先順位付けを行うべきです。
実際には、ソフトウェアを設計する際には、パフォーマンス目標を念頭に置くことがしばしば必要となるが、プログラマーは様々なトレードオフのバランスを取らなければならない。開発コストは高額であり、ハードウェアの性能は向上している。
現代のコンパイラは非常に効率的であるため、意図したパフォーマンス向上が実現しない場合もあります。コンパイラは多くの自動最適化を実行するため、一部の最適化では実行ファイルが同一になることがあります。また、ハードウェアによってマイクロ最適化の効果が低下する場合もあります。例えば、ハードウェアがソフトウェアレベルでキャッシュされたデータをキャッシュすることがあります。
マクロを用いたコード開発時の最適化は、プログラミング言語によって異なる形態をとる。
CやC++などの一部の手続き型言語では、マクロはトークン置換を用いて実装されます。現在では、多くの場合、型安全な代替手段としてインライン関数を使用できます。どちらの場合も、インライン化された関数本体は、コンパイラによるコンパイル時最適化(定数畳み込みなど)を受けることができ、これにより一部の計算がコンパイル時に実行される可能性があります。
多くの関数型プログラミング言語では、マクロは構文解析木/抽象構文木の構文解析時置換を用いて実装されており、これによりマクロの安全性が向上するとされている。多くの場合、解釈が用いられるため、これはそのような計算が構文解析時のみに実行されることを保証する一つの方法であり、場合によっては唯一の方法でもある。
Lispはこのスタイルのマクロを生み出し、このようなマクロはしばしば「Lispライクマクロ」と呼ばれます。C ++ではテンプレートメタプログラミングを用いることで同様の効果が得られます。
どちらの場合も、処理はコンパイル時に行われます。Cマクロと、LispライクなマクロやC++テンプレートメタプログラミングとの違いは、後者のツールはコンパイル時/解析時に任意の計算を実行できるのに対し、Cマクロの展開では計算は実行されず、最適化ツールの能力に依存している点です。さらに、Cマクロは再帰や反復を直接サポートしていないため、チューリング完全ではありません。
しかし、あらゆる最適化と同様に、プロジェクトが完了する前に、こうしたツールがどこで最も効果を発揮するかを予測することはしばしば困難です。
関連項目:カテゴリ:コンパイラ最適化
システム全体の最適化は、自動最適化ツールでは複雑すぎるため、通常はプログラマーが行います。この場合、プログラマーまたはシステム管理者がコードを明示的に変更して、システム全体のパフォーマンスを向上させます。効率は向上しますが、自動最適化よりもはるかにコストがかかります。多くのパラメータがプログラムのパフォーマンスに影響を与えるため、プログラム最適化空間は広大です。メタヒューリスティクスと機械学習は、プログラム最適化の複雑さに対処するために使用されます。[ 20 ]
プロファイラ(またはパフォーマンスアナライザー)を使用して、プログラムの中で最も多くのリソースを消費している部分、つまりボトルネックを特定します。プログラマーはボトルネックがどこにあるかを明確に把握していると思い込んでいることがありますが、直感は往々にして間違っています。重要でないコードを最適化しても、全体的なパフォーマンスの向上にはほとんど役立ちません。
ボトルネックが特定された場合、最適化は通常、プログラムで使用されているアルゴリズムの見直しから始まります。多くの場合、特定のアルゴリズムを特定の問題に合わせてカスタマイズすることで、汎用アルゴリズムよりも優れたパフォーマンスを実現できます。例えば、膨大な数のアイテムをソートするタスクは、通常、最も効率的な汎用アルゴリズムの1つであるクイックソートルーチンを使用して行われます。しかし、アイテムの何らかの特性(例えば、既に特定の順序で並んでいるなど)を活用できる場合は、別の方法、あるいはカスタムメイドのソートルーチンを使用することもできます。
プログラマーが最適なアルゴリズムを選択したと確信したら、コードの最適化を開始できます。ループを展開する(ループのオーバーヘッドを低減するためですが、CPUキャッシュに過負荷がかかると速度が低下する場合が多い)、可能な限り小さいデータ型を使用する、浮動小数点演算の代わりに整数演算を使用するなど、様々な最適化手法が考えられます。(これらの手法やその他の手法については、アルゴリズムの効率性に関する記事を参照してください。)
パフォーマンスのボトルネックは、プログラムで使用されているアルゴリズムやデータ構造ではなく、言語の制限に起因する場合があります。場合によっては、プログラムの重要な部分を、基盤となるマシンに直接アクセスできる別のプログラミング言語で書き直すことができます。たとえば、Pythonのような非常に高水準な言語では、速度向上のためにC言語で書かれたモジュールを持つことが一般的です。C言語で既に書かれているプログラムには、アセンブリ言語で書かれたモジュールを追加できます。D言語で書かれたプログラムでは、インラインアセンブラを使用できます。
このような状況では、コードの一部を書き直すことが「報われる」のは、90/10の法則として知られる一般的な経験則があるからです。この法則によれば、時間の90%はコードの10%に費やされ、残りの90%にはわずか10%しか費やされていません。したがって、適切な部分を見つけることができれば、プログラムのごく一部を最適化するために知的な努力を注ぐだけで、全体の速度に大きな影響を与えることができるのです。
手動による最適化は、時に可読性を損なうという副作用をもたらすことがあります。そのため、コードの最適化は(できればインラインコメントを用いて)丁寧に文書化し、今後の開発への影響を評価する必要があります。
自動最適化を実行するプログラムはオプティマイザと呼ばれます。ほとんどのオプティマイザはコンパイラに組み込まれており、コンパイル中に動作します。オプティマイザは、生成されるコードを特定のプロセッサに合わせて調整できる場合が多くあります。
現在、自動最適化はほぼコンパイラ最適化に限られています。しかし、コンパイラ最適化は通常、比較的汎用的な最適化の固定セットに限定されているため、問題や言語固有の最適化記述を受け入れ、エンジニアが独自の最適化を指定できるオプティマイザに対する需要が非常に高まっています。最適化記述を受け入れるツールはプログラム変換システムと呼ばれ、C++などの実際のソフトウェアシステムに適用され始めています。
一部の高級言語(Eiffel、Esterel )は、中間言語を使用することでプログラムを最適化します。
グリッドコンピューティング、あるいは分散コンピューティングは、使用率の高いコンピュータからアイドル状態のコンピュータへとタスクを移動させることで、システム全体の最適化を目指す。
場合によっては、最適化作業自体にかかる時間自体が問題となることもある。
既存コードの最適化は通常、新機能を追加するものではなく、さらに悪いことに、これまで正常に動作していたコードに新たなバグを発生させる可能性もあります(あらゆる変更がそうであるように)。手動で最適化されたコードは、最適化されていないコードよりも「可読性」が低下する場合があるため、最適化は保守性にも影響を与える可能性があります。最適化にはコストがかかるため、その投資が見合うものであることを確認することが重要です。
自動最適化ツール(または最適化コンパイラ、コード最適化を実行するプログラム)自体も、対象プログラムの効率をさらに向上させるため、あるいは自身の処理速度を向上させるために、最適化が必要になる場合があります。最適化を有効にした状態でコンパイルを実行すると、通常は時間がかかりますが、これは通常、プログラムが非常に大規模な場合にのみ問題となります。
特に、ジャストインタイムコンパイラの場合、ターゲットコードと共に実行される実行時コンパイルコンポーネントのパフォーマンスが、全体の実行速度を向上させる鍵となります。
場合によっては、「最適化」がパフォーマンスを低下させることもあります。並行処理や並列処理は、パフォーマンスに大きなオーバーヘッドコストをもたらし、エネルギー消費量の増加につながる可能性があります。C言語は明示的なマルチプロセッシングをほとんど使用しませんが、通常は他のどのプログラミング言語よりも高速に実行されることに注意してください。ディスクキャッシュ、ページング、スワッピングは、エネルギー消費量とハードウェアの摩耗を大幅に増加させることがよくあります。起動時間を短縮するためにバックグラウンドでプロセスを実行すると、他のすべてのプロセスが遅くなります。
しかし、私が2004年1月にホーアに問い合わせたところ、彼はそのように主張しなかった。