- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
我目前正试图了解我的一个 C 程序中的一些非常非常奇怪的行为。显然,在它的末尾添加或删除看似无关紧要的行会极大地影响程序其余部分的性能。
我的程序看起来有点像这样:
int large_buffer[10000];
void compute(FILE * input) {
for(int i=0; i<100; i++) {
do_lots_of_stuff();
printf(".");
fflush(stdout);
}
}
int main() {
FILE *input = fopen("input.txt", "r");
compute(input);
fclose(input); // <--- everything gets 2x slower if I comment this out (!)
return 0;
}
理论上,main 函数末尾的 fclose(input)
行应该无关紧要,因为操作系统应该在程序末尾自动关闭文件。但是我观察到,当我包含 fclose 语句时我的程序运行了 2.5 秒,而当我将其注释掉时运行了 5 秒。相差2倍!这不是由于程序开始或结束时的延迟:打印出 .
的速度在带有 fclose 语句的版本中明显更快。
我怀疑这可能与某些内存对齐或缓存未命中问题有关。如果我用另一个函数替换 fclose,例如 ftell,它也需要 5 秒才能运行,如果我将 large_buffer
的大小减少到 <= 8000 个元素,那么它总是在 2.5 秒内运行,无论 fclose语句存在与否。
但我真的很想 100% 确定这种奇怪行为背后的罪魁祸首是什么。是否可以在某种探查器或其他可以提供该信息的工具下运行我的程序?到目前为止,我尝试在 valgrind --tool=cachegrind
下运行这两个版本,但它报告了我的两个程序版本相同数量的缓存未命中 (0%)。
编辑 1:在 perf stat -d -d -d
下运行我的程序的两个版本后,我得到以下结果:
Performance counter stats for './no-fclose examples/bench.o':
5625.535086 task-clock (msec) # 1.000 CPUs utilized
38 context-switches # 0.007 K/sec
0 cpu-migrations # 0.000 K/sec
54 page-faults # 0.010 K/sec
17,851,853,580 cycles # 3.173 GHz (53.23%)
6,421,955,412 stalled-cycles-frontend # 35.97% frontend cycles idle (53.23%)
4,919,383,925 stalled-cycles-backend # 27.56% backend cycles idle (53.23%)
13,294,878,129 instructions # 0.74 insn per cycle
# 0.48 stalled cycles per insn (59.91%)
3,178,485,061 branches # 565.010 M/sec (59.91%)
440,171,927 branch-misses # 13.85% of all branches (59.92%)
4,778,577,556 L1-dcache-loads # 849.444 M/sec (60.19%)
125,313 L1-dcache-load-misses # 0.00% of all L1-dcache hits (60.22%)
12,110 LLC-loads # 0.002 M/sec (60.25%)
<not supported> LLC-load-misses
<not supported> L1-icache-loads
20,196,491 L1-icache-load-misses (60.22%)
4,793,012,927 dTLB-loads # 852.010 M/sec (60.18%)
683 dTLB-load-misses # 0.00% of all dTLB cache hits (60.13%)
3,443 iTLB-loads # 0.612 K/sec (53.38%)
90 iTLB-load-misses # 2.61% of all iTLB cache hits (53.31%)
<not supported> L1-dcache-prefetches
51,382 L1-dcache-prefetch-misses # 0.009 M/sec (53.24%)
5.627225926 seconds time elapsed
Performance counter stats for './yes-fclose examples/bench.o':
2652.609254 task-clock (msec) # 1.000 CPUs utilized
15 context-switches # 0.006 K/sec
0 cpu-migrations # 0.000 K/sec
57 page-faults # 0.021 K/sec
8,277,447,108 cycles # 3.120 GHz (53.39%)
2,453,171,903 stalled-cycles-frontend # 29.64% frontend cycles idle (53.46%)
1,235,728,409 stalled-cycles-backend # 14.93% backend cycles idle (53.53%)
13,296,127,857 instructions # 1.61 insn per cycle
# 0.18 stalled cycles per insn (60.20%)
3,177,698,785 branches # 1197.952 M/sec (60.20%)
71,034,122 branch-misses # 2.24% of all branches (60.20%)
4,790,733,157 L1-dcache-loads # 1806.046 M/sec (60.20%)
74,908 L1-dcache-load-misses # 0.00% of all L1-dcache hits (60.20%)
15,289 LLC-loads # 0.006 M/sec (60.19%)
<not supported> LLC-load-misses
<not supported> L1-icache-loads
140,750 L1-icache-load-misses (60.08%)
4,792,716,217 dTLB-loads # 1806.793 M/sec (59.93%)
1,010 dTLB-load-misses # 0.00% of all dTLB cache hits (59.78%)
113 iTLB-loads # 0.043 K/sec (53.12%)
167 iTLB-load-misses # 147.79% of all iTLB cache hits (53.44%)
<not supported> L1-dcache-prefetches
29,744 L1-dcache-prefetch-misses # 0.011 M/sec (53.36%)
2.653584624 seconds time elapsed
正如 kcachegrind 所报告的那样,看起来在这两种情况下都很少有数据缓存未命中,但程序的较慢版本具有更差的分支预测和更多的指令缓存未命中和 iTLB 加载。这些差异中的哪一个最有可能导致测试用例之间运行时间相差 2 倍?
编辑 2:有趣的事实,如果我用单个 NOP 指令替换“fclose”调用,显然我仍然可以保持奇怪的行为。
编辑 3:我的处理器是 Intel i5-2310 (Sandy Bridge)
编辑 4:事实证明,如果我通过编辑程序集文件来调整数组的大小,它不会变得更快。显然,当我在 C 代码中更改它们的大小时它变得更快的原因是因为 gcc 决定重新排列二进制文件中的东西的顺序。
编辑 5:更多证据表明重要的是 JMP 指令的精确地址:如果我在代码的开头添加一个 NOP(而不是 printf),它会变得更快。同样,如果我从代码的开头删除一条无用的指令,它也会变得更快。当我在不同版本的 gcc 上编译我的代码时,它也变得更快,尽管生成的汇编代码是相同的。唯一的区别是开始时的调试信息和二进制文件的各个部分的顺序不同。
最佳答案
关键指标
你的罪魁祸首是分支未命中:
440,171,927 branch-misses # 13.85% of all branches
对比
71,034,122 branch-misses # 2.24% of all branches
我不确定你运行的是哪个处理器,但如果你假设在 Haswell 上运行 2.5 GHz 处理器,你会发现每个分支预测未命中的成本约为 15 个周期(通常会多一点,因为其他东西停滞也),每个周期为0.4ns。因此,0.4ns/周期 * 15 个周期/遗漏分支 * (440,171,927 - 71,034,122) 分支遗漏 = 2.2 秒。这将取决于您的确切处理器和机器代码,但这解释了那里的大部分差异。
原因
不同芯片的分支预测算法是专有的,但如果您在这里 (http://www.agner.org/optimize/microarchitecture.pdf) 进行研究,您可以了解更多关于不同处理器及其限制的信息。从本质上讲,您会发现某些处理器在避免它们存储的分支预测表中的冲突方面做得更好,这些预测表用于预测是否采用分支。
那么,为什么它是相关的?好吧,发生的事情(99% 的可能性)是,通过非常轻微地重新安排您的程序,您可以更改完全不同的分支在内存位置方面的位置。在处理器的分支预测表中有太多映射到同一个桶。通过稍微更改可执行文件,这个问题就消失了。您必须在两个分支点之间有一个非常特定的距离才能触发此问题。不幸的是,你做到了。
简单的解决方案
如您所见,许多更改实际上会导致程序无法达到这种降级性能。本质上,任何改变两个关键分支点之间距离的东西都可以解决问题。您可以通过在这两个位置之间的某处插入 16 个字节(或足以将分支点移动到不同的存储桶)的机器代码 nop 来完成此操作。您也可以像以前那样做,改变一些会破坏与非病理情况的距离的东西。
深入挖掘
如果您想真正了解导致这种情况的原因,您需要亲自动手。乐趣!您将需要从众多工具中选择一种来查找被错误预测的特定分支。这是一种方式:How to measure mispredictions for a single branch on Linux?
在识别出预测错误的分支后,您可以确定是否有办法删除该分支(无论如何,这几乎总是一个提高性能的好主意)。如果没有,您可以为其添加一个提示,或者,最坏的情况是,只是四处移动以确保不会像之前建议的那样共享相同的条目。
更广泛的类(class)
程序员低估了分支的成本(当编译器无法在编译时删除分支时)。正如您所发现的,每个分支都会给处理器的分支预测缓冲区带来更多压力,并增加预测错误的可能性。因此,即使处理器 100% 可预测的分支也会通过减少可用于预测其他分支的资源来降低性能。此外,当一个分支被错误预测时,它至少需要 12-15 个周期,如果所需的指令不在 L1 缓存、L2 缓存、L3 缓存或天堂帮助你,主内存中,可能会更多。此外,编译器无法跨分支进行所有优化。
关于c - 我如何确定我的程序运行缓慢是否是 CPU 缓存问题(在 Linux 上)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43241895/
我正在使用 Selenium Web 驱动程序 3.0,并且想要从打开的两个对话框(一个在后台,第二个在前台)的 Activity 对话框中单击“确定”按钮。如何从 html 下面的父 div 单击前
actions: [ FlatButton( onPressed: () {
我有一个问题有点超出我的范围(我真的很高兴我是 Beta)涉及重复项(所以 GROUP BY, HAVING, COUNT),通过将解决方案保留在 SQLite 附带的标准函数中而变得更加复杂。我正在
使用DBI是否可以确定SELECT语句的已执行语句句柄是否返回任何行而不从中获取行? IE。就像是: use DBI; ... my $sth = $dbh->prepare("SELECT ..."
是否可以为“确定”和“关闭”按钮指定回调函数? 如果是JQuery Modal,则可以在初始化时使用按钮字典指定回调函数。 Semantic-ui模态是否提供类似的功能?按下确定后,我该如何寻求其他逻
我想阅读警报中的消息。 示例:如果警报显示“错误的电子邮件地址”。怎么读呢?意味着我想将该消息存储在字符串中。 如何在“警报”中单击“确定”...?? 如何使用 Selenium 来做到这一点? 最佳
我有一个删除按钮: 我试图首先查明是否已选择一个网站,如果已选择一个网站,我需要确定是否已选择一个或多个列表项,如果是,则继续删除这些项目。 我的 if 语句不断返回“您必须首先选择您的列表”,即使它
部分出于好奇——我们想知道在我们的应用程序中发生了什么——部分是因为我们需要在我们的代码中找到一些潜在的问题,我喜欢在我们的网络应用程序运行时跟踪一些一般值。这尤其包括某些对象图的分配内存。 我们的应
我将 SweetAlert 与 Symfony 结合使用,我希望用户在完成删除操作之前进行确认。 发生的情况是,当用户单击删除按钮时,SweetAlert 会弹出,然后立即消失,并且该项目被删除。 在
我们有一个应用程序可以生成不包括字母 O 的随机基数 35 [0-9A-Z]。我正在寻找一种解决方案来查找包含任何淫秽英语单词的代码,而无需搜索包含 10,000 个条目的列表每个生成的代码。每秒生成
这是我做的: #include #include int betweenArray(int a, int b){ int *arr,i,range; range = b - a +
我知道如何创建 警报和确认框,但我不知道如何做的是实际单击“确定”。我有一个弹出确认框的页面。 我想使用 Java Script 插件单击“确定”。基本上,我希望我的代码单击页面上的链接,然后在出现提
代码: swal('Your ORDER has been placed Successfully!!!'); window.location="index.php"; 甜蜜警报工
>>> import re >>> s = "These are the words in a sentence" >>> regex = re.compile('are|words') >>> [m
使用确定的理想散列函数给出随机期望线性时间算法两个数组 A[1..n] 和 B[1..n] 是否不相交,即 A 的元素是否也是 B 的元素。 谁能告诉我如何做到这一点,甚至如何开始考虑它? 最佳答案
我在计算机科学课上有这段代码: int input=15; while (input < n ) { input = input *3;} 这段代码有 log3(n/15) 次循环的上限。我们怎样才能
我有一个允许 2 位玩家玩 TicTacToe 的程序。在每个玩家移动之后,它应该在那个点显示棋盘并返回一个名为 Status 的枚举,显示玩家是否应该继续,如果玩家赢了,还是平局。但是,该算法要么返
给定一个 y 值数组,例如 [-3400, -1000, 500, 1200, 3790],我如何确定“好的”Y 轴标签并将它们放置在网格上? ^ ---(6,000)-|---
假设我有一个检查用户登录的 SQL 语句: SELECT * FROM users WHERE username='test@example.com', password='abc123', expi
teradata中有返回表中哪一列被定义为主索引的命令吗?我没有制作一些我正在处理的表,也没有尝试优化我对这些表的连接。谢谢! 最佳答案 有dbc.IndicesV,其中IndexNumber=1表示
我是一名优秀的程序员,十分优秀!