それで、SUPER低レベルのIF()はどのように見えますか、x86プロセッサによってどのように処理されますか?
7 に答える
プロセッサには、特定の条件が満たされたときに分岐する「分岐if」命令があり、それ以外の場合は次の命令に進みます。
それで
if(A)
{
dosomething;
}
になります
load A into register 0
if the zero flag is set (ie, register 0 contains 0x00) then jump to endcondition)
dosomething
endcondition:
より複雑な条件(if(A || B && C)
)は、レジスタを0またはゼロ以外の状態のままにする一連の命令になるため、branchif命令は、条件付きフラグに基づいてジャンプする場合とジャンプしない場合があります。
多くの条件付きフラグ(ゼロ、キャリー、ネガティブ、オーバーフローなど)があり、一部のbranchif命令は、より複雑な条件でも動作します(つまり、単に見ているだけでなく、レジスタが別のレジスタと等しいかどうかを実際にチェックする場合がありますフラグ)。アーキテクチャはそれぞれ異なり、トレードオフが発生するため、命令セットは完全ですが、スピーディーでコンパクトです。
moochaがコメントで指摘しているように、一部のアーキテクチャでは、一部、多数、またはすべての命令に条件を適用できるため、「branch if」命令だけでなく、「and if」、「add if」、 'moveif'など。
x86は、パイプライン処理、アウトオブオーダー実行、キャッシング、マイクロコード、およびその他すべての高度なトピックに入ると、この簡単な説明を超えて非常に非常に複雑になります。ほとんどの場合、上記の説明で十分です。ただし、手作りの非常に緊密に巻かれたアルゴリズムを作成している場合は、最大のパフォーマンスとスループットを実現するために、これらのことを考慮する必要があります。
それは別の質問のトピックですが...
-アダム
C コンパイラの出力を使用して ( -S
gcc のスイッチを使用)、コンパイル時に C の特定のスニペットが生成する出力を確認するのはかなり簡単です。ただし、おもちゃのプログラムで最適化を使用する場合は注意してください。注意しないと、オプティマイザーは、常に何らかの方向に進む条件を最適化してしまうことがよくあります (詳細な説明については、マイクロベンチマークに関するこの記事を参照してください)。
たとえば、単純な C プログラム:
#include <stdio.h>
int main (int argc, char **argv) {
int ii = 10;
int jj = 20;
if (jj > ii) {
puts ("jj > ii \n");
}
return 0;
}
次のアセンブリ言語にコンパイルされます。
.file "foo.c"
.section .rodata
.LC0:
.string "jj > ii \n"
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $20, %esp
movl $10, -8(%ebp)
movl $20, -12(%ebp)
movl -12(%ebp), %eax
cmpl -8(%ebp), %eax
jle .L2
movl $.LC0, (%esp)
call puts
.L2:
movl $0, %eax
addl $20, %esp
popl %ecx
popl %ebp
leal -4(%ecx), %esp
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.3.2-1ubuntu12) 4.3.2"
.section .note.GNU-stack,"",@progbits
何が起こっているのかを簡単に分析するには:
最初のセクション (
.rodata
) は、文字列 ' ' で定数を宣言しますjj > ii \n
)2 番目のセクションでは、スタック上の変数
ii
と変数の内容を初期化しています。jj
からのビット
cmpl -8(%ebp), %eax
が実際の比較を行っています。jle
命令は ' ' への呼び出しをスキップしています。これは事実上、' ' ステートメントputs
のロジックが逆になっています。if
ラベル '
.L2
' の後、システムはスタックの一番上を整理し、呼び出しから戻っています。
これは、特定のマシン アーキテクチャに依存する分岐命令です。特定の低レベルの条件をテストするためにメモリの場所またはレジスタを設定する方法を見つけ出します - branch-if-not-equal や branch-if-not-zero など... -- そのテストを実行してからジャンプします (または条件が失敗した場合はそうではありません) メモリの別の部分に。明らかに、複雑な条件がある場合は、多くの異なる条件を評価する必要があり、いくつかの分岐命令が含まれる場合があります。
一般に、CPU には、次に実行される現在の機械語オペコードのメモリ アドレスを保持する命令レジスタと呼ばれるものと、データを保持するための他の多数のレジスタがあります。
一般に、CPU が命令レジスタ内の各オペコードを実行した後、コンパイルされたプログラム アプリケーション内の次のオペコードを持つべきメモリ内の次の位置に移動するために、CPU は単純にそれを 1 だけインクリメントします。
1 つのオペコード (実際にはおそらくいくつかあります) は、他の 2 つの CPU レジスタの値を「比較」することによって、CPU が「分岐」できるようにし、一方が他方よりも大きい場合は、1 つのメモリ アドレスを命令レジスタにコピーします。一方、もう一方が最大の場合は、2 番目の異なるメモリ アドレスを命令レジスタにコピーします。
それは、リレーやトランジスタについて話さずに置くことができるのと同じくらい「低い」レベルです...
if ステートメントの大部分は最終的に条件付き分岐になりますが、どちらの分岐にも副作用がない非常に単純なケースでは、最適化コンパイラは、一方のみを実行するのではなく、両方を実行して結果を計算するコードを生成する場合があります。これは、両方の分岐を計算する平均コストが分岐予測ミスによる平均コストよりも少ないパイプライン化されたアーキテクチャで利点をもたらす可能性があります。
たとえば、コードは次のとおりです。
int x;
if ( y < 5 )
x = 5;
else
x = y;
次のように記述されているかのようにコンパイルできます。
y -= 5
int r = y < 0; // r is 1 if y < 5, 0 otherwise
r -= 1 // r is 0x00000000 if y < 5, 0xffffffff otherwise
x = y & r // x is 0 if y < 5, (y-5) otherwise
x += 5; // x is 5 if y < 5, y otherwise
分岐なしで機械語に変換できる
このような構造が x86 アーキテクチャでどのようにコンパイルされるかについてのかなり良い概要を以下に示します: http://en.wikibooks.org/wiki/X86_Disassembly/Branches#If-Then
場合によっては分岐を回避する方法があります (パイプラインの破損により、パフォーマンスが大幅に低下することがよくあります)。たとえば、i686 命令セット以降 (Pentium Pro から現在までのすべて) には、これをコンパイルする可能性のある条件付き move 命令があります。
if (a==0) {
b= 1;
}
このようなものに:
cmp 0, eax
cmovzl ebx, 1
コンパイラが i686+ をターゲットにするように設定されている限り、分岐はありません (そのように感じます; コンパイラは複雑で不可解です)。SET[condition] は、別の同様の条件付き命令です。
幸運な古い ARM プログラマーは、条件付きで任意の命令を指定できるため、分岐が大幅に削減されます。
基本的に、CPU内のさまざまな原子の間で大量の電子が渡されます。CPU内のシリコン原子の構造により、電子は特定のパスをたどります。これにより、コンピューターがたどる実行の分岐が決まります。
編集:私はもう少し漠然と説明する必要があるようです。私は電気工学ではなくコンピュータサイエンスを専攻していたので、これらのことをあまり深く理解していません。
CPUは、「半導体」と呼ばれる材料、通常はシリコンでできています。半導体の優れた点の1つは、材料に負または正の「電荷キャリア」の領域を作成する不純物を「ドーピング」または適用することで、その電気的特性を簡単に変更できることです。これらの領域が集まる線は「ジャンクション」と呼ばれ、電気はこれらのジャンクションを他の方向よりもはるかに簡単に通過します。この特性を利用して、電気を一方向にのみ流すことができるダイオードと、ある電流が別の電流を制御できるようにする小さなスイッチと考えることができるトランジスタを作成します。これらのトランジスタとダイオードは、CPUの論理ゲートを作成するために無数の方法で組み合わされています。
CPU内の論理ゲートの多くは、命令の取得とデコード、CPUの残りの部分への指示、そして最後に次の命令の取得を担当する「制御ユニット」として使用されます。x86では、コントロールユニットは実際に「マイクロコード」を実行しており、分岐やパイプライン処理などの処理方法を指示します。x86 ISAが特定のマイクロアーキテクチャにどのように実装されているかを理解するには、特定のプロセッサラインについて非常に具体的にする必要があります。