__local int bar(int a, int b, int c, int d, int e)
{
return a*b*c*d; // 'e' not used
}
__kernel void foobar(__global int * notusedvariable)
{
int foo=1;
for (int a=1; a<=10; a++)
for (int b=1; b<=10; b++)
for (int c=1; c<=10; c++)
for (int d=1; d<=10; d++)
for (int e=1; e<=10; e++)
foo *= bar(a,b,c,d,e);
}
当我移除最里面的循环并将foo *= bar(a,b,c,d,e);
更改为foo *= bar(a,b,c,d,1);
时,它会编译。因此存在某种过度优化或过度预先计算的情况。如果我有更多循环并且某些变量来自get_global_id(...)
。
我该怎么办?
我使用Fedora Linux 20,并已安装
opencl-utils-0-12.svn16.fc20.x86_64
opencl-1.2-intel-cpu-3.2.1.16712-1.x86_64
opencl-utils-devel-0-12.svn16.fc20.x86_64
opencl-1.2-base-3.2.1.16712-1.x86_64
GPU是Geforce 210,也就是我能找到的最便宜的。
答案 0 :(得分:2)
它并没有真正“卡住”。它只是陷入了优化内核的尝试之中。主要是通过展开固定大小的循环(和BTW,发现根本没有使用foo
变量!)
例如,当启用循环a ... d(并关闭)时,为内核创建的二进制文件如下所示:
.entry foobar(
.param .u32 .ptr .global .align 4 foobar_param_0
)
{
.reg .pred %p<4>;
.reg .s32 %r<13>;
mov.u32 %r10, 0;
BB0_1:
add.s32 %r10, %r10, 1;
mov.u32 %r11, 0;
BB0_2:
mov.u32 %r12, 10;
BB0_3:
add.s32 %r12, %r12, -2;
setp.ne.s32 %p1, %r12, 0;
@%p1 bra BB0_3;
add.s32 %r11, %r11, 1;
setp.ne.s32 %p2, %r11, 10;
@%p2 bra BB0_2;
setp.ne.s32 %p3, %r10, 10;
@%p3 bra BB0_1;
ret;
}
你可以看到它并没有真正计算任何东西 - 编译器已经很难发现实际上没什么可做的。
将此与添加行
时生成的输出进行比较notusedvariable[0]=foo;
作为内核的最后一行:现在,计算可以不被跳过并优化掉。经过一段时间的编译,它会产生结果
.entry foobar(
.param .u32 .ptr .global .align 4 foobar_param_0
)
{
.reg .pred %p<4>;
.reg .s32 %r<80>;
mov.u32 %r79, 1;
mov.u32 %r73, 0;
mov.u32 %r72, %r73;
BB0_1:
add.s32 %r7, %r73, 1;
add.s32 %r72, %r72, 2;
mov.u32 %r76, 0;
mov.u32 %r74, %r76;
mov.u32 %r73, %r7;
mov.u32 %r75, %r7;
BB0_2:
mov.u32 %r9, %r75;
add.s32 %r74, %r74, %r72;
mov.u32 %r78, 10;
mov.u32 %r77, 0;
BB0_3:
add.s32 %r40, %r9, %r77;
mul.lo.s32 %r41, %r40, %r79;
mul.lo.s32 %r42, %r40, %r41;
add.s32 %r43, %r74, %r77;
mul.lo.s32 %r53, %r42, %r40;
mul.lo.s32 %r54, %r53, %r40;
mul.lo.s32 %r55, %r54, %r40;
mul.lo.s32 %r56, %r55, %r40;
mul.lo.s32 %r57, %r56, %r40;
mul.lo.s32 %r58, %r57, %r40;
mul.lo.s32 %r59, %r58, %r40;
mul.lo.s32 %r60, %r59, %r40;
mul.lo.s32 %r61, %r60, %r43;
mul.lo.s32 %r62, %r61, %r43;
mul.lo.s32 %r63, %r62, %r43;
mul.lo.s32 %r64, %r63, %r43;
mul.lo.s32 %r65, %r64, %r43;
mul.lo.s32 %r66, %r65, %r43;
mul.lo.s32 %r67, %r66, %r43;
mul.lo.s32 %r68, %r67, %r43;
mul.lo.s32 %r69, %r68, %r43;
mul.lo.s32 %r70, %r69, %r43;
mul.lo.s32 %r79, %r70, -180289536;
add.s32 %r77, %r77, %r74;
add.s32 %r78, %r78, -2;
setp.ne.s32 %p1, %r78, 0;
@%p1 bra BB0_3;
add.s32 %r76, %r76, 1;
add.s32 %r30, %r9, %r7;
setp.ne.s32 %p2, %r76, 10;
mov.u32 %r75, %r30;
@%p2 bra BB0_2;
setp.ne.s32 %p3, %r7, 10;
@%p3 bra BB0_1;
ld.param.u32 %r71, [foobar_param_0];
st.global.u32 [%r71], %r79;
ret;
}
显然,它已经展开了一些循环,现在他再也无法优化它们了。我假设当循环“e”也被激活时,这种展开(或用于优化掉未使用的循环)所需的时间至少是二次增加。所以,如果你给他几个小时,他实际上也可能完成编译......
正如Tom Fenech在https://stackoverflow.com/a/22011454中已经说过的那样,通过将-cl-opt-disable
传递给clBuildProgram
可以缓解这个问题。
或者,您可以有选择地关闭每个循环的展开优化:插入时
#pragma unroll 1
直接在for循环之前,您实际上禁用了对此特定循环的展开。
重要不要盲目地使用任意值插入unroll
pragma。使用1
是安全的,但对于其他值,您必须手动确保它不会影响程序的正确性。请参阅CUDA编程指南的“B.21。#pragma unroll”部分。
在这种情况下,在两个最内层循环(d和e)之前插入#pragma unroll 1
似乎就足够了,以便能够进行足够的优化以快速构建程序。
编辑:叹息 prunge快了4分钟......: - (
答案 1 :(得分:0)
你正在做的计算确实会得到一个非常大的数字!
我在我的硬件(NVIDIA GTX480)上遇到了与你相同的问题,但我不认为这与硬件有关。您只是生成一个在编译时已知的数字,并且太大而无法为int
变量类型预先计算。我将int
更改为long
,现在程序正在构建。
我刚试过这个,使用英特尔平台。它汇编得很好。您也可以将开关-cl-opt-disable
传递给clBuildProgram
,使其适用于NVIDIA。这会禁用所有优化 - 您可能会对其他一些编译器开关感到满意。有关详细信息,请参阅clBuildProgram reference。
答案 2 :(得分:0)
Nvidia OpenCL编译器可能正在执行循环展开。如果它为每个嵌套循环执行此操作将导致生成大量代码!
有一个c l_nv_pragma_unroll Nvidia特定的OpenCL扩展 可以用来更好地控制循环展开。至 引用其文档:
用户可以指定展开源程序中的循环。这个 是通过一个pragma完成的。该pragma的语法如下
#pragma unroll [unroll-factor]
pragma展开可以选择指定展开因子。实用主义 必须紧接在循环之前放置并且仅适用于循环 循环。
如果未指定展开因子,则编译器将尝试执行此操作 完整或完全展开循环。如果循环展开因子是 指定编译器将执行部分循环展开。循环 factor,如果指定,则必须是编译时非负整数 恒定。
循环展开因子为1表示编译器不应展开 循环。
完整的展开规格如果行程计数没有影响 循环不是编译时可计算的。
默认情况下,它听起来会展开某些低的最大限制(例如,在您的示例中为10)但如果它们是嵌套的,则可能仍会展开(展开检查逻辑可能不够复杂以检查嵌套循环)
您可以尝试#pragma unroll 1
禁用展开:
int foo=1;
#pragma unroll 1
for (int a=1; a<=10; a++)
#pragma unroll 1
for (int b=1; b<=10; b++)
#pragma unroll 1
for (int c=1; c<=10; c++)
#pragma unroll 1
for (int d=1; d<=10; d++)
#pragma unroll 1
for (int e=1; e<=10; e++)
foo *= bar(a,b,c,d,e);
您还希望通过以下方式启用扩展程序:
#pragma OPENCL EXTENSION cl_nv_pragma_unroll : enable
也位于OpenCL源文件的顶部。