SHA512算法中的maj函数可以C函数简单方便地实现:
#includeint64_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, @functionmaj:.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个单周期指令多消耗两个超标量时间片都是有可能的。