我在以下代码生成的可执行文件上运行valgrind --tool=callgrind ./executable:
#include <cstdlib>
#include <stdio.h>
using namespace std;
class XYZ{
public:
int Count() const {return count;}
void Count(int val){count = val;}
private:
int count;
};
int main() {
XYZ xyz;
xyz.Count(10000);
int sum = 0;
for(int i = 0; i < xyz.Count(); i++){
//My interest is to see how the compiler optimizes the xyz.Count() call
sum += i;
}
printf("Sum is %d\n", sum);
return 0;
}我使用以下选项构建了一个debug:-fPIC -fno-strict-aliasing -fexceptions -g -std=c++14。release构建具有以下选项:-fPIC -fno-strict-aliasing -fexceptions -g -O2 -std=c++14。
运行valgrind会生成两个转储文件。当在KCachegrind中查看这些文件(一个用于调试可执行文件,另一个用于发行可执行文件)时,调试构建是可以理解的,如下所示:

正如预期的那样,函数XYZ::Count() const被调用10001次。但是,优化的版本构建更难破译,并且不清楚函数被调用了多少次。我知道函数调用可能是inlined。但是,人们怎么知道它实际上已经被隐匿了呢?发布版本的调用图如下所示:

从XYZ::Count() const的角度看,似乎根本没有函数main()的迹象。
我的问题是:
(1)如果不查看调试/发布版本生成的汇编语言代码,并且使用KCachegrind,如何计算一个特定函数调用多少次(在本例中为XYZ::Count() const)?在上面的版本构建调用图中,该函数甚至不被调用一次。
(2)是否有办法了解KCachegrind为发布/优化构建提供的调用图和其他细节?我已经看过了KCachegrind手册在https://docs.kde.org/trunk5/en/kdesdk/kcachegrind/kcachegrind.pdf,但我想知道是否有一些有用的黑客/经验规则,人们应该寻找在发行版的构建。
发布于 2018-02-18 17:18:05
valgrind的输出很容易理解:正如valgrind+kcachegrind告诉您的那样,这个函数在版本构建中根本没有被调用。
问题是,你所谓的电话是什么意思?如果一个函数是内联的,它是否仍然“被调用”?实际上,情况要复杂得多,就像乍一看一样,你的例子也不是那么简单。
Count()在发行版构建中有内联吗?当然,算是吧。在优化过程中,代码转换通常是非常显著的,就像在您的案例中一样--最好的判断方法是查看结果的汇编程序 (这里是clang):
main: # @main
pushq %rax
leaq .L.str(%rip), %rdi
movl $49995000, %esi # imm = 0x2FADCF8
xorl %eax, %eax
callq printf@PLT
xorl %eax, %eax
popq %rcx
retq
.L.str:
.asciz "Sum is %d\n"您可以看到,main根本不执行for-循环,而是打印结果(49995000),这是在优化期间计算的,因为在编译期间迭代的次数是已知的。
那么Count()是内联的吗?是的,在优化的第一步中的某个地方,但是代码变得完全不同了--在最终的汇编程序中没有Count()被内联的地方。
那么,当我们对编译器“隐藏”迭代次数时,会发生什么呢?例如通过命令行传递:
...
int main(int argc, char* argv[]) {
XYZ xyz;
xyz.Count(atoi(argv[1]));
...在结果的汇编程序中,我们仍然没有遇到for-循环,因为优化器可以知道,Count()的调用没有副作用,因此优化了整个过程:
main: # @main
pushq %rbx
movq 8(%rsi), %rdi
xorl %ebx, %ebx
xorl %esi, %esi
movl $10, %edx
callq strtol@PLT
testl %eax, %eax
jle .LBB0_2
leal -1(%rax), %ecx
leal -2(%rax), %edx
imulq %rcx, %rdx
shrq %rdx
leal -1(%rax,%rdx), %ebx
.LBB0_2:
leaq .L.str(%rip), %rdi
xorl %eax, %eax
movl %ebx, %esi
callq printf@PLT
xorl %eax, %eax
popq %rbx
retq
.L.str:
.asciz "Sum is %d\n"优化器给出了求和(n-1)*(n-2)/2的公式i=0..n-1!
现在让我们将Count()的定义隐藏在一个单独的翻译单元class.cpp中,这样优化器就无法看到它的定义:
class XYZ{
public:
int Count() const;//definition in separate translation unit
...现在,我们在每次迭代中都得到了for-循环和对Count()的调用,汇编程序最重要的部分是:
.L6:
addl %ebx, %ebp
addl $1, %ebx
.L3:
movq %r12, %rdi
call XYZ::Count() const@PLT
cmpl %eax, %ebx
jl .L6在每个迭代步骤中,Count()的结果(在%rax中)与当前计数器(在%ebx中)进行比较。现在,如果我们以val差制运行它,我们可以在被调用的列表中看到,XYZ::Count()被称为10001 times。
然而,对于现代工具链来说,仅仅看到单个翻译单元的汇编程序是不够的,还有一种叫做link-time-optimization的东西。我们可以通过沿着这条线建造一些地方来使用它:
gcc -fPIC -g -O2 -flto -o class.o -c class.cpp
gcc -fPIC -g -O2 -flto -o test.o -c test.cpp
gcc -g -O2 -flto -o test_r class.o test.o我们再次看到,没有调用Count()!
然而,看看机器代码(这里我使用了gcc,我的clang-安装似乎与lto有问题):
00000000004004a0 <main>:
4004a0: 48 83 ec 08 sub $0x8,%rsp
4004a4: 48 8b 7e 08 mov 0x8(%rsi),%rdi
4004a8: ba 0a 00 00 00 mov $0xa,%edx
4004ad: 31 f6 xor %esi,%esi
4004af: e8 bc ff ff ff callq 400470 <strtol@plt>
4004b4: 85 c0 test %eax,%eax
4004b6: 7e 2b jle 4004e3 <main+0x43>
4004b8: 89 c1 mov %eax,%ecx
4004ba: 31 d2 xor %edx,%edx
4004bc: 31 c0 xor %eax,%eax
4004be: 66 90 xchg %ax,%ax
4004c0: 01 c2 add %eax,%edx
4004c2: 83 c0 01 add $0x1,%eax
4004c5: 39 c8 cmp %ecx,%eax
4004c7: 75 f7 jne 4004c0 <main+0x20>
4004c9: 48 8d 35 a4 01 00 00 lea 0x1a4(%rip),%rsi # 400674 <_IO_stdin_used+0x4>
4004d0: bf 01 00 00 00 mov $0x1,%edi
4004d5: 31 c0 xor %eax,%eax
4004d7: e8 a4 ff ff ff callq 400480 <__printf_chk@plt>
4004dc: 31 c0 xor %eax,%eax
4004de: 48 83 c4 08 add $0x8,%rsp
4004e2: c3 retq
4004e3: 31 d2 xor %edx,%edx
4004e5: eb e2 jmp 4004c9 <main+0x29>
4004e7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1)我们可以看到,对函数Count()的调用是内联的,但是-还有一个for-循环(我猜这是gcc对clang的调用)。
但是您最感兴趣的是:函数Count()只被“调用”一次-它的值保存到注册%ecx,而循环实际上是:
4004c0: 01 c2 add %eax,%edx
4004c2: 83 c0 01 add $0x1,%eax
4004c5: 39 c8 cmp %ecx,%eax
4004c7: 75 f7 jne 4004c0 <main+0x20>这也是在Kcachegrid的帮助下所能看到的,如果valgrind是使用选项‘--dump-instr=yes运行的。
发布于 2018-02-14 12:44:25
在callgrind.out文件中搜索XYZ::Count(),查看valgrind是否记录了该函数的任何事件。
grep "XYZ::Count()" callgrind.out | more如果您在回调文件中找到函数名,那么重要的是要知道kcache差事隐藏函数的权重很小。参见:在kcache差使调用图中显示所有函数调用的答案
https://stackoverflow.com/questions/48779081
复制相似问题