2019独角兽企业重金招聘Python工程师标准>>>
SHA512算法中的maj函数可以C函数简单方便地实现:
#include
int64_t maj(int64_t a, int64_t b, int64_t c)
{
return((a & b) ^ (a & c) ^ (b & c));
}
从字面上看,逻辑乘"&"有3次,异或"^"有2次,事实真的如此吗?
将此函数编译成汇编语言得到如下结果:(注释是我后加上的)
.file "maj.c"
.text
.p2align 4,,15
.globl maj
.type maj, @function
maj:
.LFB0:
.cfi_startproc
# rdi = a
# rsi = b
# rdx = c
movq %rsi, %rax #rax = b
xorq %rdi, %rax #rax = (rdi ^ rax) = (a ^ b)
andq %rsi, %rdi #rdi = (rsi & rdi) = (b & a)
andq %rdx, %rax #rax = (rdx & rax) = (c & (a ^ b)) = ((c & a) ^ (c & b))
xorq %rdi, %rax #rax = (rdi ^ rax) = ((b & a) ^ (c & a) ^ (c & b))
ret #return rax as result
.cfi_endproc
.LFE0:
.size maj, .-maj
.ident "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-16)"
.section .note.GNU-stack,"",@progbits
阅读汇编代码可知,实际运算是:逻辑乘运算"andq"2次,异或运算"xorq"2次,
显然,编译器依据逻辑代数运算规则进行了化简,如果不化简按照字面直译,
我自己写的汇编代码如下:(andq x 3, xorq x 2)
# rdi = a
# rsi = b
# rdx = c
movq %rsi, %rax #rax = b
andq %rdi, %rax #rax = (rdi & rax) = (a & b)
andq %rdx, %rsi #rsi = (rdx & rsi) = (c & b)
andq %rdx, %rdi #rdi = (rdx & rdi) = (c & a)
xorq %rdi, %rax #rax = (rdi ^ rax) = ((c & a) ^ (a & b))
xorq %rsi, %rax #rax = (rsi ^ rax) = ((c & b) ^ (c ^ a) ^ (a & b))
ret #return rax as result
两种代码孰优孰劣,可以说是一目了然,GCC只用了5条指令,后者用了6条指令,
前者在运算过程中只改变两个寄存器(rax,rdi)的数值,后者在运算中改变了四个
寄存器(rax,rsi,rdi,rdx)的数值。对这么简单的函数的编程实现吹毛求疵有意义吗?
当然有,别忘了x64架构是超标量处理器,多1条指令可能多一个超标量时间片,
相当于多4个单周期指令,更糟糕的是如果临近长周期指令,很可能打乱本来很好
的超标量分组,增加1个单周期指令多消耗两个超标量时间片都是有可能的。