我想更多地了解如何分析我更经常使用的方法的性能表现。如何分析的CPP /汇编代码
我使用兰特()和时序大量调用我的方法作为绩效衡量的方法试过,但我也想了解更多有关如何通过了解汇编代码是干什么来衡量绩效。
例如,我读过关于试图优化sgn功能的人(Is there a standard sign function (signum, sgn) in C/C++?),所以我认为这将是一个很好的开始。我去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, %xmm2\n"
" vcmpgtss %xmm2, %xmm0, %xmm1\n"
" vcmpgtss %xmm0, %xmm2, %xmm3\n"
" vmovd %xmm1, %eax\n"
" vmovd %xmm3, %edx\n"
" negl %eax\n"
" negl %edx\n"
" subl %edx, %eax\n"
" ret\n"
);
extern "C" int sgn_v2(float);
__asm__(
"sgn_v2:\n"
" vxorps %xmm1, %xmm1, %xmm1\n"
" vcomiss %xmm1, %xmm0\n"
" ja ..B2.3\n"
" vcmpgtss %xmm0, %xmm1, %xmm0\n"
" vmovd %xmm0, %eax\n"
" ret\n"
" ..B2.3:\n"
" movl $1, %eax\n"
" ret\n"
);
#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
所以网点的结果显然更好; @吉姆的建议我看看分支预测器是如何工作的,但我仍然无法找到计算管道如何“满”的一种方式......
简单的方法:编写一些使用该函数的代码,并测量时间差。这应该给你一个第一和第二个功能之间的比例。另一种方法是使用类似Intel Vtune的分析器(或者Visual Studio 2013内置的分析器)。有一点需要注意的是,这可能会给你不同的结果**基于你正在使用的编译器,优化和cpu体系结构** – Raxvan 2014-09-18 16:15:40
无分支意味着没有跳转指令。这很好,因为如果CPU是流水线的,它可以一次执行多条指令。如果有分支,CPU可能不得不放弃它在管道中做的一些工作,如果它的分支预测不正确。 (http://en.wikipedia.org/wiki/Branch_predictor) – Jim 2014-09-18 17:52:20
你的'sgn_v1'和'sgn_v2'应该声明为'inline' – 2014-09-18 20:17:04