c++++ 函数调用约定对性能的影响:不同的调用约定(__stdcall__、__cdecl__、__fastcall__)影响参数传递和返回值方式。__fastcall__ 利用寄存器优化参数传递,__cdecl__ 将参数分配到被调用者堆栈上,而 stdcall 将参数分配到调用者堆栈上。性能测试表明 fastcall 表现最好,其次是 __cdecl__,最后是 __stdcall__。
C++ 函数调用约定对性能的影响
引言
函数调用约定定义了调用者和被调用者之间参数传递和返回结果的方式。在 C++ 中,有以下几种常见的函数调用约定:__stdcall__、__cdecl__ 和 __fastcall__。不同的调用约定对应用程序性能可能产生显著影响。
立即学习“C++免费学习笔记(深入)”;
点击下载“系统优化工具,一键解决电脑卡顿”;
参数传递方式
- __stdcall__: 在调用者堆栈上分配参数
- __cdecl__: 在被调用者堆栈上分配参数
- __fastcall__: 在寄存器(如果有可用)和堆栈上分配参数
返回结果的方式
-
返回值存储在:
- __stdcall__: eax 寄存器
- __cdecl__: eax 寄存器或堆栈
- __fastcall__: eax 和 edx 寄存器或堆栈
性能比较
- cdecl 因其简单性和效率而广泛使用,尤其是在调用次数较少或参数较少的情况下。
- stdcall 适用于频繁调用或参数较多的函数,因为它通过在调用者堆栈上分配参数来减少堆栈溢出的可能性。
- fastcall 主要用于优化性能关键路径上的函数,因为它利用寄存器提高了传递参数的速度。
实战案例
以下代码比较了使用不同调用约定对一个简单函数性能的影响:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
#include <iOStream>
#include <windows.h> // 用于获取函数地址
__stdcall void StdcallFun( int a, int b);
__cdecl void CdeclFun( int a, int b);
__fastcall void FastcallFun( int a, int b);
FARPROC StdcallAddr = GetProcAddress(nullptr, "StdcallFun" );
FARPROC CdeclAddr = GetProcAddress(nullptr, "CdeclFun" );
FARPROC FastcallAddr = GetProcAddress(nullptr, "FastcallFun" );
int a = 10;
int b = 20;
auto start = std::chrono::high_resolution_clock::now();
(( void (*)( int , int ))StdcallAddr)(a, b);
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Stdcall took " << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() << " nanoseconds" << std::endl;
start = std::chrono::high_resolution_clock::now();
(( void (*)( int , int ))CdeclAddr)(a, b);
end = std::chrono::high_resolution_clock::now();
std::cout << "Cdecl took " << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() << " nanoseconds" << std::endl;
start = std::chrono::high_resolution_clock::now();
(( void (*)( int , int ))FastcallAddr)(a, b);
end = std::chrono::high_resolution_clock::now();
std::cout << "Fastcall took " << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() << " nanoseconds" << std::endl;
|
结果
结果表明,FastcallFun 的性能最佳,它利用寄存器来优化参数传递。CdeclFun 排名第二,因为它将参数分配到被调用者堆栈上。StdcallFun 表现最差,因为它将参数分配到调用者堆栈上。
注意:由于编译器和系统架构的不同,实际结果可能会有所不同。
结论
选择合适的函数调用约定对于优化 C++ 程序的性能至关重要。对于参数较少或调用次数较少的函数,__cdecl__ 通常是一个不错的选择。对于参数较多或频繁调用的函数,__stdcall__ 或 fastcall 可能是更好的选择。通过仔细考虑不同的调用约定,开发人员可以提高应用程序的速度和效率。