欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > IT业 > CUDA开始的GPU编程 - 第六章:thrust库

CUDA开始的GPU编程 - 第六章:thrust库

2025/9/19 3:13:26 来源:https://blog.csdn.net/lurenze/article/details/143698735  浏览:    关键词:CUDA开始的GPU编程 - 第六章:thrust库

第六章:thrust库

使用 CUDA 官方提供的 thrust::universal_vector

虽然自己实现 CudaAllocator 很有趣,也帮助我们理解了底层原理。但是既然 CUDA 官方已经提供了 thrust 库,那就用他们的好啦。

#include <cuda_runtime.h>
#include <thrust/universal_vector.h>  // trusth库#include <cstdio>#include "helper_cuda.h"template <class Func>
__global__ void parallel_for(int n, Func func) {for (int i = blockDim.x * blockIdx.x + threadIdx.x; i < n;i += blockDim.x * gridDim.x) {func(i);}
}int main() {int n = 65536;float a = 3.14f;thrust::universal_vector<float> x(n);  // 使用 thrust库的 universal_vectorthrust::universal_vector<float> y(n);for (int i = 0; i < n; i++) {x[i] = std::rand() * (1.f / RAND_MAX);y[i] = std::rand() * (1.f / RAND_MAX);}parallel_for<<<n / 512, 128>>>(n, [a, x = x.data(), y = y.data()] __device__(int i) { x[i] = a * x[i] + y[i]; });checkCudaErrors(cudaDeviceSynchronize());for (int i = 0; i < n; i++) {printf("x[%d] = %f\n", i, x[i]);}return 0;
}

universal_vector 是基于统一内存分配的,因此无论在 GPU 还是 CPU 上,数据都可以直接访问。

使用分离的 device_vector 和 host_vector

device_vector 在 GPU 上分配内存,而 host_vector 则在 CPU 上分配内存。

可以通过 = 运算符在 device_vector 和 host_vector 之间拷贝数据,他会自动帮你调用 cudaMemcpy,非常智能。

比如这里的 x_dev = x_host 会将 x_host 中的数据复制到 GPU 上的 x_dev

#include <cuda_runtime.h>
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>#include <cstdio>#include "helper_cuda.h"template <class Func>
__global__ void parallel_for(int n, Func func) {for (int i = blockDim.x * blockIdx.x + threadIdx.x; i < n;i += blockDim.x * gridDim.x) {func(i);}
}int main() {int n = 65536;float a = 3.14f;thrust::host_vector<float> x_host(n);thrust::host_vector<float> y_host(n);for (int i = 0; i < n; i++) {x_host[i] = std::rand() * (1.f / RAND_MAX);y_host[i] = std::rand() * (1.f / RAND_MAX);}thrust::device_vector<float> x_dev = x_host;thrust::device_vector<float> y_dev = x_host;parallel_for<<<n / 512, 128>>>(n, [a, x_dev = x_dev.data(), y_dev = y_dev.data()] __device__(int i) {x_dev[i] = a * x_dev[i] + y_dev[i];});x_host = x_dev;for (int i = 0; i < n; i++) {printf("x[%d] = %f\n", i, x_host[i]);}return 0;
}

使用 Thrust 模板函数:thrust::generate

Thrust 提供了与 C++ 标准库类似的模板函数,例如 thrust::generate(b, e, f),它的作用与 std::generate 相似,能够批量生成数据并填充到区间 [b, e) 中。第三个参数是一个函数,这里我们使用了一个 lambda 表达式。

前两个迭代器参数分别是 device_vectorhost_vector 的开始和结束迭代器,可以通过成员函数 begin()end() 获取。第三个参数可以是任意函数,这里用了 lambda 表达式。

#include <cuda_runtime.h>
#include <thrust/device_vector.h>
#include <thrust/generate.h>
#include <thrust/host_vector.h>#include <cstdio>#include "helper_cuda.h"template <class Func>
__global__ void parallel_for(int n, Func func) {for (int i = blockDim.x * blockIdx.x + threadIdx.x; i < n;i += blockDim.x * gridDim.x) {func(i);}
}int main() {int n = 65536;float a = 3.14f;thrust::host_vector<float> x_host(n);thrust::host_vector<float> y_host(n);auto float_rand = [] { return std::rand() * (1.f / RAND_MAX); };thrust::generate(x_host.begin(), x_host.end(), float_rand);thrust::generate(y_host.begin(), y_host.end(), float_rand);thrust::device_vector<float> x_dev = x_host;thrust::device_vector<float> y_dev = x_host;parallel_for<<<n / 512, 128>>>(n, [a, x_dev = x_dev.data(), y_dev = y_dev.data()] __device__(int i) {x_dev[i] = a * x_dev[i] + y_dev[i];});x_host = x_dev;for (int i = 0; i < n; i++) {printf("x[%d] = %f\n", i, x_host[i]);}return 0;
}

使用 Thrust 模板函数:thrust::for_each

同理,thrust::for_each(b, e, f) 对标 C++ 的 std::for_each,用于对区间 [b, e) 中的每个元素 x 调用函数 f(x)。其中,x 实际上是一个引用。如果使用常量迭代器,则是常引用,可以通过 cbegin()cend() 获取常值迭代器。

#include <cuda_runtime.h>
#include <thrust/device_vector.h>
#include <thrust/for_each.h>
#include <thrust/generate.h>
#include <thrust/host_vector.h>#include <cstdio>#include "helper_cuda.h"template <class Func>
__global__ void parallel_for(int n, Func func) {for (int i = blockDim.x * blockIdx.x + threadIdx.x; i < n;i += blockDim.x * gridDim.x) {func(i);}
}int main() {int n = 65536;float a = 3.14f;thrust::host_vector<float> x_host(n);thrust::host_vector<float> y_host(n);thrust::for_each(x_host.begin(), x_host.end(),[](float &x) { x = std::rand() * (1.f / RAND_MAX); });thrust::for_each(y_host.begin(), y_host.end(),[](float &y) { y = std::rand() * (1.f / RAND_MAX); });thrust::device_vector<float> x_dev = x_host;thrust::device_vector<float> y_dev = x_host;thrust::for_each(x_dev.begin(), x_dev.end(),[] __device__(float &x) { x += 100.f; });thrust::for_each(x_dev.cbegin(), x_dev.cend(),[] __device__(float const &x) { printf("%f\n", x); });parallel_for<<<n / 512, 128>>>(n, [a, x_dev = x_dev.data(), y_dev = y_dev.data()] __device__(int i) {x_dev[i] = a * x_dev[i] + y_dev[i];});x_host = x_dev;for (int i = 0; i < n; i++) {printf("x[%d] = %f\n", i, x_host[i]);}return 0;
}
  • 当然还有 thrust::reduce,thrust::sort,thrust::find_if,thrust::count_if,thrust::reverse,thrust::inclusive_scan 等。

Thrust 模板函数的特点:自动决定 CPU 或 GPU 执行

Thrust 的 for_each 可以作用于 device_vector 也可以作用于 host_vector。当作用于 host_vector 时,函数会在 CPU 上执行,而作用于 device_vector 时,则会在 GPU 上执行。

在这里插入图片描述

例如,针对 x_host 使用 for_each 时,lambda 表达式不需要修饰,而针对 x_dev 使用时,lambda 表达式需要加上 __device__ 修饰符。

使用 counting_iterator 实现整数区间循环

Thrust 中的迭代器区间操作(如 thrust::for_eachthrust::transformthrust::reduce 等)通常基于迭代器区间。counting_iterator 是一种特殊的迭代器,能够生成递增的整数序列,适合用于这种情况。

这是 Thrust 提供的一种特殊的迭代器,当需要在 Thrust 算法中使用一个递增整数序列时,counting_iterator 就可以作为迭代器传递

  • counting_iterator 实际上是一个生成器,它的作用是 生成一个递增的数值序列

thrust::make_counting_iterator(num) 构建一个计数迭代器,他作为区间表示的就是整数的区间。

#include <cuda_runtime.h>
#include <thrust/device_vector.h>
#include <thrust/for_each.h>
#include <thrust/generate.h>
#include <thrust/host_vector.h>#include <cstdio>int main() {thrust::for_each(thrust::make_counting_iterator(0),thrust::make_counting_iterator(10),[] __device__(int i) { printf("%d\n", i); });return 0;
}
//  0
//  1
//  2
//  3
//  4
//  5
//  6
//  7
//  8
//  9

使用 zip_iterator 合并多个迭代器

zip_iterator 可以看作是一个复合迭代器,它将多个容器(或迭代器)作为输入,生成一个新的迭代器,这个新的迭代器能够同时访问所有输入容器中对应位置的元素。在每次迭代时,zip_iterator 会返回多个容器中相应位置的元素。

可以通过 thrust::make_zip_iterator(a, b) 将多个迭代器合并,就像 Python 中的 zip 函数一样。

在使用时,可以通过 auto const &tup 来捕获每次迭代返回的元组,并使用 thrust::get<index>(tup) 获取其中第 index 个元素。之所以这么处理,是因为 Thrust 需要兼容一些使用较老标准(如 C++03)的程序,尽管现在可以更简洁地使用 C++11 的 std::tuple 和 C++17 的结构绑定语法。

#include <cuda_runtime.h>
#include <thrust/device_vector.h>
#include <thrust/for_each.h>
#include <thrust/generate.h>
#include <thrust/host_vector.h>#include <cstdio>#include "helper_cuda.h"template <class Func>
__global__ void parallel_for(int n, Func func) {for (int i = blockDim.x * blockIdx.x + threadIdx.x; i < n;i += blockDim.x * gridDim.x) {func(i);}
}int main() {int n = 65536;float a = 3.14f;thrust::host_vector<float> x_host(n);thrust::host_vector<float> y_host(n);auto float_rand = [] { return std::rand() * (1.f / RAND_MAX); };thrust::generate(x_host.begin(), x_host.end(), float_rand);thrust::generate(y_host.begin(), y_host.end(), float_rand);thrust::device_vector<float> x_dev = x_host;thrust::device_vector<float> y_dev = x_host;thrust::for_each(thrust::make_zip_iterator(x_dev.begin(), y_dev.cbegin()),thrust::make_zip_iterator(x_dev.end(), y_dev.cend()),[a] __device__(auto const &tup) {auto &x = thrust::get<0>(tup);auto const &y = thrust::get<1>(tup);x = a * x + y;});x_host = x_dev;for (int i = 0; i < n; i++) {printf("x[%d] = %f\n", i, x_host[i]);}return 0;
}

这样,我们能够通过 zip_iterator 同时操作多个容器。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com