C 优化慢循环
代码如下所示,内部循环需要花费大量时间:C 优化慢循环,c,performance,gcc,optimization,oprofile,x86,C,Performance,Gcc,Optimization,Oprofile,X86,代码如下所示,内部循环需要花费大量时间: #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&
#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]
}
#定义(表)派生((双*)(缓冲区(温度+偏移量))
#定义表格推导(m,nbol,pos)表格推导[(m)+5*((pos)+区间推导(nbol)]]
char*Buffer_temp=malloc(…);
对于(n_-bol=0;n_-bol)
我的第一个问题是:我可以依靠oprofile来表示正确的
代码运行缓慢的地方
不完全是这样。据我所知,周期通常由等待输入的指令(或其他一些执行资源)负责,而不是生成输入或释放任何其他执行资源缓慢的指令
然而,在您的oprofile输出中,很可能它实际上是最后一个循环。在这个外部循环中是否还有其他内部循环
你分析过缓存未命中吗?除了周期之外,还有很多有趣的东西
还要注意,要真正了解性能,您需要查看asm上的配置文件注释,而不是C。例如,一个添加的时间比另一个多,这很奇怪,但这可能只是INSN到源代码行的映射问题
关于:注释循环的性能结果:
如果外循环已经接触到了内存,那么你可能只是在缓存未命中问题上遇到了瓶颈,而内循环只是再次接触到了内存?请尝试perf record-e L1 dcache load misses./a.out
然后perf report
。或者oprofile
相当于
可能是内环uop一直等待发出,直到外环中的慢指令失效。现代Intel CPU中的重排序缓冲区(ROB)大小约为200 uop,大多数INSN解码为单个uop,因此无序窗口约为200条指令
指出内环还意味着,当内环运行时,外环中任何带有依赖链的循环都没有时间完成。删除该内环可能会使外环的瓶颈发生质的变化,从吞吐量到延迟
回复:使用-Ofast-march=native
,速度提高了15倍。好吧,这很好。未优化的代码很糟糕,不应该被视为任何类型的“基线”或任何性能指标。如果你想与某物进行比较,请与-O2
(不包括自动矢量化、-ffast math
或-march=native
)
尝试使用-fprofile generate
/-fprofile use
。配置文件使用包括-funroll循环
,因此我假设在有可用的配置文件数据时,该选项最有效
回复:自动并行化:
您必须特别启用它,无论是使用OpenMP pragmas还是类似的-floop parallelize all-ftree parallelize loops=4
。如果存在非平凡的循环携带依赖项,则自动并行可能不可能。该wiki页面也很旧,可能无法反映自动并行化的最新技术。我认为nMP关于哪些循环要并行化的提示比让编译器猜测更明智,尤其是在没有-fprofile使用的情况下
我尝试了使用Clang,但收获很小(几秒钟),但我看不到获得优化报告(如-fopt info)的选项。我是否必须将程序集视为了解发生了什么的唯一选项
对于内联报告,您可以使用clang-Rpass=inline
。矢量化过程的名称是loop vectorize
,因此您可以使用-Rpass missed=loop vectorize
,或-Rpass analysis=loop vectorize
来告诉您是哪个语句导致矢量化失败
查看asm是知道它是否自动向量化不好的唯一方法,但要真正判断编译器的工作,您必须知道如何自己编写高效的asm(这样您就知道它可以做些什么。)请参阅,以及tag wiki中的其他链接
我没有试着把你的代码放在不同的编译器上,但是如果你的例子使asm代表了你从完整的源代码中看到的东西,你可以发布一个链接
自动矢量化
如果\u interval\u derivate\u dIdQ
和offset
并不总是5*8B的倍数,那么您可能需要声明double*table=…;
并将您的表修改为
#define Table_derive(nbol, pos) ( ((derive_t *)(double_table + offset/sizeof(double) + _interval_derive_dIdQ / sizeof(double) * (nbol)))[pos] )
计划生育司:
你能把double inv\u interval\u deriver\u dIdQ=1.0/\u interval\u deriver\u dIdQ;
从循环中提出来吗?乘法比除法便宜得多,特别是在延迟很重要或者sqrt也需要div单元的情况下。如果ddQ
行真的占用了你45%的时间,只需注释一下就可以给你带来巨大的加速。是吗它?矢量器的输出可能与宏\u table\u deriver
定义中的偏移量
分量有关。如果此数组所需大小的上限足够小,则使用普通的局部数组(双数组)可能是有意义的,或者可能是本地VLA。否则,如果您可以取出偏移量,使\u table\u derivate
从Buffer\u temp
的开头开始,那么这也应该可以解决对齐的任何不确定性。总的来说,整个Buffer\u temp
/offset/casting东西都有不好的代码味道。事实上,大多数casting都有不好的代码味道让编译器尽可能容易地理解代码中发生的事情有助于提高编译器的优化能力。for(k=0;k<100;k++){…}
可以被重新写入x=foo();for(k=100;--k;){ddI+=\u table\u deriver[x];ddQ+=\u table\u deriver[x+1];x+=\u interval\u deriver\u dIdQ}
或类似的东西。对\u表\u派生的
的访问模式不是很友好的缓存?是不是
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];
}
for(k=0; k< 100; k++ ) {
ddI += Table_derive(2,n_bol,k);
ddQ += Table_derive(3,n_bol,k);
}
typedef struct table_derive_entry {
double a,b,c,d,e;
} derive_t;
void foo(void)
{
// I wasn't clear on whether table is static/global, or per-call scratch space.
derive_t *table = aligned_alloc(foo*bar*sizeof(derive_t), 64); // or just malloc, or C99 variable size array.
// table += offset/sizeof(table[0]); // if table is global and offset is fixed within one call...
// maybe make offset a macro arg, too?
#define Table_derive(nbol, pos) table[offset/sizeof(derive_t) + (pos) + _interval_derive_dIdQ / sizeof(derive_t) * (nbol))]
// ...
for(k=0; k< 100; k++ ) {
ddI += Table_derive(n_bol, k).b;
ddQ += Table_derive(n_bol, k).c;
}
// ...
}
#undef Table_derive
#define Table_derive(nbol, pos) ( ((derive_t *)(double_table + offset/sizeof(double) + _interval_derive_dIdQ / sizeof(double) * (nbol)))[pos] )
ddI /= _interval_derive_dIdQ;
ddQ /= _interval_derive_dIdQ;