在编写函数时,我通常会想起
的“干净代码”原则函数不应有超过 3 个参数。
但是,考虑到下面的这些 x86-64 调用约定,我将其放宽为 4 个参数,因为这通过确保利用 CPU 寄存器而不是比寄存器访问慢的堆栈操作来涵盖跨平台函数。
系统 V AMD64 ABI
Integer/Pointer Arguments 1-6: RDI, RSI, RDX, RCX, R8, R9
Floating Point Arguments 1-8: XMM0 - XMM7
Excess Arguments: Stack
Microsoft x64 调用约定
Integer/Pointer Arguments 1-4: RCX, RDX, R8, R9
Floating Point Arguments 1-4: XMM0 - XMM3
Excess Arguments: Stack
示例
将参数数量限制为 4,确保 Windows 和 Linux 使用寄存器而不是堆栈。
#include <stdio.h>
int Add(int a, int b, int c, int d) { return a + b + c + d; }
int main() {
int sum = Add(1,2,3,4);
return 0;
}
Linux 反汇编
mov ecx, 4
mov edx, 3
mov esi, 2
mov edi, 1
call Add
Windows 拆解
mov r9d,4
mov r8d,3
mov edx,2
mov ecx,1
call Add
问题
在编写跨平台函数时,这种微优化是一个可行的新咒语吗?
函数不应有超过 4 个参数。
注意
具体用例是在 Windows 和 Linux 上使用 UASM(理解 MASM 语法)汇编代码,因此没有方便的内置优化器。
您所说的“干净代码”原则很可能就是指:代码整洁和可读性。许多参数通常意味着长行和/或分成多行的参数列表(只需查看具有 10 个参数的 Win32 函数)。
但是,由于您似乎是从严格的性能角度提出这个问题,因此函数有多少个参数并不重要。您不应该减少参数的数量,而应该尝试减少调用的数量。一个好方法是编写编译器可以内联的相当短的函数。
假设我的代码需要添加一批 4 和一批 5,而不是 4 个数字。对于 4 参数函数,我需要这样做:
#include <stdio.h>
int Add(int a, int b, int c, int d) { return a + b + c + d; }
int main() {
int sum1 = Add(1,2,3,4);
int sum2 = Add(1,2,3,Add(4,5,0,0));
return 0;
}
现在假设我使用 5 参数函数:
#include <stdio.h>
int Add(int a, int b, int c, int d, int e) { return a + b + c + d + e; }
int main() {
int sum1 = Add(1,2,3,4,0);
int sum2 = Add(1,2,3,4,5);
return 0;
}
如您所见,第二个版本仅使用 2 次调用(因为我需要 2 个不同的和)。第一个版本使用了 3 次调用。因此,您正在用 2 个
mov
堆栈访问(在 5-arg 版本中)换取整个额外的 call
/ret
对。请注意,其中还对返回地址执行相同的 2 次堆栈访问(同时还可能以影子堆栈访问、放置在新函数中的金丝雀值等形式引入额外的开销)。
当然,这个例子使用了编译器内联的
Add
函数(常数和也在编译时计算,编译器检测到不需要)。但对于更复杂的实际函数,调用次数越少越好。