【问题标题】:Cuda - OpenCL CPU 4x faster than OpenCL or CUDA GPU versionCuda - OpenCL CPU 比 OpenCL 或 CUDA GPU 版本快 4 倍
【发布时间】:2013-05-02 01:20:02
【问题描述】:

我一直在使用 C# + Cudafy(C# -> CUDA 或 OpenCL 翻译器)开发的波形模拟器效果很好,除了运行 OpenCL CPU 版本(Intel 驱动程序,15 " MacBook Pro Retina i7 2.7GHz, GeForce 650M (Kepler, 384 cores)) 的速度大约是 GPU 版本的四倍。

(无论我使用 CL 还是 CUDA GPU 后端都会发生这种情况。OpenCL GPU 和 CUDA 版本的性能几乎相同。)

为了澄清,对于一个示例问题:

  • OpenCL CPU 1200 赫兹
  • OpenCL GPU 320 Hz
  • CUDA GPU -~330 Hz

我不知道为什么 CPU 版本会 比 GPU。在这种情况下,在 CPU 和 GPU 上执行(在 CL 情况下)的内核代码是相同的。我在初始化期间选择了 CPU 或 GPU 设备,但除此之外,一切都是相同的。

编辑

这是启动其中一个内核的 C# 代码。 (其他的非常相似。)

    public override void UpdateEz(Source source, float Time, float ca, float cb)
    {
        var blockSize = new dim3(1);
        var gridSize = new dim3(_gpuEz.Field.GetLength(0),_gpuEz.Field.GetLength(1));

        Gpu.Launch(gridSize, blockSize)
            .CudaUpdateEz(
                Time
                , ca
                , cb
                , source.Position.X
                , source.Position.Y
                , source.Value
                , _gpuHx.Field
                , _gpuHy.Field
                , _gpuEz.Field
            );

    }

还有,Cudafy 生成的相关 CUDA 核函数如下:

extern "C" __global__ void CudaUpdateEz(float time, float ca, float cb, int sourceX, int sourceY, float sourceValue,  float* hx, int hxLen0, int hxLen1,  float* hy, int hyLen0, int hyLen1,  float* ez, int ezLen0, int ezLen1)
{
    int x = blockIdx.x;
    int y = blockIdx.y;
    if (x > 0 && x < ezLen0 - 1 && y > 0 && y < ezLen1 - 1)
    {
        ez[(x) * ezLen1 + ( y)] = ca * ez[(x) * ezLen1 + ( y)] + cb * (hy[(x) * hyLen1 + ( y)] - hy[(x - 1) * hyLen1 + ( y)]) - cb * (hx[(x) * hxLen1 + ( y)] - hx[(x) * hxLen1 + ( y - 1)]);
    }
    if (x == sourceX && y == sourceY)
    {
        ez[(x) * ezLen1 + ( y)] += sourceValue;
    }
}

为了完整起见,这里是用于生成 CUDA 的 C#:

    [Cudafy]
    public static void CudaUpdateEz(
        GThread thread
        , float time
        , float ca
        , float cb
        , int sourceX
        , int sourceY
        , float sourceValue
        , float[,] hx
        , float[,] hy
        , float[,] ez
        )
    {
        var i = thread.blockIdx.x;
        var j = thread.blockIdx.y;

        if (i > 0 && i < ez.GetLength(0) - 1 && j > 0 && j < ez.GetLength(1) - 1)
            ez[i, j] =
                ca * ez[i, j]
                +
                cb * (hy[i, j] - hy[i - 1, j])
                -
                cb * (hx[i, j] - hx[i, j - 1])
                ;

        if (i == sourceX && j == sourceY)
            ez[i, j] += sourceValue;
    }

显然,这个内核中的if 很糟糕,但即使是由此产生的管道停顿也不应该导致如此极端的性能增量。

唯一让我感到震惊的另一件事是我使用了一个蹩脚的网格/块分配方案 - 即,网格是要更新的数组的大小,每个块都是一个线程。我确信这对性能有一些影响,但我看不到它导致它是 CPU 上运行的 CL 代码速度的 1/4。啊!

【问题讨论】:

  • 你有一些代码示例可以分享吗?
  • @EricBainville 当然——你想要 C#、CUDA 或 CL 内核,还是什么? (这是一个半中型应用程序。我不想将 20k 行代码粘贴到 SO 中)
  • 我没有看到任何迹象表明 cuda 内核每个块使用超过 1 个线程(没有使用 threadIdx.xthreadIdx.y)。此外,启动指定每个块 1 个线程。这意味着大约 97% 的 GPU 功能未被使用。我对 cudafy 了解不多,所以我不知道你是否可以控制它,但我一点也不惊讶 cuda 代码运行速度不快。
  • 发布有关性能的问题时,请发布可重现的或使用 gridDim、blockDim 和所有循环执行的次数来注释源。启动 1 个线程的块不太可能允许 CPU 实现对代码进行矢量化。在 NVIDIA GPU 上,您将以远低于 1/32 的计算效率执行,而在 AMD GPU 上,您将以低于 1/64 的计算效率执行。我建议您分析 GPU 代码。
  • @HansRudel NVIDIA GPU 有关 SIMT 架构和执行模型的更多信息,请参阅 CUDA C 编程指南中的 SIMT Architecture。在计算能力为 1.0-3.5 的设备上,WARP_SIZE 为 32 个线程。指定具有 1 个线程的块将导致硬件执行 1 个具有 1 个活动线程和 31 个非活动线程的扭曲,从而导致 3% 的效率。 AMD GPU 在称为波前的 64 个线程组中管理和执行指令。

标签: c# cuda opencl cudafy.net


【解决方案1】:

回答此问题以将其从未回答列表中删除。

发布的代码表明内核启动指定了 1 个(活动)线程的线程块。这不是编写快速 GPU 代码的方法,因为它会使大部分 GPU 功能处于空闲状态。

典型的线程块大小应至少为每块 128 个线程,通常越高越好,以 32 的倍数计算,最高可达每块 512 或 1024 个的限制,具体取决于 GPU。

GPU“喜欢”通过“可用”大量并行工作来隐藏延迟。为每个块指定更多线程有助于实现此目标。 (在网格中拥有相当多的线程块也可能会有所帮助。)

此外,GPU 以 32 个为一组执行线程。指定每个块仅 1 个线程或指定 32 的非倍数将在每个被执行的线程块中留下一些空闲的执行槽。每个块 1 个线程特别糟糕。

【讨论】:

    猜你喜欢
    • 2012-01-26
    • 2013-08-03
    • 2015-09-23
    • 1970-01-01
    • 1970-01-01
    • 2011-02-08
    • 2013-04-07
    • 2012-02-17
    • 2011-04-17
    相关资源
    最近更新 更多