我如何说服GCC展开一个已知迭代次数但又很大的循环?
我正在编译-O3.
当然,有问题的真实代码更复杂,但这是一个具有相同行为的简化示例:
int const constants[] = { 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144 };
int get_sum_1()
{
int total = 0;
for (int i = 0; i < CONSTANT_COUNT; ++i)
{
total += constants[i];
}
return total;
}
Run Code Online (Sandbox Code Playgroud)
...如果CONSTANT_COUNT被定义为8(或更少),那么GCC将展开循环,传播常量,并将整个函数简化为简单return <value>;.另一方面,如果CONSTANT_COUNT是9(或更高),那么循环不会展开,并且GCC会生成一个二进制循环,读取常量,并在运行时添加它们 - 即使理论上,该函数仍然可以被优化到只返回一个常数.(是的,我看过反编译的二进制文件.)
如果我手动展开循环,如下所示:
int get_sum_2()
{
int total = 0;
total += constants[0];
total += constants[1];
total += constants[2];
total += constants[3];
total …Run Code Online (Sandbox Code Playgroud) 有没有一种方法来指示GCC(版本我用4.8.4),以展开在底层函数while循环完全,即剥离这个循环?循环的迭代次数在编译时是已知的:58.
我先解释一下我的尝试.
通过检查GAS输出:
gcc -fpic -O2 -S GEPDOT.c
Run Code Online (Sandbox Code Playgroud)
使用12个寄存器XMM0 - XMM11.如果我将标志-funroll-loops传递给gcc:
gcc -fpic -O2 -funroll-loops -S GEPDOT.c
Run Code Online (Sandbox Code Playgroud)
循环只展开两次.我检查了GCC优化选项.GCC表示-funroll-loops也会打开-frename-registers,所以当GCC展开一个循环时,它先前选择的寄存器分配是使用"遗留"寄存器.但是XMM12只剩下4个 - XMM15,所以GCC最多只能展开2次.如果有48个而不是16个XMM寄存器可供使用,GCC将毫无困难地展开while循环4次.
然而,我做了另一个实验.我首先手动两次展开while循环,获得一个函数GEPDOT_2.然后两者之间没有任何区别
gcc -fpic -O2 -S GEPDOT_2.c
Run Code Online (Sandbox Code Playgroud)
和
gcc -fpic -O2 -funroll-loops -S GEPDOT_2.c
Run Code Online (Sandbox Code Playgroud)
由于GEPDOT_2已用完所有寄存器,因此不执行展开.
GCC确实注册了重命名,以避免引入潜在的错误依赖.但我确信在我的GEPDOT中没有这样的潜力; 即使有,也不重要.我尝试自己展开循环,展开4次比展开2次更快,比没有展开更快.当然我可以手动展开更多次,但这很乏味.GCC可以帮我吗?谢谢.
// C file "GEPDOT.c"
#include <emmintrin.h>
void GEPDOT (double *A, double *B, double *C) {
__m128d A1_vec = _mm_load_pd(A); A += 2;
__m128d B_vec = _mm_load1_pd(B); B++;
__m128d C1_vec = A1_vec * B_vec; …Run Code Online (Sandbox Code Playgroud) 这个问题部分是GCC 5.1循环展开的后续问题.
根据GCC文档,并且如我在上述问题的答案中所述,标志如-funroll-loops打开"完全循环剥离(即完全去除具有少量恒定迭代次数的循环)".因此,当启用这样的标志时,如果确定这将优化给定代码段的执行,则编译器可以选择展开循环.
尽管如此,我注意到在我的一个项目中,即使没有启用相关标志,GCC有时会展开循环.例如,考虑以下简单的代码:
int main(int argc, char **argv)
{
int k = 0;
for( k = 0; k < 5; ++k )
{
volatile int temp = k;
}
}
Run Code Online (Sandbox Code Playgroud)
在编译时-O1,循环展开,并使用任何现代版本的GCC生成以下汇编代码:
main:
movl $0, -4(%rsp)
movl $1, -4(%rsp)
movl $2, -4(%rsp)
movl $3, -4(%rsp)
movl $4, -4(%rsp)
movl $0, %eax
ret
Run Code Online (Sandbox Code Playgroud)
即使在使用附加-fno-unroll-loops -fno-peel-loops程序进行编译以确保禁用标志时,GCC仍然意外地仍在上述示例中执行循环展开.
这一观察引出了以下密切相关的问题.为什么GCC执行循环展开,即使禁用了与此行为相对应的标志?展开是否也受其他标志控制,这些标志可以使编译器在某些情况下展开循环,即使-funroll-loops被禁用?有没有办法完全禁用GCC中的循环展开(编译的一部分-O0)?
有趣的是,Clang编译器在此处具有预期的行为,并且似乎仅在-funroll-loops启用时执行展开,而在其他情况下不执行. …
什么是JIT的循环展开政策?或者,如果没有简单的答案,那么有什么方法可以检查循环中执行循环展开的位置/时间?
GNode child = null;
for(int i=0;i<8;i++){
child = octree.getNeighbor(nn, i, MethodFlag.NONE);
if(child==null)
break;
RecurseForce(leaf, child, dsq, epssq);
}
Run Code Online (Sandbox Code Playgroud)
基本上,我有一段代码具有静态迭代次数(八次),并且当我离开for循环时它确实很糟糕.但是当我手动展开循环时,它确实更好.我有兴趣了解JIT是否实际上展开循环,如果没有,那么为什么.
在CUDA中,可以使用#pragmaunroll指令展开循环,以通过提高指令级并行性来提高性能.该#pragma可选地跟着一个数字,指定多少次循环必须展开.
不幸的是,文档没有给出关于何时应该使用该指令的具体指示.由于编译器已经展开了具有已知行程计数的小循环,是否应该#pragma在较大的循环上展开?在带有可变计数器的小循环上?那么可选的展开数量呢?还有关于cuda特定循环展开的推荐文档吗?
我的9600GT讨厌我.
片段着色器:
#version 130
uint aa[33] = uint[33](
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,
0,0,0
);
void main() {
int i=0;
int a=26;
for (i=0; i<a; i++) aa[i]=aa[i+1];
gl_FragColor=vec4(1.0,0.0,0.0,1.0);
}
Run Code Online (Sandbox Code Playgroud)
如果a=25程序以3000 fps运行.
如果a=26程序以20 fps运行.
如果aa未出现<= 32问题的大小.
视口大小为1000x1000.
仅当大小aa> 32 时才会出现问题.
的值a作为阈值与调用循环内的阵列变化(aa[i]=aa[i+1]+aa[i-1]给出了不同的期限).
我知道gl_FragColor已被弃用.但那不是问题.
我的猜测是,如果> 25且大小(aa)> 32,GLSL不会自动展开循环.为什么.它取决于阵列大小的原因是人类所不知道的.
这里解释了一个非常相似的行为:http:
//www.gamedev.net/topic/519511-glsl-for-loops/
手动展开循环确实解决了问题(3000 fps),即使aa大小> 32:
aa[0]=aa[1];
aa[1]=aa[2];
aa[2]=aa[3];
aa[3]=aa[4];
aa[4]=aa[5];
aa[5]=aa[6];
aa[6]=aa[7];
aa[7]=aa[8];
aa[8]=aa[9];
aa[9]=aa[10];
aa[10]=aa[11];
aa[11]=aa[12];
aa[12]=aa[13];
aa[13]=aa[14];
aa[14]=aa[15]; …Run Code Online (Sandbox Code Playgroud) 考虑以下简单示例:
struct __attribute__ ((__packed__)) {
int code[1];
int place_holder[100];
} s;
void test(int n)
{
int i;
for (i = 0; i < n; i++) {
s.code[i] = 1;
}
}
Run Code Online (Sandbox Code Playgroud)
for 循环正在写入code大小为 1 的字段 。之后的下一个字段code是place_holder。
我希望在 的情况下n > 1,写入code数组会溢出并1写入place_holder.
但是,在使用-O2(在 gcc 4.9.4 上但也可能在其他版本上)进行编译时,会发生一些有趣的事情。
编译器识别出代码可能溢出数组code,并将循环展开限制为 1 次迭代。
很容易看出,在编译-fdump-tree-all和查看最后一个树传递(“t.optimized”)时:
;; Function test (test, funcdef_no=0, decl_uid=1366, symbol_order=1)
Removing basic block …Run Code Online (Sandbox Code Playgroud) 您是否建议阅读内核的PTX代码以进一步优化内核?
一个例子:我读过,如果自动循环展开有效,可以从PTX代码中找到.如果不是这种情况,则必须在内核代码中手动展开循环.
#include <stdio.h>
int main() {
int i;
for(i=0;i<10000;i++){
printf("%d",i);
}
}
Run Code Online (Sandbox Code Playgroud)
我想使用gcc循环展开此代码,但即使使用该标志.
gcc -O2 -funroll-all-loops --save-temps unroll.c
Run Code Online (Sandbox Code Playgroud)
我得到的汇编代码包含一个10000次迭代的循环
_main:
Leh_func_begin1:
pushq %rbp
Ltmp0:
movq %rsp, %rbp
Ltmp1:
pushq %r14
pushq %rbx
Ltmp2:
xorl %ebx, %ebx
leaq L_.str(%rip), %r14
.align 4, 0x90
LBB1_1:
xorb %al, %al
movq %r14, %rdi
movl %ebx, %esi
callq _printf
incl %ebx
cmpl $10000, %ebx
jne LBB1_1
popq %rbx
popq %r14
popq %rbp
ret
Leh_func_end1:
Run Code Online (Sandbox Code Playgroud)
可以somone plz告诉我如何在gcc中正确实现循环展开
我刚刚阅读了 Java Magazine 文章Loop Unrolling。作者在那里演示了for带有int计数器的简单循环是通过循环展开优化编译的:
private long intStride1()
{
long sum = 0;
for (int i = 0; i < MAX; i += 1)
{
sum += data[i];
}
return sum;
}
Run Code Online (Sandbox Code Playgroud)
但是,他们随后通过将计数器类型切换为 来显示一切都发生了变化long:
private long longStride1()
{
long sum = 0;
for (long l = 0; l < MAX; l++)
{
sum += data[(int) l];
}
return sum;
}
Run Code Online (Sandbox Code Playgroud)
这会通过以下方式更改输出程序集:
这会显着降低吞吐量性能。
为什么 64 位 HotSpot VM 不为long计数器执行循环展开?为什么第二种情况需要安全点,而第一种情况不需要?
loop-unrolling ×10
gcc ×5
c ×4
optimization ×3
cuda ×2
java ×2
jit ×2
c++ ×1
glsl ×1
gpgpu ×1
hpc ×1
jvm-hotspot ×1
opengl ×1
performance ×1
ptx ×1
struct ×1
x86 ×1