在 GPU 上计算积分图像真的比在 CPU 上更快吗?

Posted

技术标签:

【中文标题】在 GPU 上计算积分图像真的比在 CPU 上更快吗?【英文标题】:Is computing integral image on GPU really faster than on CPU? 【发布时间】:2017-05-11 03:21:55 【问题描述】:

我是 GPU 计算的新手,所以这可能是一个非常幼稚的问题。 我做了一些查找,似乎在 GPU 上计算积分图像是一个不错的主意。 但是,当我真正深入研究它时,我想知道它可能并不比 CPU 快,尤其是对于大图像。所以我只想知道您对此的想法,以及 GPU 是否真的更快的一些解释。 所以,假设我们有一个 MxN 图像,积分图像的 CPU 计算大约需要 3xMxN 加法,即 O(MxN)。 在 GPU 上,按照《OpenGL 超级圣经》第 6 版提供的代码,需要一些 KxMxNxlog2(N) + KxMxNxlog2(M) 操作,其中 K 是大量移位、乘法、补充... GPU 可以并行工作,例如,一次 32 个像素,具体取决于设备,但仍然是 O(MxNxlog2(M))。 我认为即使在 640x480 的常见分辨率下,CPU 仍然更快。 我错了吗? [编辑] 这是直接来自书的着色器代码,想法是使用 2 遍:计算行的积分,然后计算第 1 遍的结果的列的积分。此着色器代码用于 1 遍。

#version 430 core
layout (local_size_x = 1024) in;
shared float shared_data[gl_WorkGroupSize.x * 2];
layout (binding = 0, r32f) readonly uniform image2D input_image;
layout (binding = 1, r32f) writeonly uniform image2D output_image;
void main(void)

    uint id = gl_LocalInvocationID.x;
    uint rd_id;
    uint wr_id;
    uint mask;
    ivec2 P = ivec2(id * 2, gl_WorkGroupID.x);
    const uint steps = uint(log2(gl_WorkGroupSize.x)) + 1;
    uint step = 0;
    shared_data[id * 2] = imageLoad(input_image, P).r;
    shared_data[id * 2 + 1] = imageLoad(input_image,
    P + ivec2(1, 0)).r;
    barrier();
    memoryBarrierShared();
    for (step = 0; step < steps; step++)
    
        mask = (1 << step) - 1;
        rd_id = ((id >> step) << (step + 1)) + mask;
        wr_id = rd_id + 1 + (id & mask);
        shared_data[wr_id] += shared_data[rd_id];
        barrier();
        memoryBarrierShared();
    
    imageStore(output_image, P.yx, vec4(shared_data[id * 2]));
    imageStore(output_image, P.yx + ivec2(0, 1),
    vec4(shared_data[id * 2 + 1]));

【问题讨论】:

试试看就知道了。 @InternetAussie 是的,我现在正在尝试。只是网上的研究表明GPU比CPU快得多,这让我很惊讶。 缺少并行算法的描述,但规定的界限似乎很糟糕。这让我觉得使用了理论方法,您可以在 1x1、1x2、2x2、2x4、4x4 零件上工作。也就是说,您递归地在更大的范围内工作,但只有很小的增量步骤。在实际代码中,您可能一次从 16x16 块开始。您甚至可以忽略并行化下一步,因为 16x16 块已经比输入像素少 256 倍 @MSalters 我添加了书中的代码,想法是计算行的积分,然后计算结果的列的积分。 @MSalters 我想我会试试你关于 16x16 块的建议,或者计算一些适合我情况的其他数字。谢谢 【参考方案1】:

integral image 是什么意思?

我的假设是将相同分辨率 MxNK 图像加在一起。在这种情况下,展位 CPUGPU 上的 O(K.M.N) 但恒定时间在 GPU 上会更好,因为 gfx 内存访问要快得多比在 CPU 方面。为此,GPU 核心通常比 CPU 核心多,因此有利于 GPU

如果K 太大而无法同时放入GPU 纹理单元U,那么您需要使用多个通道,所以O(K.M.N.log(K)/log(U)) K&gt;U... CPU 在某些情况下可能会更快。但正如之前的评论所建议的那样,您只能猜测。您还需要考虑诸如无绑定纹理和纹理数组之类的东西,它们允许在单次通过中执行此操作(但我不确定是否有任何性能成本)。

[Edit1] 清除您真正想做的事情后

为了简单起见,首先假设我们得到了正方形输入图像NxN 像素。因此,我们可以将任务分别分为 H 线和 V 线(类似于 2D FFT)以简化此过程。最重要的是,我们可以将每条线细分为M 像素组。所以:

N = M.K

其中N 是分辨率,M 是区域分辨率,K 是区域数。

    第一。通过

    为每个组渲染线,所以我们得到K 大小为M 的线。使用片段着色器计算每个区域的积分图像,只输出到一些纹理。这是T(0.5*K*M^2*N) 这整个事情可以在覆盖屏幕的单个QUAD渲染的片段中完成......

    第二。通过

    将区域积分转换为完整图像积分。所以再次渲染K 行并在片段中添加每个前一组的所有最后一个像素。这是T(0.5*K^3*N) 这整个事情也可以在由覆盖屏幕的单个 QUAD 渲染的片段中完成...

    对另一个轴方向的结果做#1,#2

整个事情都转换为

T(2*N*(0.5*K*M^2+0.5*K^3))
T(N*(K*M^2+K^3))
O(N*(K*M^2+K^3))

现在您可以将 M 调整到您的设置的最大性能...如果我将整个内容重写为 M,N 然后:

T(N*((N/M)*M^2+(N/M)^3))
T(N*(N*M+(N/M)^3))

所以你应该尽量减少热量,所以我会尝试使用周围的值

N*M = (N/M)^3
N*M = N^3/M^3
M^4 = N^2
M^2 = N
M = sqrt(N) = N^0.5

所以整个事情都转换为:

T(N*(N*M+(N/M)^3))
T(N*(N*N^0.5+(N/N^0.5)^3))
T(N^2.5+N^1.5)
O(N^2.5)

这比 naive O(N^4) 快但是你是对的 CPU 有更少的操作要做O(N^2) 并且不需要数据副本或多次传递,所以你应该找出您的任务的特定HW的阈值分辨率,并根据测量结果进行选择。 PS希望我没有在计算中的某个地方犯下愚蠢的错误。此外,如果您在 CPU 上分别进行 H 和 V 线,则 CPU 方面的复杂性将是 O(N^3) 并且使用这种方法甚至 O(N^2.5) 无需每次通过 2 次轴。

看看这个类似的 QA:

How to implement 2D raycasting light effect in GLSL

我认为这是一个很好的起点。

【讨论】:

GPU 胜过 CPU 的原因在于它的并行度很高。您只能在 CPU 上并行执行很多操作(每个内核约 2 个线程,并且取决于体系结构,每个时钟周期最多约 4 条指令),但这是针对极其密集和优化的代码。但是,有些问题难以并行化,拆分问题并重新加入结果的开销会增加大量开销(如本问答所示)。一般来说,如果拆分/连接复杂度是对数的,那么你基本上是好的。 嗯,简而言之,积分图像有一个像素 'P[a,b] = sum(A[i,j])' 与 'i en.wikipedia.org/wiki/Summed_area_table] @Spektre 非常感谢您的详细回答,我现在正在尝试看看哪个更适合我

以上是关于在 GPU 上计算积分图像真的比在 CPU 上更快吗?的主要内容,如果未能解决你的问题,请参考以下文章

在 GPU 上训练比在 CPU 上慢得多 - 为啥以及如何加快速度?

在 GPU 上选择性地注册一组操作的后向传递

为啥在 GPU 中执行方法的时间比在混合器项目中的 CPU 中执行的时间更多?

tensorflow下载和安装

让GPU跑的更快

HTML5 Canvas 在 Firefox 上比在 Chrome 上更快!为啥?