SHA256哈希类中用宏替代旋转函数是否更快?原因是什么?
在本人实现的SHA256哈希类中,包含rotate_right、unpack32、pack32等函数(具体实现如下):
uint32_t shift_right ... uint32_t rotate_right(uint32_t x, uint32_t n) { return ((x >> n) | (x << ((sizeof(x) << 3) - n))); } uint32_t rotate_left ... uint32_t choose ... uint32_t majority ... void unpack32(size_t x, uint8_t* str) { *((str)+3) = (uint8_t)((x)); *((str)+2) = (uint8_t)((x) >> 8); *((str)+1) = (uint8_t)((x) >> 16); *((str)+0) = (uint8_t)((x) >> 24); } void pack32(const uint8_t* str, uint32_t* x) { *(x) = ((uint32_t) * ((str)+3)) | ((uint32_t) * ((str)+2) << 8) | ((uint32_t) * ((str)+1) << 16) | ((uint32_t) * ((str)+0) << 24); } uint32_t f1(uint32_t x) { return (rotate_right(x, 2) ^ rotate_right(x, 13) ^ rotate_right(x, 32)); } uint32_t f2(uint32_t x) { return (rotate_right(x, 6) ^ rotate_right(x, 11) ^ rotate_right(x, 25)); } uint32_t f3(uint32_t x) { return (rotate_right(x, 7) ^ rotate_right(x, 18) ^ shift_right(x, 3)); } uint32_t f4(uint32_t x) { return (rotate_right(x, 17) ^ rotate_right(x, 19) ^ shift_right(x, 10)); }
现考虑使用如下宏定义替代同名函数:
#define rotate_right(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n)))
请问这样替换是否能让代码运行更快?若能,原因是什么?
回答
大概率能让代码运行更快,但存在例外场景
核心原因
彻底消除函数调用开销
函数版本的rotate_right每次调用都要经历栈帧创建、参数入栈、返回值传递这些流程,哪怕函数体只有一行代码,这些运行时的额外操作也会产生开销。而宏是在预编译阶段直接把代码展开到调用位置,完全跳过了函数调用的所有环节。对于SHA256这种需要反复执行位运算的算法,这类累积的开销会直接影响整体性能。给编译器留出更多优化空间
宏展开后,编译器能看到完整的位运算逻辑,更容易和周围代码做联合优化。比如在f1里调用rotate_right(x,32),宏展开后编译器能直接识别出(x >> 32) | (x << 0)等价于x,直接把这个操作替换成原变量;而如果是函数版本,若编译器没开启足够优化,可能还是会执行完整的函数调用和运算。
例外情况
如果编译器开启了O2及以上级别的优化,通常会自动把这种简单的小函数内联,这时宏和函数的性能差距会非常小甚至完全消失。但内联不是绝对的——比如函数定义在其他编译单元,或者编译器判断内联会导致代码膨胀过度,就可能不执行内联,这时宏的优势就会显现出来。
注意事项
宏存在一个潜在风险:如果调用时的参数是带有副作用的表达式(比如rotate_right(x++, 2)),宏会导致副作用被执行多次,而函数只会执行一次。所以替换前要确保所有调用rotate_right的参数都没有副作用。
内容的提问来源于stack exchange,提问作者Cihan Bilgihan

