Numba Cuda 计算似乎比顺序 运行 慢。我犯了明显的错误吗?

Numba Cuda computation seems to be slower than sequential run. Did I do obvious mistakes?

类似的主题有几个线程,但不幸的是,这些对我来说似乎太复杂了,所以我想问一个类似的问题,希望有人能具体看看我的代码来告诉我是否我搞错了。

我现在正在学习 numba cuda,从可以在网上找到的简单示例开始。我从这里开始学习本教程:

https://github.com/ContinuumIO/gtc2017-numba/blob/master/4%20-%20Writing%20CUDA%20Kernels.ipynb

显示如何并行添加数组。没有给出他们用来评估时间的系统配置。对于代码复制,我使用了 Geforce GTX 1080 Ti 和 Intel Core i7 8700K CPU。

我基本上从教程中复制了添加脚本,但还添加了顺序代码以进行比较:

from numba import cuda
import numpy as np
import time
import math

@cuda.jit
def addition_kernel(x, y, out):

    tx = cuda.threadIdx.x
    ty = cuda.blockIdx.x
    block_size = cuda.blockDim.x  
    grid_size = cuda.gridDim.x  

    start = tx+ ty * block_size
    stride = block_size * grid_size
    for i in range(start, x.shape[0], stride):
        out[i] = y[i] + x[i]

def add(n, x, y):
    for i in range(n):
        y[i] = y[i] + x[i]


if __name__ =="__main__":
    print(cuda.gpus[0])
    print("")
    n = 100000
    x = np.arange(n).astype(np.float32)
    y = 2 * x
    out = np.empty_like(x)
    x_device = cuda.to_device(x)
    y_device = cuda.to_device(y)
    out_device = cuda.device_array_like(x)



    # Set the number of threads in a block

    threadsperblock = 128

    # Calculate the number of thread blocks in the grid
    blockspergrid = 30#math.ceil(n[0] / threadsperblock)
    # Now start the kernel
    start = time.process_time()
    cuda.synchronize()
    addition_kernel[blockspergrid, threadsperblock](x_device, y_device, out_device)
    cuda.synchronize()
    end = time.process_time()
    out_global_mem = out_device.copy_to_host()
    print("parallel time: ", end - start)

    start = time.process_time()
    add(n,x,y)
    end = time.process_time()
    print("sequential time: ", end-start)

并行时间平均在0.14秒左右,而没有GPU内核的代码只需要0.02秒。

这对我来说似乎很奇怪。我做错了什么吗?或者这个问题不是并行性的好例子? (我不认为你可以 运行 并行循环)

奇怪的是,如果我不使用 to_device() 函数,我几乎看不出有什么不同。据我了解,这些应该很重要,因为它们避免了每次迭代后 CPU 和 GPU 之间的通信。

addition_kernel是在运行时编译的第一次调用的时候,所以在你测量的时间中间!内核的编译是一项相当密集的操作。您可以通过向 Numba 提供类型来强制编译提前完成(即在定义函数时)。

请注意,数组有点太小,因此您可以看到 GPU 的巨大改进。此外,与 CPU 版本的比较并不公平:您还应该将 Numba 用于 CPU 实现或至少使用 Numpy(但不是解释的纯 CPython 循环)。

这是一个例子:

import numba as nb

@cuda.jit('void(float32[::1], float32[::1], float32[::1])')
def addition_kernel(x, y, out):

    tx = cuda.threadIdx.x
    ty = cuda.blockIdx.x
    block_size = cuda.blockDim.x  
    grid_size = cuda.gridDim.x  

    start = tx+ ty * block_size
    stride = block_size * grid_size
    for i in range(start, x.shape[0], stride):
        out[i] = y[i] + x[i]

@nb.njit('void(int64, float32[::1], float32[::1])')
def add(n, x, y):
    for i in range(n):
        y[i] = y[i] + x[i]