4

バックグラウンド:

主に重い計算用に、最適化された Delphi/BASM ルーチンのユニットがあります。これらのルーチンの一部には内部ループが含まれており、ループの開始が DQWORD (16 バイト) 境界に揃えられている場合、大幅な高速化を実現できます。ルーチンのエントリ ポイントでの位置合わせがわかっている場合は、問題のループが目的どおりに位置合わせされていることを確認できます。

私の知る限り、Delphi コンパイラはプロシージャ/関数を DWORD 境界に揃えます。たとえば、ユニットに関数を追加すると、後続の関数の配置が変更される場合があります。ただし、ルーチンの末尾を 16 の倍数になるようにパディングする限り、最初のルーチンの配置に応じて、後続のルーチンも同様に配置されるか、または配置がずれることを保証できます。したがって、重要なルーチンをユニットの実装セクションの先頭に配置し、それらの前に少しのパディング コードを配置して、最初のプロシージャが DQWORD でアラインされるようにしました。

これは以下のようになります。

interface

procedure FirstProcInUnit;

implementation

procedure __PadFirstProcTo16;
asm
    // variable number of NOP instructions here to get the desired code length
end;

procedure FirstProcInUnit;
asm //should start at DQWORD boundary
    //do something
    //padding to align the following label to DQWORD boundary
    @Some16BAlignedLabel:
        //code, looping back to @Some16BAlignedLabel
    //do something else
    ret #params
    //padding to get code length to multiple of 16
end;

initialization

__PadFirstProcTo16; //call this here so that it isn't optimised out
ASSERT ((NativeUInt(Pointer(@FirstProcInUnit)) AND $0F) = 0, 'FirstProcInUnit not DQWORD aligned');

end.

これは少し首の痛みですが、必要に応じてこの種のことを機能させることができます. 問題は、そのようなユニットを別のプロジェクトで使用したり、同じプロジェクト内の他のユニットに変更を加えたりすると、__PadFirstProcTo16それ自体の配置が崩れる可能性があることです。同様に、同じプロジェクトを異なるコンパイラ バージョン (D2009 と D2010 など) で再コンパイルすると、通常は整合が崩れます。そのため、私が見つけたこの種のことを行う唯一の方法は、プロジェクトの残りの部分がすべて最終的な形になったときに行う最後の作業として、手動で行うことでした。

質問1:

(少なくともいくつかの特定の) ルーチンが DQWORD に揃えられていることを保証するという望ましい効果を達成する他の方法はありますか?

質問2:

コンパイラのコードの配置に影響を与える正確な要因はどれですか?また、(どのように)そのような特定の知識を使用して、ここで概説されている問題を克服できますか?

この質問のために、「コードのアライメントについて心配する必要はありません/関連するおそらく小さな速度の利点」は許容できる答えではないと仮定します。

4

2 に答える 2

7

Delphi XE では、コード アラインメントの問題は、$CODEALIGNコンパイラ ディレクティブを使用して簡単に解決できるようになりました(この Delphi ドキュメント ページを参照してください)。

{$CODEALIGN 16}
procedure MyAlignedProc;
begin
..
end;
于 2010-08-31T09:57:48.230 に答える
6

できることの 1 つは、明示的な ret 命令の後に、各ルーチンの最後に「魔法の」署名を追加することです。

asm
  ...
  ret
  db <magic signature bytes>
end;

これで、各ルーチンへのポインターを含む配列を作成し、実行時に一度ルーチンをスキャンしてマジック シグネチャを探し、各ルーチンの末尾とその長さを見つけることができます。次に、PAGE_EXECUTE_READWRITE を使用して VirtualAlloc で割り当てる新しいメモリ ブロックにそれらをコピーし、今度は各ルーチンが 16 バイト境界で開始されるようにします。

于 2009-12-06T02:34:41.267 に答える