基于CUDA on Clion的信息学竞赛算法优化探究(待施工,征求意见中)
研究性学习报告:基于CUDA的计算机编程探究
DL24JP24级6班 刘君睿
2025年8月
一、简介
I.选题背景与研究目标
随着第四次科技革命时代的到来,计算机科学与技术、人工智能等高新技术领域逐步为更多人所熟知。为提升科技创新能力,培养多学科并用思维,了解高新技术领域的知识,我选择CUDA这一现代图形化交互程序和深度学习项目中常用的并行计算平台为研究主题,从软件层面为主入手,尝试探究它的发展背景、计算功能和实际应用,并尝试通过设计一款程序作为成果来体现CUDA平台的高效计算能力。
II.活动信息与硬件准备
活动学期:高一下学期
课题类型:课题研究
开始时间:2025-08-1
结束时间:2025-08-27
课题名称:基于CUDA的计算机编程探究
组织形式:个人独立完成
合作伙伴:无
指导教师:李成博
本研究性学习仅本人以个人身份完成全部过程,指导教师为我校综艺组组长兼信息学竞赛队教练李成博老师。
研究性学习由2025年8月1日开始,持续至2025年8月27日,活动时间为每天上午7:30至下午16:30,在此期间本人在家利用家中计算机进行研究。
本人独立完成项目选择、资料查询、初步探究与代码实现等全部环节。
指导教师李成博老师负责对研究方向与方法提供建设性意见。
另提供本人使用的计算机处理器配置:
中央处理器:Intel Core i5-9300H
图形处理器:NVIDIA GeForce GTX 1650 Laptop
III.研究内容纲要
经过分析与简要资料查询,我选择基于以下四个方面进行研究性学习:
1.CUDA平台的诞生背景及发展历史
2.CUDA平台的计算功能及优化原理
3.CUDA平台的代码探究与程序实现
4.CUDA技术的未来前景及实际应用
二、研究过程
I.CUDA平台的诞生背景及发展历史
20世纪末,由于当时的中央处理器羸弱的运算性能不足以支撑一些图形渲染和物理运算[ 例:3D绘图软件和3D游戏],科技公司厂商发明了专用于图形计算的3D加速卡[ 3D加速卡不同于显卡。对于20世纪末,3D加速卡必须搭配额外的2D显卡使用。2D显卡只负责2D 图形显示、桌面输出,几乎没有计算能力。],用于分担CPU的计算压力。
1999年,英伟达发明了真正意义上的世界第一枚GPU[ GPU的定义为:集成了变换、照明、三角形设置/裁剪和渲染引擎的单芯片处理器,Geforce256前的3D加速卡不具备如上功能,因此不算GPU。可以理解为Geforce256是一款拥有图形处理器的显卡,而此图形处理器同时具有3D加速和图形输出功能。此后的GPU狭义上指显卡内部的图形处理器芯片。]——Geforce256。
21世纪初,因为GPU优越的计算能力,科研人员希望将其运用于科学计算,但因GPU只能用于图形计算,科研人员设计初一些基础的着色器语言,将科学计算问题转化成图形计算问题,把 GPU“滥用”来做科学计算。
2006年,伴随着英伟达GeForce 8系列GPU的发布,NVIDIA 正式推出第一个通用并行计算平台,引入了其设计研发的统一计算设备技术,也就是CUDA[ 全称Compute Unified Device Architecture,缩写为CUDA。]技术,CUDA正式诞生。CUDA平台允许开发者直接用 C语言来编写 GPU 程序,而不用再用传统着色器语言的图形API接口。
2007年,CUDA SDK开始向学术界推广,科研人员用它加速物理模拟、分子动力学[ 例:AMBER,一款空气动力学模拟程序。]等任务。
2008年,CUDA 2.0支持C++语言,CUDA生态逐渐扩大。
2010–2018年,随着多代新架构的发布,CUDA平台更加成熟,运算性能大幅提升。同时深度学习兴起CUDA 成为 AI 训练的事实标准平台。
2019–2020年,随着GPU架构的进一步更新迭代,CUDA生态已广泛覆盖科研、工业、AI等领域。
2022–2023年,一些CUDA 的替代/竞争技术平台方案相继成熟,但CUDA依然是主流的计算平台。
II.CUDA平台的计算功能及优化原理
随着显卡的发展,GPU越来越强大,而且GPU为显示图像做了优化。在计算上已经超越了通用的CPU。如此强大的芯片如果只是作为显卡就太浪费了。
在CUDA出现之前,GPU只能做图形渲染,比如玩游戏时把场景画出来。想用GPU做科学计算或者AI,就很麻烦,需要用着色器语言来调用图形API“变相计算”。CUDA 让你可以 用类似 C/C++ 的语言直接写程序,让 GPU 做任意大规模计,用于图像计算以外的目的。
使用CUDA技术,GPU可以利用其内部的流处理器(CUDA核心)进行通用计算(不仅仅是3D图形计算);这种方法被称为GPGPU。与CPU不同的是,GPU以较慢速度并发大量线程,而非快速执行单一线程。以GeForce 8800 GTX为例,其核心拥有128个流处理器。利用CUDA技术,就可以将那些内处理器做为线程处理器,以解决数据密集的计算。
举个例子。在CUDA平台下,CPU可以类比于工厂的管理层,负责复杂逻辑和控制,GPU则是超级计算工厂,有成千上万的小工人同时工作。
简单来说,CUDA是让GPU变成通用计算器的技术,核心思想是利用大量线程同时干同类工作。
GPU的性能潜力极高,但想要真正发挥CUDA平台带来的的强大计算能力,需要遵循一定的优化原则。CUDA优化问题的核心在于如何让GPU的海量核心高效运行,并避免内存瓶颈[ 内存瓶颈:GPU的计算能力很强,但数据访问(读/写内存)的速度可能跟不上,GPU等数据等得太久,计算单元空转,导致GPU不能充分发挥性能。当计算速度由 内存访问速度 决定,而不是GPU核心的算力时,一般认为产生了内存瓶颈。]。
III.CUDA平台的代码探究与程序实现[ 以下内容为本人学习英伟达官方CUDA课程后完成的内容。]
了解完CUDA平台的相关内容之后,进行CUDA平台编译器软件和代码语言的研究。因为CUDA已经支持C++语言,且本人拥有一定的C++程序设计知识,因此以下探究均基于CUDA化的C++语言进行。
1.环境配置
硬件配置:
中央处理器:Intel Core i5-9300H
图形处理器:NVIDIA GeForce GTX 1650 Laptop[ 使用CUDA平台须至少拥有一枚NVIDIA GPU。]
其余硬件略。
系统配置:
Windows11 25H2
软件配置:
1.在微软官网搜索VisualStudio2022并完成软件安装。
2.在英伟达官网搜索CUDA Toolkit并选择当前显卡支持的最高版本(12.9.1)进行安装。
3.安装Clion并完成编译器配置。
4.新建项目时选择“CUDA加载程序”,编译器便会新建.cu文件,可以在Clion中进行代码编写。
2.代码初探[ 内容来源于英伟达官网“CUDA C++ 编程指南“]
CUDA C++ 扩展了 C++,允许程序员定义 C++ 函数(称为内核),调用时,由 N 个不同的 CUDA 线程并行执行 N 次,而不是像常规 C++ 函数那样只执行一次。
CUDA C++相比标准C++,代码风格有以下特点:
为了让C++在运行函数时使用GPU进行多线程运算,我们在声明函数时应在函数类型前加上前缀[ 变量也可以添加如下几种前缀。]:
global[ 因为__global__函数是异步函数(见后文),__global__只能用于声明void类型函数。__global__函数可调用其他__global__函数。]
对于在主函数中不调用,只在__global__函数被调用的函数,我们应在函数类型前加上前缀:
device[ 除__global__函数以外,__device__函数还可以被其他__device__函数调用。device__无类型限制,可以声明任意类型的函数。]
对于在只在main函数中调用的函数,我们应在函数类型前加上前缀:
host[ 通常该前缀可省略,函数默认定义为__host__类型。该前缀还可以与__device__前缀合用,使该定义的函数在各类函数中均可被调用。]
在主函数调用__global__函数时,我们应用如下格式:
name<<<a,b>>>();//创建大小ab的网格[ 网格由板块组成,板块由线程组成。a为板块数,b为每个板块的线程数。]
但应当注意,__global__函数是异步函数,不同于传统C++代码调用函数后直接运行,在CUDA C++中调用函数后,仅仅是将该函数加入到运行队列等待运行,而非直接运行,所以我们在主函数调用__global__函数后应加上代码:
cudaDeviceSynchronize();
以便让CPU陷入等待[ 前文提到:在CUDA环境中,CPU不参与函数运算,指负责程序调度。],等GPU完成运行队列中的所以函数任务后在进行其它操作。
在其他函数内调用__device__函数与标准C++中调用函数格式相同,无须更改格式。
对于__global__函数的调用代码,
括号内填入函数本身所需的数据,而尖括号内a为该函数运行的板块数,b为每个板块中的线程数量。代码运行时,函数将同时运行ab次[ 每个线程占用一个流处理器,所以程序同时运行的前提是调用代码中的a*b<=GPU总流处理器数。];
在调用的函数内,我们可以调用内置变量blockIdx.x,girdDim.x,threadIdx.x,blockDim.x[ 函数支持三维调用,对于上例中的一维调用,使用.x表示获取第一维数据。];分别表示当前运行板块编号、当前运行总板块数、当前板块内运行线程编号,当前板块内运行总线程数[ 板块和线程的运行顺序听从CPU调度灵活运行,并非按照编号严格递增顺序运行。]。
同时,还有以下几点需要注意[ CUDA C++另内置多个专用库函数以进行精细地图形化运算,本研究性学习不涉及。]:
1.主函数和主程序外的变量存储在CPU内存中,而各函数因为在GPU内运行,因此如果不加处理,无法以任何形式访问函数外的变量。解决这个问题,最简单的方法是在变量类型前加上前缀__managed[ 不可用于const类型定量。]。使用这种方法来统一内存占用固定的内存位置,无法灵活调整内存位置,可能造成一定的内存负担,但对于小型程序来说足矣。
2.在CUDA C++中如果频繁递归运行任意类型的函数都可能因为内存占用造成栈溢出,程序不能正常运行,因此应把递归程序等效改写。
3.使用代码name<<<a,b>>>();来调用函数不能等效视为ab次for循环,对于同一板块内的b个线程,线程间共享相同数值的场外变量,因此不能直接使用代码对变量进行ab次赋值[ 因为CUDA C++多线程并行运算性质,直接调用只会按函数要求赋值1次,将b调成1可以一定程度缓解该问题,但实际赋值次数仍远小于ab。]。对于一次name<<<a,b>>>();调用,当我们将blockDim.xblockIdx.x+threadIdx.x作为某次函数调用的线程编号时,保证ab次调用完毕后ab个线程编号的集合为0~lockDim.x*gridDim.x的整数数集[ 对于1n的整数数集,保证其是合计n个不重整数1n的排列。]。
4.介于与1.同样的原因,函数内无法使用std::cout/cin。须用printf/scanf代替。
3.优化验证
下面我们通过编写简易的代码来证明相同条件下CUDA C++使用多线程并行运算能优化代码运行速度。
我们以输出速度为例。
CUDA C++的并行运行支持输出操作,所以理论上CUDA C++可以并行输出进而优化输出时间,故设计一个程序,利用CUDA C++内置语句,测试CUDA C++输出1~2^20整数数集的耗时,并与传统C++代码进行比较。
在Clion编译器软件内编写的传统C++代码如下:
include <bits/stdc++.h>
using namespace std;
const long long mod=1024*1024;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);//关闭同步流,提升cout输出速率。
double time;
clock_t start, finish;
start = clock();
for(int i=1;i<=mod;i++)cout<<i<<endl;
finish=clock();
time=(double)(finish-start);
cout<<"time:"<<(double)time/1000.00<<"s"<<endl;
return 0;
}
运行如上10次代码后,测出平均运行时间为35.314528秒[ GPU的温度和实时频率和其他硬件因素的变化可能小幅度影响程序运算结果。但不影响最终结论。]。
接下来在同一编译器软件内编写CUDA C++程序。
介于CUDA并行运算的线程编号的随机性,我们不要求CUDA C++程序顺序输出。因为保证线程编号为0~a*b-1的整数数集,且测试所用GPU:GTX1650Laptop的流处理器恰好为210,所以利用这一特质,对于每个线程编号i,让其递增顺序输出所有i+1+k*1024(k∈Z&&k∈[1,1023])。为防止并行输出导致实际输出数量小于220,另设bool数组a用于标记,在完成测时后进行遍历,查找未被扫过的数字。实际代码如下:
include //本代码无需特殊函数语句,导入标准库即可
using namespace std;
managed bool a[int(10241024+1)];//标记数组
global void kernel() {
int i = blockIdx.x * blockDim.x + threadIdx.x + 1;
while (i < 1024 * 1024 + 1) {//输出所有i+1+k1024(k∈Z&&k∈[1,1023])
a[i]=1;
printf("%d\n", i);
i += 1024;
}
}
int main() {
cudaEvent_t start, stop;//声明起始和结束两个事件
float time;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord( start, 0 );//记录开始时间
kernel<<<32, 32>>>();
cudaEventRecord( stop, 0 );//记录结束时间
cudaDeviceSynchronize();
cudaEventSynchronize( stop );
cudaEventElapsedTime( &time, start, stop );
cudaEventDestroy( start );
cudaEventDestroy( stop );
cout<<"time:"<<time/1000.00<<"s"<<endl;//输出
for (int i=1;i<=1024*1024;i++)if (!a[i])cout<<i<<endl;//检测
return 0;
}
运行如上10次代码后,10次运行均未出现输出数量异常现象,测出平均运行时间为0.854213秒[ GPU的温度和实时频率和CPU、内存占用等其他硬件因素的变化可能小幅度影响两程序运算结果。但不影响最终结论。]。
误差分析:
1.对于传统C++程序,我们关闭同步流,其理论输出速度快于等量的printf输出速度。
2.对于CUDA C++程序,因为函数内使用统一内存读写a[i]数组,可能一定程度上减慢程序运行速度。
3.俩程序的测时程序均基于同一原理。
4.俩程序在同一编译器软件Clion内运行,CUDA C++程序使用VisualStudio编译器,传统C++程序使用WinGW编译器,二者运行性能相近。
以上四点可能存在的误差均可忽略不计,因此,俩程序运行测试结果有效。
综上,在输出相同的情况下,基于本人的计算机配置,CUDA C++并行编译速度大概是传统C++使用CPU线性编译的40倍。

浙公网安备 33010602011771号