对于支持GPU编程的各种库,我发现我的算法在GPU与CPU上的性能更差。我认为这是由于两个设备之间的延迟通信造成的。
我的平台是W10x64,戴尔XPS 15笔记本电脑配备i7-7700HQ和GTX 1050。
如果我使用任何库,例如pytorch.cuda.FloatTensor
或触摸GPU阵列的cupy.ndarray
似乎需要大约20~40us。这是一个MWE:
import cupy as cu
ary = cu.empty((1))
const_one = cu.ones((1))
%timeit ary + const_one
> 18.5 µs ± 102 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
操作1个元素不是GPU的用途,这是一个人为的例子,显示两个数据的最小操作时间,两个数据都驻留在GPU上。
我相信cuda代码的结构是在GPU运行时构建和使用操作队列,所以这种延迟会随着时间的推移或更大的内存块而消失?
这是numpy和cupy中相同算法的完全比较,它完成了128x128光学瞳孔的双精度相位误差,并用它来创建点扩散函数。
我尽量小心减轻主机设备的转移;只有数组大小的整数存在于CPU上,因为我无法提前在GPU上获得它们。
初始设置:
precision = 'float32'
ary_size = 128
pad = ary_size // 2
cu0 = cu.zeros((1))
cu2 = cu.ones((1)) * 2
cu1 = cu.ones((1))
CUDA执行
%%timeit
x = cu.linspace(-cu1, cu1, ary_size, dtype=precision)
y = cu.linspace(-cu1, cu1, ary_size, dtype=precision)
xx, yy = cu.meshgrid(x, y)
rho, phi = cu.sqrt(xx**cu2 + yy**cu2), cu.arctan2(yy, xx)
phase_err = rho ** cu2 * cu.cos(phi)
mask = rho > cu1
wv_ary = cu.exp(1j * cu2 * np.pi * phase_err)
wv_ary[mask] = cu0
padded = cu.pad(wv_ary, ((pad, pad), (pad, pad)), mode='constant', constant_values=0)
psf = fftshift(fft2(ifftshift(padded)))
intensity_psf = abs(psf)**cu2
> 4.73 ms ± 86.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
Numpy等价物:
%%timeit
x = np.linspace(-1, 1, ary_size, dtype=precision)
y = np.linspace(-1, 1, ary_size, dtype=precision)
xx, yy = np.meshgrid(x, y)
rho, phi = np.sqrt(xx**2 + yy**2), np.arctan2(yy, xx)
phase_err = rho ** 2 * np.cos(phi)
mask = rho > 1
wv_ary = np.exp(1j * 2 * np.pi * phase_err)
wv_ary[mask] = 0
padded = np.pad(wv_ary, ((pad, pad), (pad, pad)), mode='constant', constant_values=0)
psf = nfftshift(nfft2(nifftshift(padded)))
intensity_psf = abs(psf)**2
> 7.29 ms ± 63.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
所以我用cuda只能获得35%的性能提升。我知道我没有特别强大的GPU,它的fp64比fp32性能差得多;但是用f32精度重复会导致速度无法显着增加。
我也知道如果我将尺寸改为更大的值,例如512,CUDA展示了更好的GPU性能,GPU的时间为8.19ms,CPU为144ms,
所以看起来这种GPU-CPU协调延迟是小阵列大小的原因。这是我笔记本电脑的怪癖吗?很难找到有关CPU-GPU延迟的信息,但有一些报告我看到PCI-E延迟小于1us。如果是这种情况,那么我的cuda代码将大约快20倍并且更加可用。
答案 0 :(得分:1)
似乎你的所有操作都是内存绑定的,可能除了你的GPU上的exp和atan之外。根据{{3}},GPU的内存带宽似乎为112GB / s。根据{{3}},您的CPU可能具有大约37GB / s的带宽。这是一个x4。
请注意,小数据集确实适合CPU的L2缓存,因此您可以假设写入后的读取位于缓存中(比dram快几个数量级)。这可能会发挥x2。
最后,当在GPU上启动此类操作时,问题的大小不足以让GPU隐藏延迟,因此您无法获得全部带宽:读取的成本更接近于它的延迟超过其吞吐量。如果填充读取总线的一半,则获得一半的带宽。
所有这些都可以通过NV教授验证或不分析您的代码。然后,您应该看到单个内核的时间和延迟。