【发布时间】:2016-04-27 14:56:44
【问题描述】:
代码看起来像这样,内部循环需要大量时间:
#define _table_derive ((double*)(Buffer_temp + offset))
#define Table_derive(m,nbol,pos) _table_derive[(m) + 5*((pos) + _interval_derive_dIdQ * (nbol))]
char *Buffer_temp=malloc(...);
for (n_bol=0; n_bol<1400; n_bol++) // long loop here
[lots of code here, hundreds of lines with computations on doubles, other loops, etc]
double ddI=0, ddQ=0;
// This is the original code
for(k=0; k< 100; k++ ) {
ddI += Table_derive(2,n_bol,k);
ddQ += Table_derive(3,n_bol,k);
}
ddI /= _interval_derive_dIdQ;
ddQ /= _interval_derive_dIdQ;
[more code here]
}
oprofile 告诉我大部分运行时间都花在了这里(第二列是时间百分比):
129304 7.6913 :for(k=0; k< 100; k++) {
275831 16.4070 :ddI += Table_derive(2,n_bol,k);
764965 45.5018 :ddQ += Table_derive(3,n_bol,k);
我的第一个问题是:我可以依靠 oprofile 来指示代码慢的正确位置吗(我在 -Og 和 -Ofast 中尝试过,它基本上是一样的)。
我的第二个问题是:为什么这个非常简单的循环比 sqrt、atan2 和之前的数百行计算慢?我知道我没有显示所有代码,但是代码很多,对我来说没有意义。
我尝试了各种优化器技巧来矢量化(不起作用)或展开(起作用),但收效甚微,例如:
typedef double aligned_double __attribute__((aligned(8)));
typedef const aligned_double* SSE_PTR;
SSE_PTR TD=(SSE_PTR)&Table_derive(2,n_bol,0); // We KNOW the alignement is correct because offset is multiple of 8
for(k=0; k< 100; k++, TD+=5) {
#pragma Loop_Optimize Unroll No_Vector
ddI += TD[0];
ddQ += TD[1];
}
我检查了优化器的输出: “-Ofast -g -march=native -fopt-info-all=missed.info -funroll-loops” 在这种情况下,我得到“循环展开 9 次”,但如果我尝试矢量化,我得到(简而言之): “不能强制对齐参考”, “矢量对齐可能无法到达”, “矢量化未对齐的访问”, “访问的未知对齐方式:*(prephitmp_3784 + ((sizetype) _1328 + (long unsigned int) (n_bol_1173 * 500) * 2) * 4)”
有什么方法可以加快速度?
附录: 谢谢大家的cmet,我会在这里尝试回答:
- 是的,我知道代码很丑(不是我的),而且您还没有看到真正的原始代码(这是一个巨大的简化)
- 我被这个数组卡住了,因为 C 代码位于库中,而大型数组一旦被 C 处理和修改,就会传递给调用者(IDL、Python 或 C)。
- 我知道使用一些结构而不是将 char* 转换为复杂的多维 double* 会更好,但请参见上文。在第一次编写这个 prog 时,结构可能不是 C 规范的一部分(开个玩笑……也许吧)
- 我知道对于矢量化器来说,使用数组结构比使用结构数组更好,但是,叹息...见上文。
- 有一个实际的外循环(在调用程序中),所以这个整体数组的总大小约为 2Gb
- 按原样,在没有优化的情况下运行大约需要 15 分钟,并且在我重写了一些代码(更快的 atan2,数组内部的一些手动对齐...)并且我使用 -Ofast 和 -march=native
- 由于硬件约束发生变化,我正努力加快速度以跟上数据流。
- 我尝试使用 Clang,但收益很小(几秒钟),但我看不到获取优化报告的选项,例如 -fopt-info。我是否必须将程序集视为了解发生了什么的唯一选择?
- 该系统是一个拥有 500Gb RAM 的 64 核,但我无法插入任何 OpenMP pragma 来并行化上述代码(我已经尝试过):它读取一个文件,将其完全解压缩到内存中(2Gb),按顺序分析它(诸如'+='之类的东西)并将一些结果吐出给调用 IDL/Python。全部在一个内核上(但其他内核非常忙于实际采集和后期处理)。 :(
- 没用,感谢您的出色建议:删除 ddQ += ... 似乎将时间百分比转移到上一行:376280 39.4835:ddI+=...
- 这给我们带来了更好的结果:删除两者(因此整个循环)保存...什么都没有!所以我想正如彼得所说,我不能相信探查器。如果我分析无环编,我会得到更均匀的时间分布(以前只有 1 秒以上的 3 行,现在大约 10 行,所有这些都像简单的变量分配一样无意义)。
我猜这个内循环从一开始就是一条红鲱鱼;我将使用手动计时重新开始优化。谢谢。
【问题讨论】:
-
如果
ddQ行确实占用了您 45% 的时间,那么只需将其注释掉就可以大大加快速度。是吗? -
矢量化器的输出可能与宏
_table_derive定义中的offset组件有关。如果此数组所需大小的上限足够小,则使用普通的本地数组(doubles)或本地 VLA 可能是有意义的。否则,如果您可以取出偏移量,使_table_derive从Buffer_temp的开头开始,那么这也应该解决有关对齐的任何不确定性。 -
总的来说,整个
Buffer_temp/offset/casting 的东西都有不好的代码气味。事实上,大多数铸件本身就有不好的代码气味。让编译器尽可能容易地理解代码中发生的事情往往会提高编译器的优化能力。 -
for(k=0; k< 100; k++ ) { ... }可以重写为x = foo(); for(k=100; --k; ) { ddI +=_table_derive[x]; ddQ +=_table_derive[x+1]; x += _interval_derive_dIdQ}或类似的东西。 -
_table_derived的访问模式对缓存不是很友好?它是一张按需调入的巨型桌子吗?即使它全部在内存中,您也可能会遭受大量缓存未命中。省略的代码可能对缓存更友好,因此速度相对较快。整体代码真的很慢吗?还是只是优化器指向内部循环并说大部分时间都花在这里?
标签: c performance gcc optimization oprofile