如何分析cpp/assembly代码的性能
How do I analyze performance of cpp / assembly code
我正在尝试了解更多关于如何分析我更常用的方法的性能的信息。
我已经尝试过使用rand()和计时对我的方法的大量调用作为性能测量方法,但我也想通过了解汇编代码的作用来了解更多关于如何测量性能的信息。
例如,我读到有人试图优化sgn函数(C/C++中有标准的符号函数(signum,sgn)吗?)所以我觉得这将是一个很好的开始。我去了http://gcc.godbolt.org并为以下代码生成asm(具有-march=core-avx2 -fverbose-asm -Ofast -std=c++11
的ICC):
int sgn_v1(float val)
{
return (float(0) < val) - (val < float(0));
}
和
int sgn_v2(float val)
{
if (float(0) < val) return 1;
else if (val < float(0)) return -1;
else return 0;
}
这产生了以下组件
L__routine_start__Z6sgn_v1f_0:
sgn_v1(float):
vxorps %xmm2, %xmm2, %xmm2 #3.38
vcmpgtss %xmm2, %xmm0, %xmm1 #3.38
vcmpgtss %xmm0, %xmm2, %xmm3 #3.38
vmovd %xmm1, %eax #3.38
vmovd %xmm3, %edx #3.38
negl %eax #3.38
negl %edx #3.38
subl %edx, %eax #3.38
ret #3.38
和
L__routine_start__Z6sgn_v2f_1:
sgn_v2(float):
vxorps %xmm1, %xmm1, %xmm1 #8.3
vcomiss %xmm1, %xmm0 #8.18
ja ..B2.3 # Prob 28% #8.18
vcmpgtss %xmm0, %xmm1, %xmm0 #
vmovd %xmm0, %eax #
ret #
..B2.3: # Preds ..B2.1
movl $1, %eax #9.12
ret #9.12
我的分析从以下事实开始,sgn_v1
有9条指令,sgn_v2
有6或5条指令,这取决于跳跃的结果。上一篇文章讨论了sgn_v1
是如何无分支的,这似乎是一件好事,我认为这意味着sgn_v1
中的多个指令可以同时执行。我去了http://www.agner.org/optimize/instruction_tables.pdf我无法资助haswell部分的大部分手术(p187-p202)。
我该如何分析?
编辑:
响应@Raxvan的评论,我运行了以下测试程序
extern "C" int sgn_v1(float);
__asm__(
"sgn_v1:n"
" vxorps %xmm2, %xmm2, %xmm2n"
" vcmpgtss %xmm2, %xmm0, %xmm1n"
" vcmpgtss %xmm0, %xmm2, %xmm3n"
" vmovd %xmm1, %eaxn"
" vmovd %xmm3, %edxn"
" negl %eaxn"
" negl %edxn"
" subl %edx, %eaxn"
" retn"
);
extern "C" int sgn_v2(float);
__asm__(
"sgn_v2:n"
" vxorps %xmm1, %xmm1, %xmm1n"
" vcomiss %xmm1, %xmm0n"
" ja ..B2.3n"
" vcmpgtss %xmm0, %xmm1, %xmm0n"
" vmovd %xmm0, %eaxn"
" retn"
" ..B2.3:n"
" movl $1, %eaxn"
" retn"
);
#include <cstdlib>
#include <ctime>
#include <iostream>
int main()
{
size_t N = 50000000;
std::clock_t start = std::clock();
for (size_t i = 0; i < N; ++i)
{
sgn_v1(float(std::rand() % 3) - 1.0);
}
std::cout << "v1 Time: " << (std::clock() - start) / (double)(CLOCKS_PER_SEC / 1000) << " ms " << std::endl;
start = std::clock();
for (size_t i = 0; i < N; ++i)
{
sgn_v2(float(std::rand() % 3) - 1.0);
}
std::cout << "v2 Time: " << (std::clock() - start) / (double)(CLOCKS_PER_SEC / 1000) << " ms " << std::endl;
start = std::clock();
for (size_t i = 0; i < N; ++i)
{
sgn_v2(float(std::rand() % 3) - 1.0);
}
std::cout << "v2 Time: " << (std::clock() - start) / (double)(CLOCKS_PER_SEC / 1000) << " ms " << std::endl;
start = std::clock();
for (size_t i = 0; i < N; ++i)
{
sgn_v1(float(std::rand() % 3) - 1.0);
}
std::cout << "v1 Time: " << (std::clock() - start) / (double)(CLOCKS_PER_SEC / 1000) << " ms " << std::endl;
}
我得到了以下结果:
g++-4.8 -std=c++11 test.cpp && ./a.out
v1 Time: 423.81 ms
v2 Time: 657.226 ms
v2 Time: 666.233 ms
v1 Time: 436.545 ms
因此,无分支的结果显然更好@Jim建议我研究分支预测器是如何工作的,但我仍然找不到计算管道"满"的方法…
通常情况下,时间是一种非常嘈杂的测量方法,尤其是当您在单个运行/进程中按顺序测量事物时,这意味着一个接一个的交错事件可能会增加噪声。正如您所提到的,分支对管道有很大影响,根据经验,分支较少的代码应该表现得更好,通常,对性能起作用的两个主要因素是参考位置和分支预测,而在更复杂的情况下,如使用多线程时,还有其他因素。为了回答您的问题,我想说,最好使用诸如perf之类的工具,例如,它可以指示缓存未命中的数量和分支未命中预测,这应该是一个很好的指示,通常,根据您正在开发的平台,您可能能够找到一个适当的工具来查询CPU的性能计数器。此外,您应该真正生成一组随机值,并在两个函数中使用完全相同的值,这样就可以消除std::rand()执行过程中的噪声。最后要记住,代码的性能会因编译器、编译选项(显然)和目标体系结构的不同而有所不同,但无论如何,您可以应用的一些逻辑都应该保持不变,因为在您的示例中,没有条件分支的代码应该总是表现得更好。如果你真的很挑剔,你应该仔细阅读英特尔的手册(尤其是avx)。
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 代码在main()中运行,但在函数中出现错误
- 在VS代码中交叉编译Windows与Linux上的MinGW的SDL程序
- 编译包含字符串的代码时遇到问题
- 我在c++代码中生成了一个运行时#3异常
- 如何在linux终端中同时编译和运行c++代码
- 为cl.exe(Visual Studio代码)指定命令行C++版本
- 在Linux for Windows上编译C++代码时出错
- 我的字符计数代码计算错误.为什么
- 孤立代码块在结构中引发异常
- 在编译C++代码(具有dlib和opencv)到WASM时面临问题
- 为什么我的C#代码在调用回C++COM直到Task时会暂停.等待/线程.加入
- 处理小于cpu数据总线的数据类型.(c++转换为机器代码)
- 此代码是否违反一个定义规则
- 为什么我的代码在输出中增加了93天
- 我的简单if-else语句是如何无法访问的代码
- 使用动态分配的数组会导致代码分析发出虚假的C6386缓冲区溢出警告
- 为什么在这个代码结束循环中没有得到结束
- 在c代码之间共享数据的最佳方式
- 如何分析cpp/assembly代码的性能