clBuildProgram陷入嵌套循环

时间:2014-02-25 10:18:04

标签: opencl

在尝试这种.cl文件时,clBuildProgram似乎卡住了,没有任何错误消息:

__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,也就是我能找到的最便宜的。

3 个答案:

答案 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源文件的顶部。