在 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
是什么意思?
我的假设是将相同分辨率 MxN
的 K
图像加在一起。在这种情况下,展位 CPU 和 GPU 上的 O(K.M.N)
但恒定时间在 GPU 上会更好,因为 gfx 内存访问要快得多比在 CPU 方面。为此,GPU 核心通常比 CPU 核心多,因此有利于 GPU。
如果K
太大而无法同时放入GPU 纹理单元U
,那么您需要使用多个通道,所以O(K.M.N.log(K)/log(U)) K>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 上慢得多 - 为啥以及如何加快速度?