【CUDA】 Trust基本特性介绍及性能分析

Trust简介

Thrust 是一个实现了众多基本并行算法的 C++ 模板库,类似于 C++ 的标准模板库(standard template library, STL)。该库自动包含在 CUDA 工具箱中。这是一个模板库,仅仅由一些头文件组成。在使用该库的某个功能时,包含需要的头文件即可。该库中的所有类型与函数都在命名空间thrust中定义,所以都以thrust::开头。用命名空间的目的是避免名称冲突。例如,Thrust中的thrust::sort和STL 中的 std::sort 就不会发生名称冲突。

数据结构

Thrust 中的数据结构主要是矢量容器(vector container),类似于 STL中的std::vector。在 Thrust 中,有两种矢量:

(1)一种是存储于主机的矢量 thrust::host_vector<typename>。

(2)一种是存储于设备的矢量 thrust::device_vector<typename>。这里的 typename 可以是任何数据类型。例如,下面的语句定义了一个设备矢量x,元素类型为双精度浮点数(全部初始化为0),长度为10:

thrust::device_vector<double>x(10,0);

要使用这两种矢量,需要分别包含如下头文件:

#incldue <thrust/host vector.h>

#incldue <thrust/device vector.h>

算法

Thrust 提供了5类常用算法,包括

(1)变换(transformation)。

(2)归约(reduction)。

(3)前缀和(prefxsum)。

(4)排序(sorting)与搜索(searching)。

(5)选择性复制、替换、移除、分区等重排(reordering)操作。

除了 thrust::copy,Thrust 算法的参数必须都来自于主机矢量或都来自于设备矢量。否则,编译器会报错。


实例分析

在了解 Thrust 库更多的细节之前,我们先分析Code1所示的程序,这个程序展示了Thrust库的一些显著特点。

Code1

#include <iostream>
#include <cstdio>
#include <ctime>
#include <cmath>#include <cuda_runtime.h>
#include <thrust/host_vector.h>
#include <thrust/device_vector.h>
#include <thrust/generate.h>
#include <thrust/sort.h>
#include <cstdlib>int main()
{thrust::host_vector<int> h_vec(1 << 24);thrust::device_vector<int> d_vec = h_vec;thrust::generate(h_vec.begin(), h_vec.end(), rand);thrust::sort(d_vec.begin(), d_vec.end());thrust::copy(d_vec.begin(), d_vec.end(), h_vec.begin());return 0;
}

Code1分配了两个向量容器:host_vector与 device_vector。host_vector位于主机端,device_vector位于GPU设备端。Thrust 的向量容器与C++ STL中的向量容器类似,host_vector与 device_vector 是通用的容器(即可以存储任何数据类型),可以动态调整大小。如Code1所示,容器可以自动分配和释放内存空间并且简化主机端和设备端之间的数据交换。

程序在向量容器上执行时,使用了generate、sort和copy算法。采用了STL中的迭代器进行遍历。在这个例子中,迭代器h_vec.beginO和h_vec.end()分别指向容器的第一个元素和最后一个元素的后一个位置(与STL一致左闭右开)。通过计算h_vec.end() – h_vec.beginO,我们可以得到容器的大小。

注意,在执行排序算法的时候,Thrust 会建议启动一个或多个CUDA kernel,但编程人员并不需要进行相关配置,因为Thrust的接口已经将这些细节抽象化了。对于性能敏感变量(比如 Thrust 库的网格和块大小)的选择,内存管理的细节,甚至排序算法的选择都留给具体实现的人自行决定。

迭代器和内存空间

虽然向量迭代器类似于数组的指针,但它们还包含了一些额外的信息。注意,我们不需要指定在 device_vector 元素上操作的sort算法,也不用暗示复制操作是从设备内存端到主机内存端。在Thrust库中,每个范围的内存空间可以通过迭代器参数自动推断,并调度合适的算法进行执行。

另外,关于内存空间,Thrust 的迭代器对大量信息进行隐式编码,这些信息可以用来指导进程调度。比如,Code1中sort的例子,它对基本的整型数据类型进行比较操作。在这个例子中,Thrust库中采用高度优化的基数排序(radix sort)算法,要比基于数据之间比较的排序算法(例如归并排序算法速度快很多。需要注意的是,这个调度过程并不会造成性能或存储开销:迭代器对元数据编码只存在于编译阶段并且它的调度策略已经确定。实际上,Thrust的静态调度策略可以利用迭代器类型的任何信息。

互操作性

Thrust库完全由CUDA C/C++实现,并且保持了与CUDA 生态系统其余部分的互操作性。互操作性是一个重要特性,因为没有一个单一的语言或库能够很好地解决所有问题。例如,尽管Thrust 算法在内部使用了像共享存储器的CUDA特性,但是并没有为用户提供机制通过 Thrust库直接使用共享存储器。因此,有时候应用程序需要直接访问CUDAC,实现一些特定的算法。Thrust和CUDA C之间的互操作性允许程序员只修改少量外围代码,就能用CUDA kerel函数替换Thrust kerel函数,反之亦然。

将Thrust转换成CUDA C很简单,类似于用标准C代码使用C++STL。外部库通过从向量中抽取“原始”指针,可以访问驻留在Thrust容器中的数据。Code2中的代码示例说明了使用原始指针转换,得到指向device_vector内容的整型指针。

Code2

//Thrust 与 CUDA C/C++的互操作//Thrust dev To CUDA kernel
thrust::device_vector<int> d_vec(1 << 24);thrust::device_vector<int> dev_Y;reduction1<int> << <gridDim, threads, threads.x * sizeof(double) >> > (thrust::raw_pointer_cast(d_vec.data()),temp,thrust::raw_pointer_cast(dev_Y.data()));//CUDA dev To Thrust devint* h_test = (int*)malloc((1 << 24) * sizeof(int));int* d_test;cudaMemcpy(d_test, h_test, (1 << 24) * sizeof(int),cudaMemcpyHostToDevice)thrust::device_ptr<int> dev_ptr = thrust::device_pointer_cast(d_test);thrust::sort(dev_ptr, dev_ptr + (1 << 24));

在Code2中,函数raw_pointer_cast()接受设备向量d_vec的元素0的地址(.data()与STL类似)作为参数,并且返回原始C指针raw_ptr。这个指针可用于调用CUDA C API函数(如cudaMemset()函数),或者作为参数传递到CUDA C kerel函数中(reduction1函数)。

将 Thrust 算法应用到原始C指针也很简单。一旦原始指针经过 device_ptr 的包装,它便能作为普通的 Thrust迭代器。

Code2中,C指针raw_ptr 指向设备内存中由函数cudaMalloc()分配的一片内存。通过 device_pointer_cast()函数,它可以转换为指向设备向量的设备指针。转换后的指针提供了一些内存空间信息,以便Thrust库调用适当的算法实现,并且为从主机端访问设备存储器提供了方便的机制。在这个例子中,这些信息指明dev_ptr指向设备内存中的向量并且元素类型是整型。

Thrust的原生CUDA C的互操作性保证Thrust总是能作为CUDA C的很好补充,Thrust和CUDA C的结合使用通常比单独使用CUDA C或者Thrust效果好。事实上,即使能够完全使用 Thrust 函数编写完整的并行程序,但是在某些特定领域内直接使用CUDA C实现函数功能会取得更好的结果。原生CUDA C的抽象层次允许程序员能够细粒度地控制计算资源到特定问题的精确映射。在这个层次上编程给开发者提供了实现特定算法的灵活性。互操作性也有利于迭代开发策略:(1)使用Thrust库快速开发出并行应用的原型:(2)确定程序热点;(3)使用CUDA C实现特定算法并作必要优化。

Thrust性能分析

Code

耗时测试代码

#include <iostream>
#include <cstdio>
#include <ctime>
#include <cmath>#include <cuda_runtime.h>
#include <thrust/host_vector.h>
#include <thrust/device_vector.h>
#include <thrust/generate.h>
#include <thrust/sort.h>
#include <cstdlib>#include "helper_cuda.h"
#include "error.cuh"using namespace std;const int FORTIME = 50;template<typename T> __global__
void reduction1(T* X, uint32_t n, T* Y) {extern __shared__ uint8_t shared_mem[];T* partial_sum = reinterpret_cast<T*>(shared_mem);uint32_t tx = threadIdx.x;uint32_t i = blockIdx.x * blockDim.x + threadIdx.x;partial_sum[tx] = i < n ? X[i] : 0;__syncthreads();for (uint32_t stride = 1; stride < blockDim.x; stride <<= 1) {if (tx % (2 * stride) == 0)partial_sum[tx] += tx + stride < n ? partial_sum[tx + stride] : 0;__syncthreads();}if (tx == 0) Y[blockIdx.x] = partial_sum[0];
}template<typename T>
void rand_array(T* array, size_t len) {for (int i = 0; i < len; ++i) {array[i] = ((T)rand()) / RAND_MAX;}
}int main(int argc, char* argv[])
{thrust::host_vector<int> h_vec(1 << 24);cout <<"Test Mem :\t" << (1 << 24) * sizeof(int) / 1024 / 1024 << "MB" << endl;thrust::host_vector<int> h_vec1(5);thrust::generate(h_vec1.begin(), h_vec1.end(), rand);h_vec1[0] = 0;h_vec1[4] = 4;cout << "h_vec1[4] = \t" << h_vec1[4] << endl << "h_vec1.end() - 1 = \t" << *(h_vec1.end() - 1) << endl;thrust::generate(h_vec.begin(), h_vec.end(), rand);thrust::device_vector<int> d_vec(1 << 24);cudaEvent_t start, stop;float elapsed_time;checkCudaErrors(cudaEventCreate(&start));checkCudaErrors(cudaEventCreate(&stop));checkCudaErrors(cudaEventRecord(start));for (int i = 0; i < FORTIME; i++)d_vec = h_vec;checkCudaErrors(cudaEventRecord(stop));checkCudaErrors(cudaEventSynchronize(stop));checkCudaErrors(cudaEventElapsedTime(&elapsed_time, start, stop));std::cout << "thrust HostToDevice elapsed_time:" << elapsed_time / FORTIME << std::endl;thrust::sort(d_vec.begin(), d_vec.end());checkCudaErrors(cudaEventRecord(start));for (int i = 0; i < FORTIME; i++)thrust::copy(d_vec.begin(), d_vec.end(), h_vec.begin());checkCudaErrors(cudaEventRecord(stop));checkCudaErrors(cudaEventSynchronize(stop));checkCudaErrors(cudaEventElapsedTime(&elapsed_time, start, stop));std::cout << "thrust Copy DeviceToHost elapsed_time:" << elapsed_time / FORTIME << std::endl;checkCudaErrors(cudaEventRecord(start));for (int i = 0; i < FORTIME; i++)h_vec = d_vec;checkCudaErrors(cudaEventRecord(stop));checkCudaErrors(cudaEventSynchronize(stop));checkCudaErrors(cudaEventElapsedTime(&elapsed_time, start, stop));std::cout << "thrust DeviceToHost elapsed_time:" << elapsed_time / FORTIME << std::endl;//-------------------------------------------------------int* h_test = (int*)malloc((1 << 24) * sizeof(int));int* d_test;if (h_test == nullptr)return -1;rand_array(h_test, 1 << 24);checkCudaErrors(cudaMalloc((void**)&d_test, (1 << 24) * sizeof(int) ));checkCudaErrors(cudaEventRecord(start));for (int i = 0; i < FORTIME; i++)checkCudaErrors(cudaMemcpy(d_test, h_test, (1 << 24) * sizeof(int),cudaMemcpyHostToDevice));checkCudaErrors(cudaEventRecord(stop));checkCudaErrors(cudaEventSynchronize(stop));checkCudaErrors(cudaEventElapsedTime(&elapsed_time, start, stop));std::cout << "cudaMemcpy HostToDevice elapsed_time:" << elapsed_time / FORTIME << std::endl;checkCudaErrors(cudaEventRecord(start));for (int i = 0; i < FORTIME; i++)checkCudaErrors(cudaMemcpy(h_test, d_test, (1 << 24) * sizeof(int), cudaMemcpyDeviceToHost));checkCudaErrors(cudaEventRecord(stop));checkCudaErrors(cudaEventSynchronize(stop));checkCudaErrors(cudaEventElapsedTime(&elapsed_time, start, stop));std::cout << "cudaMemcpy DeviceToHost elapsed_time:" << elapsed_time / FORTIME << std::endl;//Thrust 与 CUDA C/C++的互操作thrust::device_ptr<int> dev_ptr = thrust::device_pointer_cast(d_test);thrust::sort(dev_ptr, dev_ptr + (1 << 24));thrust::device_vector<int> dev_Y;dim3 threads(1024);dim3 gridDim;uint32_t temp = 1 << 24; int sumTime = 0;do {gridDim = dim3((temp + threads.x - 1) / threads.x);d_vec = dev_Y;dev_Y.resize(gridDim.x);checkCudaErrors(cudaEventRecord(start));reduction1<int> << <gridDim, threads, threads.x * sizeof(double) >> > (thrust::raw_pointer_cast(d_vec.data()),temp,thrust::raw_pointer_cast(dev_Y.data()));checkCudaErrors(cudaEventRecord(stop));checkCudaErrors(cudaEventSynchronize(stop));checkCudaErrors(cudaEventElapsedTime(&elapsed_time, start, stop));sumTime += elapsed_time;temp = gridDim.x;} while (temp > 1);free(h_test);cudaFree(d_test);return 0;
}

具体代码参考Code

可见Thrust的HostToDev、DevToHost和copy()耗时与CUDA C相似。


Reduction函数耗时分析:

Thrust虽然方便但是相对于固定优化的CUDA C耗时更长。其它Reduction函数请参考:【CUDA】 归约 Reduction

参考文献:

1、大规模并行处理器编程实战(第2版)

2、​​​CUDA C 编程:基础与实践

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/43124.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【linux】 sudo apt update报错——‘由于没有公钥,无法验证下列签名: NO_PUBKEY 3B4FE6ACC0B21F32’

【linux】 sudo apt update报错——‘由于没有公钥&#xff0c;无法验证下列签名&#xff1a; NO_PUBKEY 3B4FE6ACC0B21F32’ 在运行sudo apt update时遇到报错&#xff0c;由于没有公钥&#xff0c;无法验证下列签名&#xff1a; NO_PUBKEY 3B4FE6ACC0B21F32 解决方法&#x…

Qt:11.输入类控件(QLineEdit-单行文本输入控件、QTextEdit-多行文本输入控件、QComboBox-下拉列表的控件)

一、QLineEdit-单行文本输入控件&#xff1a; 1.1QLineEdit介绍&#xff1a; QLineEdit 是 Qt 库中的一个单行文本输入控件&#xff0c;不能换行。允许用户输入和编辑单行文本。 1.2属性介绍&#xff1a; inputMask 设置输入掩码&#xff0c;以限定输入格式。setInputMask(con…

react学习——25redux实现求和案例(完整版)

1、目录结构 2、count/index.js import React, {Component} from "react"; //引入store,用于获取数据 import store from ../../redux/store //引入actionCreator 专门创建action对象 import {createDecrementAction,createIncrementAction} from ../../redux/coun…

CSS【详解】边框 border,边框-圆角 border-radius,边框-填充 border-image,轮廓 outline

边框 border border 是以下三种边框样式的简写&#xff1a; border-width 边框宽度 —— 数值 px&#xff08;像素&#xff09;,thin&#xff08;细&#xff09;,medium&#xff08;中等&#xff09;,thick&#xff08;粗&#xff09;border-style 边框线型 —— none【默认值…

78. UE5 RPG 创建技能数据并初始化技能ui

在上一篇文章里&#xff0c;我们创建了技能的UI&#xff0c;接下来&#xff0c;我们要考虑如何实现对技能UI的填充&#xff0c;肯定不能直接写死&#xff0c;需要有一些方法去实现技能的更新。我们期望能够创建一个技能数据&#xff0c;然后根据数据通过回调的方式实现数据的更…

一键掌握天气动态 - 基于Vue和高德API的实时天气查询

前言 本文将学习如何使用Vue.js快速搭建天气预报界面,了解如何调用高德地图API获取所需的天气数据,并掌握如何将两者有机结合,实现一个功能丰富、体验出色的天气预报应用 无论您是前端新手还是有一定经验,相信这篇教程都能为您带来收获。让我们一起开始这段精彩的Vue.js 高德…

桌面悬浮备忘录哪个好?能在桌面悬浮使用的备忘app

备忘录是我们日常工作和生活中的常用工具&#xff0c;它帮助我们记录重要信息&#xff0c;提醒我们完成各项任务。而将备忘录悬浮在桌面上使用&#xff0c;无疑能进一步提高我们的工作效率。想象一下&#xff0c;在处理复杂的工作任务时&#xff0c;你能够随时在桌面上查看提醒…

JS获取本机ip地址方法

前端获取本机ip地址&#xff1b;使用第三方免费API <script>function ipJson(ipJson) {console.log(获取到的网络IP,ipJson);//可以把结果存在window上&#xff0c;方便调用window.ipJson ipJson;} </script> <script src"https://whois.pconline.com.cn/…

产品使用手册深度剖析:五步快速敲定产品手册策划思路

引言 在这个信息爆炸的时代&#xff0c;产品使用手册不仅是产品的“说明书”&#xff0c;更是品牌与用户之间建立情感连接的桥梁。一份优秀的手册&#xff0c;能够迅速吸引用户的注意力&#xff0c;引导他们轻松上手&#xff0c;并深入体验产品的魅力。那么&#xff0c;如何撰…

ruoyi项目swagger文档升级knife4j文档

注释admin模块中的swagger依赖加入knife4j依赖 <!-- swagger3--> <!-- <dependency>--> <!-- <groupId>io.springfox</groupId>--> <!-- <artifactId>springfox-boot-starter</artifactId>--…

IDEA常用技巧荟萃:精通开发利器的艺术

1 概述 在现代软件开发的快节奏环境中&#xff0c;掌握一款高效且功能全面的集成开发环境&#xff08;IDE&#xff09;是提升个人和团队生产力的关键。IntelliJ IDEA&#xff0c;作为Java开发者的首选工具之一&#xff0c;不仅提供了丰富的编码辅助功能&#xff0c;还拥有高度…

预算有限?如何挑选经济适用的安全管理系统?

如今&#xff0c;无论是信息安全、生产安全还是人员安全&#xff0c;都直接关系到企业的稳定运营和长远发展。然而&#xff0c;对于许多中小企业而言&#xff0c;高昂的安全管理系统投入往往成为一大难题。那么&#xff0c;在预算有限的情况下&#xff0c;如何挑选一款既经济适…

Github 2024-07-07php开源项目日报 Top9

根据Github Trendings的统计,今日(2024-07-07统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目9Blade项目2JavaScript项目1Laravel:表达力和优雅的 Web 应用程序框架 创建周期:4631 天开发语言:PHP, BladeStar数量:75969 个Fork数…

如何整合生成的人工智能?(GenAI)为你未来的工作增加动力

生成人工智能(GenAI)它发展迅速&#xff0c;以前所未有的速度取得了突破。人工智能将继续改变各行各业&#xff0c;预计2023年至2030年的年增长率将达到37.3%。由于一种新的知识工作者现在面临被取代的风险&#xff0c;生成式人工智能的惊人崛起进一步加剧了这种紧迫性。据《未…

嘎嘎详细的三维变换详细讲解,包括视图变换、投影变换等,超级通俗易懂!

前置二维空间的各种变换笔记&#xff1a;二维变换 三维空间中的齐次坐标 从二维变换开始引申&#xff0c;可得到三维中的一个点的表达方式为 ( x , y , z , 1 ) ⊤ (\mathbf{x}, \mathbf{y}, \mathbf{z}, 1)^{\top} (x,y,z,1)⊤&#xff0c;也就是w1&#xff0c;而三维的向量…

插入排序算法(C语言版)

直接插入排序 插入排序&#xff08;insert sort&#xff09;是一种简单的排序算法&#xff0c;它的工作原理与手动整理一副牌的过程非常相似。 具体来说&#xff0c;我们在未排序区间选择一个基准元素&#xff0c;将该元素与其左侧已排序区间的元素逐一比较大小&#xff0c;并…

(自用)gtest单元测试

gtest是Google的一套用于编写C测试的框架&#xff0c;可以运行在很多平台上&#xff08;包括Linux、Mac OS X、Windows、Cygwin等等&#xff09;。基于xUnit架构。支持很多好用的特性&#xff0c;包括自动识别测试、丰富的断言、断言自定义、死亡测试、非终止的失败、生成XML报…

Mybatis的优缺点及适用场景?

目录 一、什么是Mybatis&#xff1f; 二、Mybatis框架的特点 三、Mybatis框架的优点&#xff1f; 四、MyBatis 框架的缺点&#xff1f; 五、MyBatis 框架适用场合&#xff1f; 六、代码示例 1. 配置文件 mybatis-config.xml 2. 映射文件 UserMapper.xml 3. Java 代码…

QT TCP网络通信编程

学习目标&#xff1a; TCP网络通信编程 前置环境 运行环境:qt creator 4.12 学习内容 一、TCP 协议基础知识: TCP 是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 拥塞控制算法包括慢启动、拥塞避免、快速重传和快速恢复。TCP 通信需要建立连接,Qt 提供 QTcp…

利用Python的sympy包求解一元多次方程

一元1次方程 import sympy as sp # 导入sympy包 x sp.Symbol(x) # 定义符号变量 f 2*x -8 # 定义要求解的一元1次方程 x sp.solve(f) # 调用solve函数求解方程 x[4]一元2次方程 import sympy as sp # 导入sympy包 x sp.Symbol(x) # 定义符号变量 f …