首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >OpenCL性能优化

OpenCL性能优化
EN

Stack Overflow用户
提问于 2012-08-05 12:08:43
回答 2查看 3.4K关注 0票数 5

我已经开始学习OpenCL,目前我正在尝试测试一个简单的骨骼动画算法可以在多大程度上提高性能。为此,我编写了一个程序,从随机生成的顶点和变换矩阵执行骨骼动画两次,一次是在纯C++中使用SSE优化的线性代数库,另一次是在GPU上使用我自己的OpenCL内核(我在Nvidia gtx460上测试)。

我从一个简单的内核开始,其中每个工作项恰好转换一个顶点,所有值都从全局内存中读取。因为我对这个内核的性能不满意,所以我试着对它进行了一些优化。我现在的内核是这样的:

代码语言:javascript
复制
inline float4 MultiplyMatrixVector(float16 m, float4 v)
{
    return (float4) (
        dot(m.s048C, v),
        dot(m.s159D, v),
        dot(m.s26AE, v),
        dot(m.s37BF, v)
    );
}


kernel void skelanim(global const float16* boneMats, global const float4* vertices, global const float4* weights, global const uint4* indices, global float4* resVertices)
{
    int gid = get_global_id(0);
    int lid = get_local_id(0);

    local float16 lBoneMats[NUM_BONES];
    async_work_group_copy(lBoneMats, boneMats, NUM_BONES, 0);

    barrier(CLK_LOCAL_MEM_FENCE);

    for (int i = 0 ; i < NUM_VERTICES_PER_WORK_ITEM ; i++) {
        int vidx = gid*NUM_VERTICES_PER_WORK_ITEM + i;

        float4 vertex = vertices[vidx];
        float4 w = weights[vidx];
        uint4 idx = indices[vidx];

        resVertices[vidx] = (MultiplyMatrixVector(lBoneMats[idx.x], vertex * w.x)
                + MultiplyMatrixVector(lBoneMats[idx.y], vertex * w.y)
                + MultiplyMatrixVector(lBoneMats[idx.z], vertex * w.z)
                + MultiplyMatrixVector(lBoneMats[idx.w], vertex * w.w));
    }
}

现在,我为每个工作项处理固定数量的顶点,并且只为每个工作项将所有骨骼矩阵预取一次到本地内存中,我相信这会带来更好的性能,因为之后可以从更快的本地内存中读取多个顶点的矩阵。不幸的是,这个内核的性能比我第一次尝试的要差,甚至比只有CPU的实现还要差。

为什么这种本应优化的性能如此之差?

如果有帮助,下面是我执行内核的方式:

代码语言:javascript
复制
#define NUM_BONES 50
#define NUM_VERTICES 30000
#define NUM_VERTICES_PER_WORK_ITEM 100
#define NUM_ANIM_REPEAT 1000

uint64_t PerformOpenCLSkeletalAnimation(Matrix4* boneMats, Vector4* vertices, float* weights, uint32_t* indices, Vector4* resVertices)
{
    File kernelFile("/home/alemariusnexus/test/skelanim.cl");

    char opts[256];
    sprintf(opts, "-D NUM_VERTICES=%u -D NUM_REPEAT=%u -D NUM_BONES=%u -D NUM_VERTICES_PER_WORK_ITEM=%u", NUM_VERTICES, NUM_ANIM_REPEAT, NUM_BONES, NUM_VERTICES_PER_WORK_ITEM);

    cl_program prog = BuildOpenCLProgram(kernelFile, opts);

    cl_kernel kernel = clCreateKernel(prog, "skelanim", NULL);

    cl_mem boneMatBuf = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, NUM_BONES*sizeof(Matrix4), boneMats, NULL);
    cl_mem vertexBuf = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, NUM_VERTICES*sizeof(Vector4), vertices, NULL);
    cl_mem weightBuf = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, NUM_VERTICES*4*sizeof(float), weights, NULL);
    cl_mem indexBuf = clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, NUM_VERTICES*4*sizeof(uint32_t), indices, NULL);
    cl_mem resVertexBuf = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR, NUM_VERTICES*sizeof(Vector4), NULL, NULL);

    uint64_t s, e;
    s = GetTickcount();

    clSetKernelArg(kernel, 0, sizeof(cl_mem), &boneMatBuf);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &vertexBuf);
    clSetKernelArg(kernel, 2, sizeof(cl_mem), &weightBuf);
    clSetKernelArg(kernel, 3, sizeof(cl_mem), &indexBuf);
    clSetKernelArg(kernel, 4, sizeof(cl_mem), &resVertexBuf);

    size_t globalWorkSize[] = { NUM_VERTICES / NUM_VERTICES_PER_WORK_ITEM };
    size_t localWorkSize[] = { NUM_BONES };

    for (size_t i = 0 ; i < NUM_ANIM_REPEAT ; i++) {
        clEnqueueNDRangeKernel(cq, kernel, 1, NULL, globalWorkSize, localWorkSize, 0, NULL, NULL);
    }

    clEnqueueReadBuffer(cq, resVertexBuf, CL_TRUE, 0, NUM_VERTICES*sizeof(Vector4), resVertices, 0, NULL, NULL);

    e = GetTickcount();

    return e-s;
}

我猜还有更多的东西可以优化,也许可以一起批处理其他一些全局读取,但首先我真的想知道为什么第一个优化不起作用。

EN

回答 2

Stack Overflow用户

发布于 2016-10-31 14:12:27

有两件事会影响你的练习效果。

1) OpenCL遵循不包含任何有关内联函数的C99标准,即clcc编译器要么忽略inline关键字并执行常规调用,要么静默支持内联。但它并未被强制支持该功能。

因此,最好将MultiplyMatrixVector定义为预处理器宏。虽然在你的情况下这不是一个大问题。

2)您错误地威胁了本地内存( LDM)。

尽管其等待时间比global memory在正确访问时的等待时间要短,但local memory容易发生存储体冲突。

您的顶点索引是根据每个工作项的步幅100来计算的。存储体的数量取决于正在使用的图形处理器,但通常是16或32,即如果所有这些变量都在不同的存储体中,您可以在一个周期内访问多达16(32)个四字节的LDM变量,而不会造成任何损失。否则,您将获得一个序列化的bank conflict (当两个或更多线程访问同一个库时)。工作组中的100个线程访问LDM中的数组,没有关于库冲突的特殊安排。此外,数组元素是float16,即单个元素跨越所有16个存储体(或32个存储体的一半)。因此,在MultiplyMatrixVector函数的每一行中都有一个存储体冲突。至少冲突16x32的累积degree (这里16是您访问的向量元素的数量,32是半波前或半翘曲的大小)。

这里的解决方案不是将该数组复制到LDM,而是使用CL_MEM_READ_ONLY在主机中分配它(您已经这样做了),并使用boneMats参数的__constant说明符声明您的内核。然后,OpenCL库将在GPU内的常量区域中分配内存,并且对该数组的访问将会很快:

代码语言:javascript
复制
kernel void skelanim(__constant const float16* boneMats, 
                     global const float4* vertices, 
                     global const float4* weights, 
                     global const uint4* indices, 
                     global float4* resVertices)
票数 1
EN

Stack Overflow用户

发布于 2012-08-05 21:44:37

看起来在计算开始之前,工作组中的每个线程都在复制相同的50个浮点数。这将使全局内存带宽饱和。

尝尝这个

代码语言:javascript
复制
if ( lid == 0 )
{
    async_work_group_copy(lBoneMats, boneMats, NUM_BONES, 0);
}

这在每个工作组中只执行一次复制。

票数 -2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/11813893

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档