目录

5.1。概观5.2。使用Cachegrind,cg_annotate和cg_merge
5.2.1。运行Cachegrind5.2.2。输出文件5.2.3。运行cg_annotate5.2.4。输出序言5.2.5。全局和功能级别计数5.2.6。逐行计数5.2.7。注释汇编代码程序5.2.8。分岔程序5.2.9。cg_annotate警告5.2.10。异常注释案例5.2.11。将配置文件与cg_merge合并5.2.12。带cg_diff的差异配置文件
5.3。Cachegrind命令行选项5.4。cg_annotate命令行选项5.5。cg_merge命令行选项5.6。cg_diff命令行选项5.7。代表Cachegrind的信息5.8。模拟细节
5.8.1。缓存模拟细节5.8.2。分支模拟细节5.8.3。准确性
5.9。实施细节
5.9.1。Cachegrind如何工作5.9.2。Cachegrind输出文件格式

要使用此工具,必须--tool=cachegrind在Valgrind命令行上指定 。

5.1。概观

Cachegrind模拟程序如何与机器的缓存层次结构和(可选)分支预测器进行交互。它模拟具有独立的第一级指令和数据高速缓存(I1和D1)的机器,由统一的二级缓存(L2)支持。这完全符合许多现代机器的配置。

然而,一些现代机器具有三或四级缓存。对于这些机器(在Cachegrind可以自动检测缓存配置的情况下)Cachegrind模拟一级和最后一级缓存。这个选择的原因是最后一级缓存对运行时的影响最大,因为它掩盖了对主内存的访问。此外,L1高速缓存通常具有低关联性,因此模拟它们可以检测代码与该高速缓存交互不良的情况(例如,以行长为2的行列列逐列)。

因此,Cachegrind总是引用I1,D1和LL(最后一级)缓存。

Cachegrind收集以下统计信息(括号中给出每个统计量使用的缩写):

  • 我缓存读取(Ir等于执行的指令数),I1缓存读取未命中(I1mr)和LL缓存指令读取misses(ILmr)。

  • D缓存读取(Dr等于内存读取次数),D1缓存读取未命中(D1mr)和LL缓存数据读取misses(DLmr)。

  • D缓存写入(Dw等于存储器写入数),D1缓存写入未命中(D1mw)和LL缓存数据写入未命中(DLmw)。

  • 条件分支执行(Bc)和条件分支mispredicted(Bcm)。

  • 间接分支执行(Bi)和间接分支错误(Bim)。

请注意,D1总访问由D1mr+ 给出 D1mw,LL总访问由ILmrDLmr+ 给出DLmw

为整个程序和程序中的每个功能提供这些统计信息。您还可以使用直接导致的计数来注释程序中的每一行源代码。

在现代机器上,L1故障通常会花费大约10个周期,LL错误可能花费多达200个周期,并且在10到30个周期的区域中,错误的分支成本。详细的缓存和分支分析可以非常有助于了解您的程序如何与机器交互,从而如何使其更快。

另外,由于每个执行指令都执行了一条指令高速缓存读取,您可以找出每行执行的指令数,这对于传统的分析来说是有用的。

5.2。使用Cachegrind,cg_annotate和cg_merge

首先,对于正常的Valgrind使用,您可能希望使用调试信息(-g选项)编译 。但是与正常的Valgrind使用相比,您可能想要优化,因为您应该对程序进行配置,因为它将正常运行。

然后,您需要运行Cachegrind本身来收集分析信息,然后运行cg_annotate来获取该信息的详细介绍。作为可选的中间步骤,您可以使用cg_merge将多个Cachegrind运行的输出合并到一个文件中,然后将其用作cg_annotate的输入。或者,您可以使用cg_diff将两个Cachegrind运行的输出区分为一个文件,然后将其用作cg_annotate的输入。

5.2.1。运行Cachegrind

要在程序上运行Cachegrind prog,请运行:

valgrind --tool = cachegrind prog

程序将执行(缓慢)。完成后,将打印如下的摘要统计信息:

== 31751 ==我参考:27,742,716
== 31751 == I1 misses:276
== 31751 == LLi misses:275
== 31751 == I1 miss rate:0.0%
== 31751 == LLi miss rate:0.0%
== == 31751
== 31751 == D refs:15,430,290(10,955,517 rd + 4,474,773 wr)
== 31751 == D1 misses:41,185(21,905 rd + 19,280 wr)
== 31751 == LLd misses:23,085(3,987 rd + 19,098 wr)
== 31751 == D1失误率:0.2%(0.1%+ 0.4%)
== 31751 ==差错率:0.1%(0.0%+ 0.4%)
== == 31751
== 31751 == LL misses:23,360(4,262 rd + 19,098 wr)
== 31751 == LL错失率:0.0%(0.0%+ 0.4%)

首先总结指令读取的高速缓存访​​问,给出取得的数量(这是执行的指令数,这可以用于知道自己的权限),I1的次数以及LL指令的数量(LLi)未命中。

缓存访问数据跟随。信息类似于指令提取的信息,除了这些值还显示在读取和写入之间分开(注意每行 rd和 wr值都加在行的总数上)。

LL缓存的组合指令和数据数据就是这样。注意,相对于存储器访问的总数而不是L1未命中的数量来计算LL错误率。也就是 (ILmr + DLmr + DLmw) / (Ir + Dr + Dw) 不是 (ILmr + DLmr + DLmw) / (I1mr + D1mr + D1mw)

默认情况下不收集分支预测统计信息。为此,请添加该选项--branch-sim=yes

5.2.2。输出文件

除了打印摘要信息,Cachegrind还将更详细的剖析信息写入文件。默认情况下,该文件被命名 cachegrind.out.<pid><pid>程序的进程ID 在哪里 ),但可以使用该--cachegrind-out-file选项更改其名称。该文件是人类可读的,但意图由下一节中描述的随附程序cg_annotate解释。

.<pid>输出文件名中的默认后缀有两个目的。首先,这意味着您不必重命名不想覆盖的旧日志文件。其次,更重要的是,它允许使用--trace-children=yes生成子进程的程序选项进行正确的 分析。

输出文件可以是大的,对于大量应用程序构建完整调试信息的兆字节数。

5.2.3。运行cg_annotate

在使用cg_annotate之前,如果可能,扩展窗口至少应该是120个字符,因为输出行可能相当长。

要获取功能函数摘要,请运行:

cg_annotate <filename>

在Cachegrind输出文件上。

5.2.4。输出序言

输出的第一部分如下所示:

-------------------------------------------------- ------------------------------
I1缓存:65536 B,64 B,双向关联
D1缓存:65536 B,64 B,双向关联
LL缓存:262144 B,64 B,8路关联
命令:concord vg_to_ucode.c
事件记录:Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
事件显示:Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
事件排序顺序:Ir I1mr ILmr Dr D1mr DLmr Dw D1mw DLmw
门槛值:99%
选择注释:
自动注释:关闭

这是注释选项的摘要:

  • I1缓存,D1缓存,LL缓存:缓存配置。所以你知道获得这些结果的配置。

  • 命令:命令行调用正在检查的程序。

  • 记录的事件:记录了哪些事件。

  • 显示的事件:显示的事件,这是收集的事件的一个子集。这可以通过--show选项进行调整 。

  • 事件排序顺序:显示函数的排序顺序。例如,在这种情况下,功能将从最高Ir计数排序到最低。如果两个函数具有相同的Ir计数,则它们将按 计数排序I1mr,依此类推。可以使用--sort选项调整此顺序 。

    请注意,这表示函数出现的顺序。这不是列出现的顺序。这是由“显示的事件”行(可以随--show 选项更改)所规定的。

  • 阈值:默认情况下cg_annotate忽略导致非常低计数的功能,以避免淹没您的信息。在这种情况下,cg_annotate显示了占Ir计数的99%的函数的摘要; Ir被选为阈值事件,因为它是主要排序事件。可以使用--threshold 选项调整阈值 。

  • 选择注释:手动指定用于注释的文件的名称; 在这种情况下没有。

  • 自动注释:是否通过--auto=yes 选项请求自动注释。在这种情况下没有。

5.2.5。全局和功能级别计数

然后按照整个程序的总结统计:

-------------------------------------------------- ------------------------------
Irlmr ILmr Dr D1mr DLmr Dw D1mw DLmw
-------------------------------------------------- ------------------------------
27,742,716 276 275 10,955,517 21,905 3,987 4,474,773 19,280 19,098计划总额

这些与Cachegrind完成运行时提供的摘要类似。

然后来函数统计:

-------------------------------------------------- ------------------------------
Irlmr ILmr Dr D1mr DLmr Dw D1mw DLmw文件:功能
-------------------------------------------------- ------------------------------
8,821,482 5 5 2,242,702 1,621 73 1,794,230 0 0 getc.c:_IO_getc
5,222,023 4 4 2,276,334 16 12 875,959 1 1 concord.c:get_word
2,649,248 2 2 1,344,810 7,326 1,385。。。vg_main.c:STRCMP
2,521,927 2 2 591,215 0 0 179,398 0 0 concord.c:哈希
2,242,740 2 2 1,046,612 568 22 448,548 0 0 ctype.c:tolower
1,496,937 4 4 630,874 9,000 1,400 279,388 0 0 concord.c:insert897,991 51 51 897,831 95 30 62 1 1 ???598,068 1 1 299,034 0 0 149,517 0 0 ../sysdeps/generic/lockfile.c:__flockfile598,068 0 0 299,034 0 0 149,517 0 0 ../sysdeps/generic/lockfile.c:__funlockfile598,024 4 4 213,580 35 16 149,506 0 0 vg_clientmalloc.c:malloc446,587 1 1 215,973 2,167 430 129,948 14,057 13,957 concord.c:add_existing341,760 2 2 128,160 0 0 128,160 0 0 vg_clientmalloc.c:vg_trap_here_WRAPPER320,782 4 4 150,711 276 0 56,027 53 53 concord.c:init_hash_table298,998 1 1 106,785 0 0 64,071 1 1 concord.c:create149,518 0 0 149,516 0 0 1 0 0 ???:tolower @@ GLIBC_2.0149,518 0 0 149,516 0 0 1 0 0 ????:fgetc @@ GLIBC_2.095,983 4 4 38,031 0 0 34,409 3,152 3,150 concord.c:new_word_node85,440 0 0 42,720 0 0 21,360 0 0 vg_clientmalloc.c:vg_bogus_epilogue

每个功能由一file_name:function_name对标识 。如果列只包含一个点,则表示该函数不会执行该事件(例如,第三行显示不 strcmp()包含写入内存的指令)。这个名字 ???是用来如果文件名称和/或函数的名称无法从调试信息来确定。如果大多数条目具有该???:???程序可能未被编译的形式 -g

值得注意的是,功能将来自concord.c于类别程序(例如)和库(例如getc.c

5.2.6。逐行计数

有两种注释源文件的方法 - 通过将其手动指定为cg_annotate的参数或使用该 --auto=yes选项。例如,cg_annotate <filename> concord.c对于我们的示例,运行的输出 产生与上述相同的输出,后跟一个注释版本 concord.c,其中的一部分看起来像:

-------------------------------------------------- ------------------------------
- 用户注释源:concord.c
-------------------------------------------------- ------------------------------
Irlmr ILmr Dr D1mr DLmr Dw D1mw DLmw。。。。。。。。。void init_hash_table(char * file_name,Word_Node * table [])3 1 1。。。1 0 0 {。。。。。。。。。FILE * file_ptr;。。。。。。。。。Word_Info *数据;1 0 0。。。1 1 1 int line = 1,i;。。。。。。。。。5 0 0。。。3 0 0 data =(Word_Info *)create(sizeof(Word_Info));。。。。。。。。。(i = 0; i <TABLE_SIZE; i ++)4,991 0 0 1,995 0 0 998 0 03,988 1 1 1,994 0 0 997 53 52 table [i] = NULL;。。。。。。。。。。。。。。。。。。/ *打开文件,检查。* /6 0 0 1 0 0 4 0 0 file_ptr = fopen(file_name,“r”);2 0 0 1 0 0。。。if(!(file_ptr)){。。。。。。。。。fprintf(stderr,“无法打开'%s'。\ n”,file_name);1 1 1。。。。。。出口(EXIT_FAILURE);。。。。。。。。。}。。。。。。。。。165,062 1 1 73,360 0 0 91,700 0 0 while((line = get_word(data,line,file_ptr))!= EOF)146,712 0 0 73,356 0 0 73,356 0 0 insert(data  - >; word,data-> line,table);。。。。。。。。。4 0 0 1 0 0 2 0 0免费(数据);4 0 0 1 0 0 2 0 0 fclose(file_ptr);3 0 0 2 0 0。。。}

(尽管柱宽度自动最小化,但广泛的终端显然是有用的。)

每个源文件被清楚地标记为(User-annotated source)被手动选择用于注释。如果在-I--includeoptions中指定的目录之一中找到该文件 ,则会同时给出目录和文件。

每一行都用事件计数进行注释。不适用于一行的事件由点表示。这对于区分不能发生的事件和可以但不能发生的事件是有用的。

有时只执行一小部分源文件。为了最小化不感兴趣的输出,Cachegrind仅在注释行的一小段距离内显示注释行和行。间隙标有行号,以便您知道显示的代码来自哪个文件的哪个部分,例如:

(线704的图和代码)
- 第704行----------------------------------------
-  878行----------------------------------------
(第878行的数字和代码)

在注释行显示的上下文的数量由--context 选项控制。

要获取自动注释,请使用该--auto=yes选项。cg_annotate会自动注释每个函数摘要中提到的每个源文件。因此,为自动注释选择的文件会受到--sort和 --threshold选项的影响。每个源文件被清楚地标记为(Auto-annotated source)被自动选择。在输出结尾处提到无法找到的任何文件,例如:

-------------------------------------------------- ----------------
找不到以下选择自动注释的文件:
-------------------------------------------------- ----------------getc.cctype.c../sysdeps/generic/lockfile.c

这对于库文件是很常见的,因为库通常使用调试信息进行编译,但源文件通常不在系统中。如果选择了手动和自动注释的文件,则将其标记为User-annotated source。如果从调试信息中找到的文件名不够详细,请使用 -I--include选项告诉Valgrind寻找源文件的位置。

请注意,cg_annotate可能需要一些时间来消化大 cachegrind.out.<pid>文件,例如30秒或更长。还要注意,如果您的程序很大,自动注释可以产生大量的输出!

5.2.7。注释汇编代码程序

Valgrind也可以注释汇编代码程序,也可以注释为C程序生成的汇编代码。有时,这有助于了解当有趣的C代码行被转换成多个指令时,真正发生了什么。

为此,您只需要.s使用程序集级调试信息来汇编 文件。您可以使用compile将-SC / C ++程序编译为汇编代码,然后组装汇编代码文件 -g以实现此目的。然后,您可以以与C / C ++源文件相同的方式对汇编代码源文件进行配置和注释。

5.2.8。分岔程序

如果您的程序分叉,该子将继承所有为父级收集的分析数据。

如果输出文件格式字符串(受控制 --cachegrind-out-file)不包含%p,则父和子的输出将混合在单个输出文件中,这几乎肯定会使其不能被cg_annotate读取。

5.2.9。cg_annotate警告

有几种情况,cg_annotate发出警告。

  • 如果源文件比 cachegrind.out.<pid>文件更新。这是因为信息仅以 cachegrind.out.<pid>行号记录,因此如果行号在源中完全变化(例如添加,删除,交换的行),任何注释将不正确。

  • 如果记录关于文件末尾的行号的信息。这可能是由于上述问题引起的,即在使用旧cachegrind.out.<pid>文件时缩短源 文件。如果发生这种情况,虚线的数字打印(清楚地标记为假),以防重要。

5.2.10。异常注释案例

在注释期间可能发生的一些奇怪的事情:

  • 如果在汇编级别注释,您可能会看到如下:

          1 0 0。。。。。。leal -12(%ebp),%eax1 0 0。。。1 0 0 movl%eax,84(%ebx)2 0 0 0 0 0 1 0 0 movl $ 1,-20(%ebp)。。。。。。。。。.align 4,0x901 0 0。。。。。。movl $ .LnrB,%eax1 0 0。。。1 0 0 movl%eax,-16(%ebp)

    当其他人只执行一次时,如何执行第三条指令两次?事实证明,不是。这是可执行文件的转储,使用 objdump -d

          8048f25:8d 45 f4 lea 0xfffffff4(%ebp),%eax8048f28:89 43 54 mov%eax,0x54(%ebx)8048f2b:c7 45 ec 01 00 00 00 movl $ 0x1,0xffffffec(%ebp)8048f32:89 f6 mov%esi,%esi8048f34:b8 08 8b 07 08 mov $ 0x8078b08,%eax8048f39:89 45 f0 mov%eax,0xfffffff0(%ebp)

    注意额外的mov %esi,%esi说明。这是从哪里来的?GNU汇编器将其插入作为movl $.LnrB,%eax四字节边界对齐指令所需的两个字节的填充,但假设它在添加调试信息时不存在。因此,当Valgrind读取调试信息时,它认为该movl $0x1,0xffffffec(%ebp)指令本身覆盖地址范围0x8048f2b - 0x804833,并将其计数归因mov %esi,%esi于它。

  • 有时,相同的文件名可能用相对名称表示,并且在调试信息的不同部分中使用绝对名称,例如: /home/user/proj/proj.h和 ../proj.h。在这种情况下,如果您使用自动注释,则文件将被注释两次,并在两者之间进行分割。

  • 如果你编译一些没有的文件 -g,一些在没有调试信息的文件中发生的事件可以归因于具有调试信息的文件的最后一行(无论哪个文件放在可执行文件中的非调试信息文件之前) )。

这个列表看起来很长,但是这些例子应该是相当罕见的。

5.2.11。将配置文件与cg_merge合并

cg_merge是一个简单的程序,它读取由Cachegrind创建的多个配置文件,将它们合并在一起,并以相同的格式将结果写入另一个文件。然后,您可以使用cg_annotate <filename>如上所述来检查合并的结果 。如果您希望通过同一程序的多次运行来汇总成本,或者从具有相同程序的多个实例的单个并行运行中合并功能可能会很有用。

cg_merge被调用如下:

cg_merge -o outputfile file1 file2 file3 ...

它读取和检查file1,然后读取和检查file2并将其合并到运行总计中,然后与file3等同等 。最终结果将写入outputfile或标准输出,如果没有指定输出文件。

成本按每个功能,每行和每个指令进行总结。因为这样,输入文件的顺序不重要,虽然你应该注意只提一次文件,因为提到的两个文件将被添加两次。

cg_merge不会尝试检查输入文件是否来自相同可执行文件的运行。它将很乐意将来自完全不相关程序的配置文件合并在一起。然而,它检查Events:所有输入的 线条是否相同,以确保增加成本是有意义的。例如,将一个数字表示为从一个指定LL写入未命中的不同文件中的一个数字的数字的数字将是无意义的。

在阅读输入时,还会完成许多其他语法和理性检查。如果任何输入文件失败,cg_merge将停止并尝试打印有用的错误消息。

5.2.12。带cg_diff的差异配置文件

cg_diff是一个简单的程序,它读取由Cachegrind创建的两个配置文件,找到它们之间的区别,并以相同的格式将结果写入另一个文件。然后,您可以使用cg_annotate <filename>如上所述来检查合并的结果 。如果您想衡量一个程序的更改如何影响其性能,这是非常有用的。

cg_diff被调用如下:

cg_diff file1 file2

它读取和检查file1,然后读取和检查file2,然后计算差异(有效file1file2)。最终结果写入标准输出。

费用是按功能计算的。每行费用并不总计,因为这样做太难了。例如,考虑区分两个配置文件,一个来自单个文件程序A,另一个来自同一程序A,其中在文件顶部插入单个空行。每一行一行计数都发生了变化。相比之下,每个功能的计数没有改变。每个函数的计数差异对于确定程序之间的差异仍然非常有用。注意,因为结果是两个轮廓的差异,许多计数将为负数; 这表示第二版中相关功能的计数少于第一版中的计数。

cg_diff不会尝试检查输入文件是否来自相同可执行文件的运行。它将很乐意将来自完全不相关程序的配置文件合并在一起。然而,它检查Events:所有输入的 线条是否相同,以确保增加成本是有意义的。例如,将一个数字表示为从一个指定LL写入未命中的不同文件中的一个数字的数字的数字将是无意义的。

在阅读输入时,还会完成许多其他语法和理性检查。如果任何输入文件失败,cg_diff将停止并尝试打印有用的错误消息。

有时你会想要比较并排坐的两个版本的程序的Cachegrind配置文件。例如,你可能有 version1/prog.c和 version2/prog.c,其中第二个是第一个稍有不同。两者的直接比较将不是有用的 - 因为函数使用文件名进行限定,所以第一个版本 的功能 f将被列为 第二个版本。version1/prog.c:fversion2/prog.c:f

发生这种情况时,可以使用该--mod-filename选项。它的参数是Perl搜索和替换表达式,它将应用于Cachegrind输出文件中的所有文件名。它可用于删除文件名中的微小差异。例如,这个选项 --mod-filename='s/version[0-9]/versionN/'就足够了。

类似地,有时编译器自动生成某些功能并给它们随机化的名称。例如,GCC有时会自动生成名称类似的函数,T.1234后缀因构建而异。您可以使用该--mod-funcname选项来删除像这样的小差异; 它的工作方式与 --mod-filename

5.3。Cachegrind命令行选项

Cachegrind特定的选项是:

--I1=<size>,<associativity>,<line size>

指定级别1指令高速缓存的大小,关联度和行大小。

--D1=<size>,<associativity>,<line size>

指定1级数据缓存的大小,关联度和行大小。

--LL=<size>,<associativity>,<line size>

指定最后一级缓存的大小,关联性和行大小。

--cache-sim=no|yes [yes]

启用或禁用缓存访问和错误计数的收集。

--branch-sim=no|yes [no]

启用或禁用分支指令和错误预测计数的收集。默认情况下,这是禁用的,因为Cachegrind下降了大约25%。请注意,您无法指定--cache-sim=no 并--branch-sim=no 在一起,因为这将使Cachegrind无法收集信息。

--cachegrind-out-file=<file>

将配置文件数据写入 file而不是默认的输出文件 cachegrind.out.<pid>。的 %p%q格式说明可以用来嵌入进程ID和/或名称的环境变量的内容,由于是用于芯选项的情况下--log-file

5.4。cg_annotate命令行选项

-h --help

显示帮助信息。

--version

显示版本号。

--show=A,B,C [default: all, using order in cachegrind.out.<pid>]

指定要显示的事件(和列顺序)。默认是使用cachegrind.out.<pid>文件中的所有 文件(并使用文件中的顺序)。如果你想集中精力,比如我缓存misses(--show=I1mr,ILmr)或数据读取misses(--show=D1mr,DLmr)或LL数据丢失(--show=DLmr,DLmw)。最适合使用 --sort

--sort=A,B,C [default: order in cachegrind.out.<pid>]

指定逐个功能条目排序的事件。

--threshold=X [default: 0.1%]

设置函数功能摘要的阈值。如果占用主排序事件的计数的X%以上,则显示一个函数。如果自动注释,还会影响哪些文件被注释。

注意:可以为多个事件设置阈值,方法是附加--sort冒号和数字的任何事件 (不含空格)。例如,如果您想查看涵盖超过1%LL读取错误或1%LL写入未命中的每个功能,请使用此选项:

--sort=DLmr:1,DLmw:1

--auto=<no|yes> [default: no]

启用后,会自动注释可以找到的逐个功能摘要中提到的每个文件。还列出了无法找到的列表。

--context=N [default: 8]

在每个注释行之前和之后打印N行上下文。避免打印未执行的大部分源文件。使用大量(例如100000)显示所有源行。

-I<dir> --include=<dir> [default: none]

将目录添加到搜索文件的列表中。多-I--include 选项可以给添加多个目录。

5.5。cg_merge命令行选项

-o outfile

将配置文件数据写入outfile 而不是标准输出。

5.6。cg_diff命令行选项

-h --help

显示帮助信息。

--version

显示版本号。

--mod-filename=<expr> [default: none]

指定应用于所有文件名的Perl搜索和替换表达式。用于消除位于不同目录的程序的两个不同版本之间的路径中的小差异。

--mod-funcname=<expr> [default: none]

喜欢--mod-filename,但为文件名。有助于消除一些编译器生成的自动生成函数的随机化名称中的微小差异。

5.7。代表Cachegrind的信息

Cachegrind给你很多信息,但是对这些信息的处理并不总是容易的。以下是我们发现有用的一些经验法则。

首先,全球命中/错失次数和错失率并不那么有用。如果您有多个程序或多个程序运行,则比较数字可能会识别是否有异常值并值得进行仔细调查。否则,他们还不够行事。

查看功能的功能计数更有用,因为它们确定哪些功能导致大量计数。但是,请注意,内联可以使这些计数产生误导。如果函数 f总是内联,则计数将归因于其内联的功能,而不是其本身。但是,如果您查看逐行注释,f您将看到属于的计数f。(这是很难避免的,这是调试信息的结构。)所以值得寻找大量的逐行注释。

逐行源代码注释更加有用。根据我们的经验,最好的开始是看 Ir数字。它们简单地测量每行执行多少指令,并且不包括任何缓存信息,但是它们仍然可以用于识别瓶颈。

之后,我们发现,LL错误通常是比L1错失更大的减速来源。所以值得寻找任何高代码DLmr或 代码片段DLmw。(例如,您可以使用 --show=DLmr --sort=DLmrcg_annotate来重点关注数字 DLmr。)如果找到任何内容,那么解决问题仍然不容易。您需要对缓存的工作原理,本地原则以及程序的数据访问模式有一个合理的了解。改进事情可能需要重新设计数据结构。

看着Bcm和 Bim错过也可以是有帮助的。特别地,Bim错误通常是由switch语句引起的,在某些情况下,这些 switch语句可以被表驱动代码替代。例如,您可能会替换如下代码:

枚举E {A,B,C};
枚举E e
int i
...
开关(e)
{情况A:i + = 1; 打破;情况B:i + = 2; 打破;情况C:i + = 3; 打破;
}

代码如下:

枚举E {A,B,C};
枚举E e
枚举E表[] = {1,2,3};
int i
...
i + =表[e];

这显然是一个例证,但基本原则适用于各种情况。

简而言之,Cachegrind可以告诉你代码中的一些瓶颈,但是它不能告诉你如何解决它们。你必须为自己工作。但至少你有信息!

5.8。模拟细节

本节将介绍您不需要了解的细节,以便使用Cachegrind,但有些人可能会感兴趣。

5.8.1。缓存模拟细节

缓存模拟的具体特点如下:

  • 写分配:当写入未命中时,写入的块被带入D1高速缓存。大多数现代高速缓存有此属性。

  • 位选择散列函数:由字节地址的中间位M - (M + N-1)选择存储器块映射到的高速缓存中的行集合,其中:

    • 行大小= 2 ^ M字节

    • (高速缓存大小/行大小/关联性)= 2 ^ N个字节

  • 包含LL缓存:LL缓存通常复制L1高速缓存的所有条目,因为取入L1涉及首先提取LL(这不保证严格的包容性,因为从LL中移除的行仍然可以驻留在L1中)。这是Pentium芯片的标准配置,但是AMD Opterons,Athlons和Durons使用的唯一LL缓存仅保留从L1驱逐的块。同时也是最现代的VIA CPU。

使用x86 CPUID指令自动确定模拟的缓存配置(缓存大小,关联性和行大小)。如果您有一台机器,(a)不支持CPUID指令,或(b)在不提供任何缓存信息的早期化身中支持,那么Cachegrind将回退到使用默认配置3/4速龙)。Cachegrind会告诉你是否发生这种情况。可以手动指定的命令行的高速缓存中的一个,两个或所有三个级别(I1 / D1 / LL)使用 --I1, --D1和 --LL选项。对于缓存参数对模拟有效,集合的数量(每个组中的高速缓存行数量的关联性)必须是2的幂。

在PowerPC平台Cachegrind不能自动确定缓存配置,所以你将需要与指定它 --I1, --D1和 --LL选项。

其他值得注意的行为:

  • 跨越两条缓存行的引用如下处理:

    • 如果两个块都击中 - >计数为一个命中

    • 如果一个块命中,另一个错过 - >算作一个小姐。

    • 如果两个块都错过 - >计数为一个错过(不是两个)

  • 修改存储器位置(例如inc和 dec)的指令被视为仅仅读取,即单个数据引用。这可能看起来很奇怪,但是由于写入永远不会导致错过(读取保证块位于缓存中),这并不是很有趣。

    因此,它不会测量数据高速缓存被访问的次数,而是数据高速缓存未命中的次数。

如果您有兴趣模拟具有不同属性的缓存,编写自己的缓存模拟器或修改现有缓存模拟器并不是特别难 cg_sim.c。我们有兴趣从任何人那里听到。

5.8.2。分支模拟细节

Cachegrind模拟了大概在2004年左右的主流桌面/服务器处理器的分支预测器。

使用16384个2位饱和计数器的数组来预测条件分支。用于分支指令的数组索引部分地由分支指令的地址的低位进行计算,并且部分地使用最后几个条件分支的采取/未采取的行为。因此,任何具体分支的预测都取决于它自己的历史和以前分支的行为。这是提高预测精度的标准技术。

对于间接分支(即跳转到未知目的地),Cachegrind使用一个简单的分支目标地址预测器。使用由分支指令地址的低位9位索引的512个条目的数组预测目标。预计每个分支将跳到与上次相同的地址。任何其他行为都会导致错误预测。

最近的处理器具有更好的分支预测因子,特别是更好的间接分支预测器。Cachegrind的预测器设计是故意保守的,以便代表大型安装的处理器的基础,预先广泛部署更复杂的间接分支预测器。具体来说,晚期型号Pentium 4(Prescott),Pentium M,Core和Core 2具有比Cachegrind建模的更复杂的间接分支预测器。

Cachegrind不会模拟一个返回栈预测器。它假设处理器完美地预测函数返回地址,这个假设可能接近于true。

参见轩尼诗和帕特森的经典文本“计算机体系结构:定量方法”,第4版(2007),第2.3节(第80-89页),用于现代分支预测器的背景。

5.8.3。准确性

Valgrind的缓存分析有一些缺点:

  • 它不考虑内核活动 - 忽略系统调用对缓存和分支预测器内容的影响。

  • 它不考虑其他流程活动。考虑单个程序时,这可能是可取的。

  • 它不考虑虚拟到物理地址映射。因此,模拟并不是缓存中发生的事情的真实表示。大多数缓存和分支预测器都被物理索引,但Cachegrind使用虚拟地址来模拟缓存。

  • 它不考虑在指令级别不可见的高速缓存未命中,例如由TLB未命中或者推测执行引起的缓存未命中。

  • Valgrind将安排线程与本机运行时的方式不同。这可能会扭曲线程程序的结果。

  • 在x86 / AMD64指令bts, btr并且 btc将错误地被计算为这样做,如果双方的观点是寄存器,例如读取数据:

        btsl%eax,%edx

    这只会很少发生。

  • 数据大小为28和108字节(例如fsave)的x86 / amd64 FPU指令 被视为只能访问16个字节。这些说明似乎是罕见的,希望这不会影响准确性。

值得注意的另一件事是,结果非常敏感。更改正在分析的可执行文件的大小,或其使用的任何共享库的大小,甚至文件名的长度可能会影响结果。变化会很小,但如果您的程序发生变化,不要指望完美的重复性结果。

更新近的GNU / Linux发行版将解决空间随机化问题,其中相同程序的相同运行将其共享库加载到不同位置,作为安全措施。这也扰乱了结果。

虽然这些因素意味着您不应该将结果信任为超级准确,但应该足够接近有用。

5.9。实施细节

本节将介绍您不需要了解的细节,以便使用Cachegrind,但有些人可能会感兴趣。

5.9.1。Cachegrind如何工作

了解Cachegrind如何工作的最佳参考是Nicholas Nethercote的“动态二进制分析和仪器”第3章。它可在Valgrind出版物页面上找到。

5.9.2。Cachegrind输出文件格式

文件格式相当简单,基本上是给每个行的成本中心,按文件和功能分组。它也是完全一般的和自我描述的,因为它可以用于可以逐行计算的任何事件,而不仅仅是缓存和分支预测器事件。例如,早期版本的Cachegrind没有分支预测器模拟。添加此文件后,文件格式无需更改。所以格式(和因此,cg_annotate)可以被其他工具使用。

文件格式:

file :: = desc_line * cmd_line events_line data_line + summary_line
desc_line :: =“desc:”ws?non_nl_string
cmd_line :: =“cmd:”ws?CMD
events_line :: =“events:”ws?(事件ws)+
data_line :: = file_line | fn_line | count_line
file_line :: =“fl =”filename
fn_line :: =“fn =”fn_name
count_line :: = line_num ws?(计数ws)+
summary_line :: =“summary:”ws?(计数ws)+
count :: = num | “”

哪里:

  • non_nl_string 是不包含换行符的任何字符串。

  • cmd 是一个保存分析程序的命令行的字符串。

  • event 是一个不包含空格的字符串。

  • filename并且 fn_name是字符串。

  • num并且 line_num是十进制数。

  • ws 是空白。

“desc:”行的内容打印在摘要的顶部。这是提供模拟特定信息的通用方式,例如为高速缓存模拟提供高速缓存配置。

可以为每个文件/ fn /行号显示多行信息。在这种情况下,命名事件的计数将被累积。

计数可以是“”。代表零。这使得文件更容易让人阅读。

在每个计数的数量 line和 summary_line不应超过在事件的数量 event_line。如果每个数字line较少,cg_annotate会将那些丢失的对象视为“。”。条目。这节省了空间。

file_line更改当前文件名。A fn_line 更改当前函数名称。A count_line包含与当前文件名/ fn_name相关的计数。一个“fn =” file_line, fn_line必须出现在任何count_lines 之前 给出第一个count_lines 的上下文。

每个file_line通常会紧随其后的是a fn_line。但它不一定是。

摘要行是多余的,因为它只保留每个事件的总计数。但这是对数据的有益的健康检查; 如果每个事件的总计与摘要行不匹配,则出现了错误。

Cachegrind:缓存和分支预测分析器相关推荐

  1. Callgrind:调用图生成缓存和分支预测分析器

    目录 6.1.概观 6.1.1.功能6.1.2.基本用法 6.2.高级用法 6.2.1.从一个程序运行多次分析转储6.2.2.限制收集事件的范围6.2.3.计算全球巴士事件6.2.4.避免循环6.2. ...

  2. 【linux】Valgrind工具集详解(十四):Cachegrind(缓存和分支预测分析器)

    一.概述 Cachegrind,它模拟CPU中的一级缓存I1,Dl和二级缓存,能够精确地指出程序中cache的丢失和命中.如果需要,它还能够为我们提供cache丢失次数,内存引用次数,以及每行代码,每 ...

  3. 时序图 分支_BOOM微架构学习(1)——取指单元与分支预测

    之前在RISC-V的"Demo"级项目--Rocket-chip一文中曾经简介过BOOM处理器的流水线,这次我们开始一个系列,深入学习一下BOOM的微架构,这样对于乱序执行的超标量 ...

  4. 慌!还不了解Java中的分支预测?!

    点击上方"朱小厮的博客",选择"设为星标" 后台回复"1024"获取公众号专属1024GB资料 来源:rrd.me/fLHvf 1.引言 分 ...

  5. Intel Core Enhanced Core架构/微架构/流水线 (5) - 分支预测/指令预取

    Branch Prediction Unit 在分支条件决断结果实际计算出来之前,分支预测机制就可以让处理器开始"投机式地"执行预测的分支指令.所有的分支指令都依赖于分支预测单元进 ...

  6. Pentium 4处理器架构/微架构/流水线 (7) - NetBurst前端详解 - 分支预测

    Branch Predication 对于使用深度指令流水线的处理器,分支预测能力至关重要.分支预测使得处理器可以在分支指令决断之前就开始执行(预测的)分支路径指令.分支延迟是由于分支预测错误导致的性 ...

  7. Pentium II Pentium III架构/微架构/流水线 (2) - P6详解 - 前端(指令预取/译码/动态分支预测静态分支预测)

    Pentium II & III Instruction Pipeline Details Front-End Pipeline Details Pentium II & III处理器 ...

  8. Pentium Pro架构/流水线及其优化 (3) - 指令流水线/乱序执行核/高速缓存/分支预测/指令预取

    Instruction Pipeline 关于Pentium Pro的指令流水线,我从4个来源看到3种不同的说法:11级,12级和14级(没有13级说,不大吉利吧).其实大同小异,不用纠结到底是多少级 ...

  9. 聊一聊分支预测,思考为什么使用 if/else 语句会降低程序效率

    写在前面 如果觉得写得好,有所收获,记得点个关注和点个赞哦,感谢支持. 在Stack Overflow上看到了这样的一个帖子,觉得挺值得学习的,这个帖子是关于讨论为什么处理排序数组比处理未排序数组快? ...

最新文章

  1. Linux那些事儿之我是Sysfs(3)设备模型上层容器
  2. this和super的区别
  3. ComputeShader中Counter类型的使用
  4. Windows server 2012体验之集成ISCSI功能
  5. 【附答案】Java面试2019常考题目汇总(一)
  6. 第一节《Git初始化》
  7. Java集合转化为数组
  8. 理解计算机程序与指令
  9. yolo3做行人检测+deep-sort做匹配,端对端做多目标跟踪
  10. 02 ZooKeeper分布式集群安装
  11. 实现MFC扩展DLL中导出类和对话框
  12. 2022Vue经典面试题及答案汇总(持续更新)
  13. 计算机键盘中复制粘贴快捷键,电脑复制粘贴快捷键,教您电脑怎么用键盘复制粘贴...
  14. 百度地图点击地图获取地址
  15. 用Python做数据分析之数据统计
  16. 解决报错 See config.log for more details 的问题
  17. Vue之生命周期mounted、activated区别及实际运用例子解析
  18. 四川汶川县今天又连发生地震!
  19. html访问MDB数据库,使用MDB Viewer打开和查看访问数据库 | MOS86
  20. 音频开发工具包LEADTOOLS教程:如何在eDiscovery应用程序使用?

热门文章

  1. c语言实现 windows socket_C语言实现Socket简单通信
  2. sklearn.model_selection中train_test_split的坑
  3. Anaconda3的安装
  4. OpenCV中反向投影
  5. 这个网站收集了很多杂志的审稿周期和收稿、拒稿意见,值得看看
  6. 学生PHP校园超市网站制作 学生PHP网页毕设源码 学生动态数据库网站作品 PHP电子商务商城购物网站
  7. HarmonyOS应用如何开发,使用什么开发工具及安装使用教程说明!
  8. 【docker】第二节:安装nginx、mysql、php
  9. python定时执行脚本实例
  10. php网站跨站脚本监测,基于PHP的在线跨站脚本检测工具.pdf