gforth
エンジンと gforth-fast
エンジンは、 別の最適化、 つまり、
レプリケーションを伴う動的スーパー命令(Dynamic superinstructions with replication)を使用します。
例として、 以下のコロン定義について考えてみましょう:
: squared ( n1 -- n2 ) dup * ;
Gforth はこれを以下のスレッド化コード・シーケンスにコンパイルします
dup * ;s
simple-see
(see Examining compiled code)を使用して、
コロン定義のスレッド化コードを確認します。
通常の直接スレッド化コードでは、 上記のプリミティブごとに 1 つのセルを占めるコード・アドレスがあります。
各コード・アドレスはマシン・コード・ルーチンを指し、 インタープリターはプリミティブを実行するためにこのマシン・コードにジャンプします。 上記 3
つのプリミティブのルーチンは以下のとおりです(386 の gforth-fast
の場合):
Code dup ( $804B950 ) add esi , # -4 \ $83 $C6 $FC ( $804B953 ) add ebx , # 4 \ $83 $C3 $4 ( $804B956 ) mov dword ptr 4 [esi] , ecx \ $89 $4E $4 ( $804B959 ) jmp dword ptr FC [ebx] \ $FF $63 $FC end-code Code * ( $804ACC4 ) mov eax , dword ptr 4 [esi] \ $8B $46 $4 ( $804ACC7 ) add esi , # 4 \ $83 $C6 $4 ( $804ACCA ) add ebx , # 4 \ $83 $C3 $4 ( $804ACCD ) imul ecx , eax \ $F $AF $C8 ( $804ACD0 ) jmp dword ptr FC [ebx] \ $FF $63 $FC end-code Code ;s ( $804A693 ) mov eax , dword ptr [edi] \ $8B $7 ( $804A695 ) add edi , # 4 \ $83 $C7 $4 ( $804A698 ) lea ebx , dword ptr 4 [eax] \ $8D $58 $4 ( $804A69B ) jmp dword ptr FC [ebx] \ $FF $63 $FC end-code
動的なスーパー命令とレプリケーションを使用すると、 コンパイラーはスレッド化されたコードを配置しないだけでなく、 コード断片のコピーも行い、 通常は各コード断片最後で行うジャンプを行いません。
( $4057D27D ) add esi , # -4 \ $83 $C6 $FC ( $4057D280 ) add ebx , # 4 \ $83 $C3 $4 ( $4057D283 ) mov dword ptr 4 [esi] , ecx \ $89 $4E $4 ( $4057D286 ) mov eax , dword ptr 4 [esi] \ $8B $46 $4 ( $4057D289 ) add esi , # 4 \ $83 $C6 $4 ( $4057D28C ) add ebx , # 4 \ $83 $C3 $4 ( $4057D28F ) imul ecx , eax \ $F $AF $C8 ( $4057D292 ) mov eax , dword ptr [edi] \ $8B $7 ( $4057D294 ) add edi , # 4 \ $83 $C7 $4 ( $4057D297 ) lea ebx , dword ptr 4 [eax] \ $8D $58 $4 ( $4057D29A ) jmp dword ptr FC [ebx] \ $FF $63 $FC
スレッド化コードの制御フローに変更が発生した場合(;s
など)にのみジャンプが追加されます。 この最適化により、
これらのジャンプの多くが排除され、 残りの部分がより予測可能になります。 高速化はプロセッサとアプリケーションによって異なります。 Athlon
および Pentium III では、 この最適化により通常 2 倍の速度向上が得られます。
直接スレッド化コード内のコード・アドレスは、 コピーされたマシン・コード内の適切なポイントを指すように設定されます。 この例では以下のようになります:
primitive code address dup $4057D27D * $4057D286 ;s $4057D292
したがって、 このコード部分の任意の場所にスレッド化コードからのジャンプ先が存在する可能性があります。 これにより、 逆コンパイルもかなり簡素化されます。
See-code
(see Examining compiled code)は、 動的スーパー命令(dynamic
superinstructions)のネイティブ・コードと混在するスレッド化コードを示します。 最近では、
動的に生成されたネイティブ・コードに追加の最適化が適用されているため、 特定の AMD64 インストール上の gforth-fast
での
see-code squared
の出力は以下のようになります:
$7FB689C678C8 dup 1->2 7FB68990C1B2: mov r15,r8 $7FB689C678D0 * 2->1 7FB68990C1B5: imul r8,r15 $7FB689C678D8 ;s 1->1 7FB68990C1B9: mov rbx,[r14] 7FB68990C1BC: add r14,$08 7FB68990C1C0: mov rax,[rbx] 7FB68990C1C3: jmp eax
--no-dynamic を使用してこの最適化を無効にできます。 --no-super を使用すると、 ジャンプを排除せずにコピーを使用できます(つまり、 動的レプリケーション、 しかしスーパー命令は使用しません)。 これは分岐予測のみに利点をもたらします。 パフォーマンスへの影響は CPU によって異なります。 Athlon および Pentium III では、 レプリケーションを伴う動的スーパー命令(dynamic superinstructions with replication)よりも高速化が若干損なわれます。
これらのオプションの用途の 1 つは、 スレッド化されたコードにパッチを適用する場合です。 スーパー命令を使用すると、 ディスパッチ・ジャンプの多くが削除されるため、 パッチを当てても効果がなくなることがよくあります。 これらのオプションは、 すべてのディスパッチ・ジャンプを保持します。
一部のマシンでは動的スーパー命令(dynamic superinstructions)は安全ではないため、 デフォルトで無効になっています。 しかしながら、 あなたが冒険してみたい場合は、 --dynamic を使用して有効にすることができます。