(如何)我可以使用LLVM机器代码分析器预测代码片段的运行时间
(How) can I predict the runtime of a code snippet using LLVM Machine Code Analyzer?
我使用llvm mca来计算代码pice的总周期,认为它们可以预测其运行时间。然而,动态测量运行时几乎没有显示出相关性。那么:为什么llvm mca计算的总周期不能准确预测运行时间?我可以用llvm-mca以更好的方式预测运行时吗
详细信息:
我想知道不同类型的begin
(和end
)迭代器的以下代码的运行时间,因为startValue
是0.0
或0ULL
:
std::accumulate(begin, end, starValue)
为了预测运行时,我使用了编译器资源管理器(https://godbolt.org/z/5HDzSF)它的LLVM机器代码分析器(LLVM-mca)插件,因为LLVM-mca是"一种性能分析工具,它使用LLVM中可用的信息(例如调度模型)来静态测量性能"。我使用了以下代码:
using vec_t = std::vector<double>;
vec_t generateRandomVector(vec_t::size_type size)
{
std::random_device rnd_device;
std::mt19937 mersenne_engine {rnd_device()};
std::uniform_real_distribution dist{0.0,1.1};
auto gen = [&dist, &mersenne_engine](){
return dist(mersenne_engine);
};
vec_t result(size);
std::generate(result.begin(), result.end(), gen);
return result;
}
double start()
{
vec_t vec = generateRandomVector(30000000);
vec_t::iterator vectorBegin = vec.begin();
vec_t::iterator vectorEnd = vec.end();
__asm volatile("# LLVM-MCA-BEGIN stopwatchedAccumulate");
double result = std::accumulate(vectorBegin, vectorEnd, 0.0);
__asm volatile("# LLVM-MCA-END");
return result;
}
然而,我发现llvm mca计算的总周期与运行相应std::accumulate的墙上时钟时间之间没有相关性。例如,在上面的代码中,总周期为2806,运行时间为14ms。当我切换到startValue0ULL
时,Total Cycles为2357,但运行时间为117ms。
TL:DR:LLVM-MCA分析了这些注释之间的整个代码块,就好像它是循环的主体一样,并向您显示了所有这些指令的100次迭代的循环计数
但是,除了实际的(微小的)循环外,大多数指令都是循环设置和循环后的SIMD水平和,实际上只运行一次。(这就是为什么对于带有double
累加器的0.0
版本,周期计数以千为单位,而不是400=Skylake上vaddpd
的4周期延迟的100倍。)
如果您取消选中Godbolt编译器资源管理器上的"//"框,或者修改asm语句以添加类似于"nop # LLVM-MCA-END"
的nop,您将能够在asm窗口中找到这些行,并查看LLVM-MCA在"循环"中看到的内容。
LLVM MCA模拟指定的汇编指令序列,并计算在指定的目标体系结构上每次迭代执行所需的周期数。LLVM MCA进行了一些简化,例如(我突然想到):(1)它假设所有的条件分支都通过,(2)它假定所有的内存访问都是回写内存类型,并且都在一级缓存中命中,(3)它假设前端工作最佳,(4)call
指令没有进入被调用的过程,它们只是通过。还有其他的假设,我现在记不起来了。
从本质上讲,LLVM MCA(像Intel IACA一样)只适用于后端计算绑定的简单循环。在IACA中,虽然支持大多数指令,但也有少数指令没有详细建模。例如,预取指令被假设只消耗微体系结构资源,但基本上没有延迟,并且对内存层次结构的状态没有影响。然而,在我看来,MCA完全无视这些指示。不管怎样,这与你的问题并不是特别相关。
现在回到您的代码。在您提供的编译器资源管理器链接中,您没有将任何选项传递给LLVM MCA。因此,默认的目标体系结构生效,也就是工具运行的任何体系结构。这恰好是SKX。您提到的周期总数是针对SKX的,但不清楚您是否在SKX上运行了代码。您应该使用-mcpu
选项来指定体系结构。这独立于您传递给gcc的-march
。还要注意,将核心周期与毫秒进行比较是没有意义的。您可以使用RDTSC
指令来测量核心周期的执行时间。
注意编译器是如何内联对std::accumulate
的调用的。显然,这个代码从汇编行405开始,std::accumulate
的最后一条指令在第444行,总共有38条指令。LLVM MCA估计与实际性能不匹配的原因现在已经很清楚了。该工具假设所有这些指令都在一个循环中执行大量迭代。显然情况并非如此。从420-424只有一个环路:
.L75:
vaddpd ymm0, ymm0, YMMWORD PTR [rax]
add rax, 32
cmp rax, rcx
jne .L75
只有此代码才应该是MCA的输入。在源代码级别,实际上没有办法告诉MCA只分析这些代码。您必须手动内联std::accumulate
,并将LLVM-MCA-BEGIN
和LLVM-MCA-END
标记放在其中的某个位置
当将0ULL
而不是0.0
传递到std::accumulate
时,LLVM MCA的输入将在汇编指令402处开始,并在441处结束。请注意,MCA不支持的任何指令(如vcvtsi2sdq
)将从分析中完全省略。实际处于循环中的代码部分是:
.L78:
vxorpd xmm0, xmm0, xmm0
vcvtsi2sdq xmm0, xmm0, rax
test rax, rax
jns .L75
mov rcx, rax
and eax, 1
vxorpd xmm0, xmm0, xmm0
shr rcx
or rcx, rax
vcvtsi2sdq xmm0, xmm0, rcx
vaddsd xmm0, xmm0, xmm0
.L75:
vaddsd xmm0, xmm0, QWORD PTR [rdx]
vcomisd xmm0, xmm1
vcvttsd2si rax, xmm0
jb .L77
vsubsd xmm0, xmm0, xmm1
vcvttsd2si rax, xmm0
xor rax, rdi
.L77:
add rdx, 8
cmp rsi, rdx
jne .L78
请注意,在目标地址位于块中某个位置的代码中存在条件跳转jns
。MCA只是假设跳跃会失败。如果在实际的代码运行中不是这样,MCA将不必要地增加7条指令的开销。还有另一个跳跃,jb
,但我认为这个跳跃对大向量来说并不重要,而且大部分时间都会下降。最后一个跳转jne
也是最后一条指令,因此MCA将再次假设下一条指令是顶部指令。对于足够多的迭代次数,这个假设是完全正确的。
总的来说,很明显,第一个代码比第二个小得多,所以可能要快得多。你的测量确实证实了这一点。您也不需要真正使用微体系结构分析工具来理解原因。第二段代码只是做了更多的计算。因此,您可以很快得出结论,在所有体系结构上,通过0.0
在性能和代码大小方面都更好。
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 代码在main()中运行,但在函数中出现错误
- 在VS代码中交叉编译Windows与Linux上的MinGW的SDL程序
- 编译包含字符串的代码时遇到问题
- 我在c++代码中生成了一个运行时#3异常
- Cppcheck 静态代码分析器实际上可以检测到不太常见的警告(如 "Relative Path Traversal (CWE-23)" 或"Buffer Under-read(CWE-127)")吗
- 将文本字符串作为常量字符 * 参数传递会导致代码分析器错误
- (如何)我可以使用LLVM机器代码分析器预测代码片段的运行时间
- 我们是否可以使用 g++ 编译代码通过 Solaris Studio 的性能分析器进行性能分析?
- C++背景代码分析器,如C#的Resharper
- 静态代码分析器不需要生成
- 需要帮助解决C++中扫描仪/词法分析器代码部分中的分割错误
- 文本分析器c++代码
- 生成独立C++代码的分析器生成器
- VS2012静态代码分析器在取消分配内存向量时给出误报
- 跳过Qt Valgrind函数分析器中的代码
- Linux中C++的静态代码分析器
- 在那里我可以得到在linux上运行sonarqube代码分析器的c++代码的步骤
- 搜索 C++ 代码分析器以查看所有签名
- Eclipse:编译,但 CODAN(内部代码分析器)给出C++ 11 <includes>中的函数错误