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]
类似的主题有几个线程,但不幸的是,这些对我来说似乎太复杂了,所以我想问一个类似的问题,希望有人能具体看看我的代码来告诉我是否我搞错了。
我现在正在学习 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]