基本的な最適化
最適化ということになると、多くの人は「コンピューターの速度は今とても速いのに、数パーセント速くなっても何の意味があるのでしょうか?」と否定的に考えます。これにはある程度の意味があります。グラフィックス、画像、マルチメディアなどの特定のソフトウェアの開発を除いて、開発者がコードを作成する場合には、意図的な最適化は必要ありません。最適化を完了すると同時に、開発効率を向上させることができるのはなぜでしょうか。
もちろん、アルゴリズムの設計が最適化の核心です。ほとんどの場合、プログラムの実行効率は、主に開発者のプログラムの全体的な理解、アルゴリズムの設計などによって決まります。しかし、場合によっては細部を最適化することも意味があります。
さらに、多くの場合、この種の最適化ではアセンブリを通じて直接コードを記述する必要はありませんが、この場合、アセンブリの知識を習得することの優位性も反映されます。
たとえば、次の 2 つの関数があります。
関数 GetBit(i: 基数; n: 基数): ブール値;
始める
結果 := Boolean((i shr n) and 1);
終わり;
関数 GetBit(i: 基数; n: 基数): ブール値;
始める
結果 := Boolean((1 shl n) および i);
終わり;
対応するアセンブリコード:
MOV ECX、EDX
SHR EAX、CL
および EAX、01 ドル
MOV ECX、EDX
MOV EDX、$01
SHL EDX、CL
およびEAX、EDX
これらは同じ機能を持ち、すべて i の特定のビットの値を受け取り、1 の場合は True を返し、0 の場合は False を返します。
表面上、2 つの関数の実行効率は同じであると思われるかもしれませんが、実際には違いがあります。最初のプログラムのシフト演算は、Delphi のデフォルトの呼び出し規則に従って、 register で実行されます。このとき、値はレジスタ EAX に格納され、シフト演算は直接完了できますが、2 番目のプログラムは異なり、即値 1 のシフト演算を最初にレジスタに転送する必要があります。したがって、もう 1 つ指示があるはずです。もちろん、すべての場合において、命令の数が少ない方が命令の数が多いよりも必ずしも高速になるわけではありません。また、命令実行のクロック サイクルや命令のペアなどの問題も考慮する必要があります (これについては後で説明します)。問題は、特定のコード環境でのみ比較できる場合にのみ発生します。
通常の状況では、この効率の差は無視できるほど小さいですが、プログラミング中に最適化を意識することは決して悪いことではありません。このようなコードがループの最内層に位置し、多数のループを通じて N クロック サイクルが蓄積されると、実行効率の差が非常に大きくなる可能性があります。
上記はほんの一例ですが、開発中にいくつかの問題をアセンブリの観点から考えることができれば、開発効率を確保しながら、より効率的に詳細なコードを高級言語で書くことができることがわかります。しかし、埋め込みアセンブリ コードを使用して詳細な最適化を完了する必要がある場合は依然として多く、埋め込みアセンブリ コードの適用によってコードの記述がより効率化される場合もあります。
32 桁の数値のバイト順序を逆にする必要がある場合、Delphi の高級言語で完全にそれを行うにはどうすればよいでしょうか?シフトを使用したり、組み込み関数 Swap を複数回呼び出すこともできますが、BSWAP 命令を考えると、すべてが非常に簡単になります。
関数 SwapLong(値: Cardinal): Cardinal;
アズム
BSWAP EAX
終わり;
注: 上記と同様に、Value の値はレジスタ EAX に格納され、32 桁の値も EAX を介して返されるため、必要な文は 1 文だけです。
もちろん、ほとんどの組み込みアセンブリの最適化はそれほど単純ではありませんが、大学で学んだわずかなアセンブリの知識では、より詳細な最適化を達成することは困難です。経験は、コンパイルされたアセンブリ コードの継続的な蓄積と比較によってのみ得られます。幸いなことに、ほとんどの場合、詳細な最適化はプログラム設計の主要部分ではありません。
ただし、開発したプログラムにグラフィックス、画像、マルチメディアなどが含まれる場合は、より詳細な最適化を実行する必要があります。幸いなことに、Delphi6 は、浮動小数点命令の最適化であっても、MMX、SSE、3DNow などのアプリケーションであっても、優れたサポートを提供できます。以前のバージョンの Delphi でこれらの CPU 拡張命令セットをサポートしたい場合、または将来的に新しい CPU 命令セットをサポートしたい場合でも、Delphi がサポートする DB、DW、DD、および DQ の 4 つのアセンブリ命令を組み込みアセンブリで使用できます ( BorlandのDelphi6公式では(言語マニュアルにはDB、DW、DDをサポートしているとだけ書かれています)、関連する命令の数値表現も柔軟に実装できます。
のように:
DW $A20F //CPUID
DW $770F //EMMS
DB $0F、$6F、$C1 //MOVQ MM0、MM1
命令を理解することは基礎にすぎません。FPU、MMX、SSE に関するアルゴリズムを設計した後、それをさらに最適化したい場合は、CPU 自体の技術的特性の一部も理解する必要があります。
次の 2 つのコードを見てみましょう。
アズム
ADD [a]、ECX
追加[b]、EDX
終わり
アズム
MOV EAX、[a]
MOV EBX、[b]
EAX、ECXを追加
EBX、EDXを追加
MOV [a]、EAX
MOV [b]、EBX
終わり
2番目の方が効率的ですか?間違いです。前述したように、命令数が少ないからといって実行効率が高いわけではありません。関連情報によると、コードの最初のセクションにある 2 つの命令を実行するクロック サイクルは 3 です (各命令は 3 つの読み取りステップを完了する必要があります)。変更と書き込みのステップ)、コードの 2 番目のセクションの 6 つの命令によって実行されるクロック サイクルはすべて 1 です。ということは、2 つのコードの効率は同じなのでしょうか?これも間違いです。2 番目のコードは実際には最初のコードよりも効率的に実行されます。なぜ? Pentium クラス以降の CPU は命令を実行するパイプラインを 2 つ備えているため、隣接する 2 つの命令をペアにすることができれば、同時に実行することができます。上記の 2 つのコードに特有の理由は何ですか?
最初のコードの 2 つの命令はペアにすることができますが、必要な合計実行クロック サイクルは 3 ではなく 5 ですが、2 番目のコードの 6 つの命令は並列実行できるため、この結果が得られます。
そういえば、これらはすべて非常に単純な例であり、それだけではあまり役に立ちません。特定のプログラムを本当に最適化したい場合は、FPU や MMX の最適化に関する特集記事を探したり、技術マニュアルを見つけて「アウトオブオーダー実行」や「分岐予測」などの技術を研究したりする必要があります。大学に通う私の友人全員が、それらの「お金を稼ぐ」開発ツールやファッショナブルな新しいテクノロジーだけに集中するのではなく、しっかりとした基礎を築き、新しい知識をすぐに習得できるようにすることにもっと時間を費やしてほしいと願っています。 , この方法でのみ、新しい開発ツールとスキルをより早く習得できるようになります...(千語省略)。
しかし、繰り返しになりますが、実際的な問題を解決するには知識を活用する必要があります。毎日技術的な詳細だけに集中していると、優れたハッカーにはなれるかもしれませんが、一流のソフトウェアを開発することはできません。したがって、基本的な目的はやはり価値を創造することでなければなりません。ということで…これ以上話すと技術記事っぽくなくなってしまいますので、これ以上は話しません。 ^_^
添付: プログラムの最適化では、実行効率を考慮するだけでなく、サイズの問題も考慮する必要があります (サイズが小さいと、メモリのロードが速くなり、命令のデコードやその他のタスクをより速く完了できます)。たとえば、EAX レジスタをクリアするには、SUB EAX、EAX、または XOR EAX を使用します。 、MOV EAX、$0 の代わりに EAX です。実行クロック サイクルはどちらも 1 ですが、前者の命令長 (2 バイト) は後者の命令長 (5 バイト) よりも明らかに短いです。ただし、上記はすべて詳細であるため、ボリュームの問題については言及されていませんでした。サイズ削減に関するその他の問題は、コンパイラに解決を任せる必要があります。埋め込み ASM コードを作成するときは、少し注意してください。