第一章 指针篇
第二章 cuda原理篇
第三章
随着人工智能的发展与人才的内卷,很多企业已将深度学习算法的C++部署能力作为基本技能之一。面对诸多arm相关且资源有限的设备,往往想更好的提速,满足更高时效性,必将更多类似矩阵相关运算交给CUDA处理。同时,面对市场诸多教程与诸多博客岑子不起的教程或高昂教程费用,使读者(特别是小白)容易迷糊,无法快速入手CUDA编程,实现工程化。
因此,我将结合我的工程实战经验,我将在本专栏实现CUDA系列教程,帮助读者(或小白)实现CUDA工程化,掌握CUDA编程能力。学习我的教程专栏,你将绝对能实现CUDA工程化,完全从环境安装到CUDA核函数编程,从核函数到使用相关内存优化,从内存优化到深度学习算子开发(如:nms),从算子优化到模型(以yolo系列为基准)部署。最重要的是,我的教程将简单明了直切主题,CUDA理论与实战实例应用,并附相关代码,可直接上手实战。我的想法是掌握必要CUDA相关理论,去除非必须繁杂理论,实现CUDA算法应用开发,待进一步提高,将进一步理解更高深理论。
CUDA依赖指针,本篇需掌握相关指针内容,为CUDA编程提供依据。
…
cuda将使用stream方式控制计算,基本使用指针的方式进行数据拷贝或者开辟内存等,为此学习cuda编码需了解指针相关基础。
具体代码展示示例如下:
cuda个人编写示例:
CUDA_CHECK(cudaMallocHost((void**)&img_buffer_host, max_image_size * 3));
// prepare input data in device memory
CUDA_CHECK(cudaMalloc((void**)&img_buffer_device, max_image_size * 3));
cuda源码示例:
extern __host__ cudaError_t CUDARTAPI cudaMallocHost(void **ptr, size_t size);
inline __device__ cudaError_t CUDARTAPI cudaMalloc(void **p, size_t s)
{
return cudaErrorUnknown;
}
以上代码为内存分配示例,可看出使用了指针,因此我们调用cuda相关函数需要大量涉及指针相关方法。
指针定义:指针是一个值为内存地址的变量或者数据对象。指针变量的值是地址。
如:*p表示定义指针,实际p是指针,指针变量名为p,那么我们可以通过 p = &a,将a的地址赋值给p,因为指针存存的是地址,从而我们可以通过对p进行操作,对a进行修改等操作。
*运算符,叫间接运算符或叫解引用运算符,找出存储在地址的值。*运算符后面跟着一个指针名或者地址时,*给出存储在指针所指向地址的值。
如:*ptr表示指针ptr对应地址的值,与val=*ptr是等量的,而ptr=&val是等量的,&val表示变量val的地址。
&运算符,叫做地址运算符。后跟变量名时,&给出该变量的地址。
如:变量val,&val表示变量val的地址,若定义*p的指针,可将指针指向变量使用p=&val。
此部分将给出指针几个列子,我将根据这些列子说明指针实际用法与表示含义,以下列子均经过验证,可使用的方法。
在进行指针示例演示代码之前,我将在此贴出公用代码,此代码或许下面某些示例需要使用。
void Print_Pointers(int* ptr,int N) {
for (int i = 0; i < N; i++)
{
std::cout << "order:\t" << i << "\tptr_value:\t" << *ptr << "\tphysical address:" << ptr << std::endl;
ptr++;
}
}
指针赋值共探索2种方法,其一在创建指针时候赋值,其二在创建指针完成后赋值,以下代码将指针指向数组,arr数组变量名表示地址,因此可直接赋值;指针p赋值也需要指向变量a的地址,因此需使用&符号获取变量a的地址,然后将地址赋给指针p。
代码如下:
void pointer_1() {
/* 探索指针赋值方法 */
const int N = 6;
int arr[N];
for (int i = 0; i < N; i++) arr[i] = i + 1; //数组赋值
//指针第一种赋值方法
int* ptr=nullptr ;
ptr = arr;
//指针第二种赋值方法
int *ptr2 = arr;
std::cout << "output ptr1 " << std::endl;
Print_Pointers(ptr, N);
std::cout << "\n\noutput ptr2 " << std::endl;
Print_Pointers(ptr2,N);
//单独变量赋值
int a = 20;
int* p = &a;
std::cout << "\n\noutput p value: \t" <<*p<<"\tphysical address:\t"<<p<< std::endl;
}
指针的初始化、解引用操作以及自增,看一个有趣的语句“int *ptr = arr;”,该语句用数组名初始化指针。在这里数组名代表的是数组第一个元素的地址,之后在循环内程序会递增指针以指向数组的后面几个元素。
代码如下:
void pointer_1() {
const int N = 6;
int arr[N];
for (int i = 0; i < N; i++) arr[i] = i + 1; //数组赋值
int* ptr = arr; //构建指针
for (int i = 0; i < 5; i++)
{
std::cout << "ptr_value_" <<i<< ":\t" << *ptr << std::endl;;
ptr++;
}
}
结果显示:
此部分探索地址指针指向的值变化:
1.通过指针更改值,将指针赋值为num的地址,通过更改p的指针来更改指针指向num地址的值p=*p+20,该物理地址的值更改,而num存的值的物理地址为p,因此也更改了num的值;
2.通过变量更改值,将num=30,更改num的值,而指针p保存指向num的地址,地址不变情况,通过*p查看地址的值,因此为num更改的值30。
代码如下:
void pointer_3() {
int num = 4;
int* p = #
cout << "*p:\t" <<*p<<"\t p address:\t"<<p<<"\tnum value:\t"<<num<<"\tnum address:\t"<<num << endl;
*p = *p + 20; //通过指针更改地址的值
cout << "*p:\t" << *p << "\t p address:\t" << p << "\tnum value:\t" << num << "\tnum address:\t" << num << endl;
num = 30; //更改变量值
cout << "*p:\t" << *p << "\t p address:\t" << p << "\tnum value:\t" << num << "\tnum address:\t" << num << endl;
}
定义:指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。 指针的指针就是将指针的地址存放在另一个指针里面。
通俗解释:一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的物理位置。
一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。
探索指针的指针 值与地址之间的关系:
1.指针的指针赋值,结果中看指针的指针p2与p3打印值*p2/*p3与地址p2/p3输出一致 ,则赋值方法与指针赋值差不多,但需要&符号赋值。
2.指针的指针p2说明,前提p1指针指向地址000000E9AC0FF914,并保存地址,本身p1指针有自己地址,为此p2指针指向p1指针的地址,*p2表示p1的值也为num的地址。
3.指针p2的**p2与*p2说明,p2是指针p1的值,而p1的值是num的地址,在前加表示取p1值(num)的值为num值4。
实际理解与单指针*p1一样,只需记住此时p1的值为num的地址,而本身p1存在自身地址保存值(num地址)。
void pointer_4() {
int num = 4;
int* p1 = #
//指针的指针第一种赋值方法
int** p2=&p1 ;
//指针的指针第二种赋值方法
int** p3;
p3 = &p1;
cout << "num value:\t" << num << "\t num address:\t" << &num << endl;
cout << "p1 value:\t" << *p1 << "\t p1 address:\t" << p1 << endl;
cout << "p2 value:\t" << *p2 << "\t p2 address:\t" << p2 << endl;
cout << "p3 value:\t" << *p3 << "\t p3 address:\t" << p3 << endl;
cout << "p2 value:\t" << **p2 << "\t p2 address:\t" << *p2 << endl;
}
结果显示:
假设变量num的值为4,定义指针*p1与指针的指针**p2,p1指向num,p2指向p1,其示意图如下:
解释如下:
1.p2与p1本质是指针,因此需指向地址,从图中可看出p2的值value3指向address2,p1的值value2指向address1。
2.p2、p1与num本身有自己的地址,分别为address3、address2与address1。
3.定义的**p2与*p定义完后,p2与p1分别为地址address3与address2,即p2=address3,p1=address2。
4.使用*获得p2与p1指向指针的地址的值,从图可看出p1的值value指向地址address1,且地址address1的值为value1=4,则 *p1=4;
同理,p2的值value3指向地址address2,且address2的值为value2=address1,则*p2=address1。
5.如何通过p2获得num的值呢?从4可看出p2=address1,而address1的值为value1=4,则需再次使用(*p2)的方式再次获得address1的值即可,
则**p2=value1=4。