可以查看哪个变体生成更少的汇编指令,但查看哪个变体在更短的时间内实际执行是另一回事。
为了帮助您分析第一个问题,请学习使用 C 编译器的命令行标志来捕获其中间输出。GCC 是 C 编译器的常见选择。让我们看看它针对两个不同程序的未优化汇编代码。
#include <stdio.h>
void report_either_zero()
{
int a = 1;
int b = 0;
if (a == 0 || b == 0)
{
puts("One of them is zero.");
}
}
将该文本保存到诸如zero-test.c之类的文件中,然后运行以下命令:
gcc -S zero-test.c
GCC 将发出一个名为zero-test.s的文件,这是它在生成目标代码时通常会提交给汇编器的汇编代码。
让我们看一下汇编代码的相关片段。我在 Mac OS X 上使用 gcc 4.2.1 版生成 x86 64 位指令。
_report_either_zero:
Leh_func_begin1:
pushq %rbp
Ltmp0:
movq %rsp, %rbp
Ltmp1:
subq $32, %rsp
Ltmp2:
movl %edi, -4(%rbp)
movq %rsi, -16(%rbp)
movl $1, -20(%rbp) // a = 1
movl $0, -24(%rbp) // b = 0
movl -24(%rbp), %eax // Get ready to compare a.
cmpl $0, %eax // Does zero equal a?
je LBB1_2 // If so, go to label LBB1_2.
movl -24(%rbp), %eax // Otherwise, get ready to compare b.
cmpl $0, %eax // Does zero equal b?
jne LBB1_3 // If not, go to label LBB1_3.
LBB1_2:
leaq L_.str(%rip), %rax
movq %rax, %rdi
callq _puts // Otherwise, write the string to standard output.
LBB1_3:
addq $32, %rsp
popq %rbp
ret
Leh_func_end1:
您可以看到我们将整数值 1 和 0 加载到寄存器中的位置,然后准备将第一个与零进行比较,如果第一个非零则再次与第二个进行比较。
现在让我们尝试一种不同的比较方法,看看汇编代码是如何变化的。请注意,这不是同一个谓词;这个检查两个数字是否都为零。
#include <stdio.h>
void report_both_zero()
{
int a = 1;
int b = 0;
if (!(a | b))
{
puts("Both of them are zero.");
}
}
汇编代码有点不同:
_report_both_zero:
Leh_func_begin1:
pushq %rbp
Ltmp0:
movq %rsp, %rbp
Ltmp1:
subq $16, %rsp
Ltmp2:
movl $1, -4(%rbp) // a = 1
movl $0, -8(%rbp) // b = 0
movl -4(%rbp), %eax // Get ready to operate on a.
movl -8(%rbp), %ecx // Get ready to operate on b too.
orl %ecx, %eax // Combine a and b via bitwise OR.
cmpl $0, %eax // Does zero equal the result?
jne LBB1_2 // If not, go to label LBB1_2.
leaq L_.str(%rip), %rax
movq %rax, %rdi
callq _puts // Otherwise, write the string to standard output.
LBB1_2:
addq $16, %rsp
popq %rbp
ret
Leh_func_end1:
如果第一个数字为零,则第一个变体通过避免第二次寄存器移动,就所涉及的汇编指令的数量而言,所做的工作更少。如果第一个数字不为零,则第二个变体通过避免与零进行第二次比较来减少工作量。
现在的问题是“移动、移动、按位或比较”是否比“移动、比较、移动、比较”运行得更快。答案可能归结为处理器是否学会预测第一个整数为零的频率,以及它是否始终如一。
如果让编译器优化这段代码,例子太简单了;编译器在编译时决定不需要比较,只是将该代码压缩为写入字符串的无条件请求。将代码更改为对参数而不是常量进行操作是很有趣的,并了解优化器如何以不同的方式处理这种情况。
变体一:
#include <stdio.h>
void report_either_zero(int a, int b)
{
if (a == 0 || b == 0)
{
puts("One of them is zero.");
}
}
变体二(再次,不同的谓词):
#include <stdio.h>
void report_both_zero(int a, int b)
{
if (!(a | b))
{
puts("Both of them are zero.");
}
}
使用以下命令生成优化的汇编代码:
gcc -O -S zero-test.c
让我们知道你发现了什么。