[参加CUDA线上训练营]day one

操作系统

 

操作系统的作用:

是现代计算机系统中最基本和最重要的系统软件。

是配置在计算机硬件上的第一层软件,是对硬件系统的首次扩展。

主要作用是管理好硬件设备,并为用户和应用程序提供一个简单的接口,以便于使用。

而其他的诸如编译程序、数据库管理系统,以及大量的应用软件,都直接依赖于操作系统的支持。

X86 UBUNTU与L4T UBUNTU 的区别

X86 Ubuntu:是指运行在X86架构CPU的linux ubuntu版本的操作系统。

L4T Ubuntu: L4T是linux for tegra的缩写,Tegra是集成了ARM架构的CPU和NVIDIA的GPU的处理器芯片,所以L4T Ubuntu就是为运行在基于arm架构的Tegra芯片上的linux ubuntu版本的操作系统,它是专门为Tegra定制的Ubuntu特殊版本。

应用场景:ARM处理器定位于嵌入式平台,应用在开发板、边缘设备、

智能设备上;X86定位于桌面PC和服务器。

                         ARM是为了低功耗高效率设计的,而X86是为了追求高性能。

设计架构:ARM是精简指令集(RISC)架构;x86是复杂指令集(CISC)

架构。

ARM几乎都采用Linux的操作系统;X86多为window系统也可采用linux

操作系统。

UBUNTU目录结构

 UBUNTU权限管理

exit 退出当前用户
sudo+命令 使用户可以在自己的环境下执行本需要root权限的命令

修改形式字母法:chmod(u g o a) (+ - =)(文件名)
修改形式数字法:chmod+数字组合+文件名

cd [dir] 切换到指定目录
ls [-opt][dir,file] 查看指定目录所有内容
pwd [-opt] 打印当前工作目录
cat [-opt][file] 查看文件内容/创建文件/文件合并/追加文件内容等
more [-opt] file 分页显示文件内容

touch [-opt] file 若文件不存在则创建,否则修改
mkdir [-opt] file 创建目录
mkdir -p 层级创建目录  eg.mkdir -p a/b/c
rm [-opt] file 不可恢复地删除文件或目录
rmdir [-opt] dir 删除空目录

cp [-opt] src target 复制文件或目录
mv [-opt] src/dir target/dir 移动/重命名文件或目录

find [path][-opt][exp] 在目录中搜索文件
grep [-opt][pattern][file] 在文本文件中查找内容

date 查看系统时间
cal -y 查看当年日历

df [-opt][file] 显示磁盘容量等
du [-opt][file] 显示目录下文件大小

ps [opt] 查看进程信息
top [-opt]动态显示进程有序详细信息
kill [-signal] pid 终止进程

jtop查看工具:sudo -H pip install -U jetson-stats

ifconfig [-opt]或 ip addr 查看或配置网卡信息
ping [-opt] destination 检测到目标地址链接通讯是否正常

ssh [-opt][user@hostname]

makefile

example 1:

test: test.c
	gcc test.c -o test

example 2:

CC = gcc
main: main.c tool.o bar.o
	$(CC) main.c tool.o bar.o -o main
  
tool.o: tool.c
	$(CC) -c tool.c

bar.o: bar.c
	$(CC) -c  bar.c
  
clean:
	rm *.o main

example 3:

OBJ = main.o add.o sub.o mul.o div.o
CC = gcc

app: $(OBJ)
	$(CC) -o app $(OBJ)

main.o: main.c
	$(CC) -c main.c

add.o: add.c
	$(CC) -c add.c

sub.o:sub.c
	$(CC) -c sub.c

mul.o: mul.c
	$(CC) -c mul.c

div.o: div.c
	$(CC) -c div.c

.PHONY : clean
clean :
	-rm $(OBJ) app    

什么是Makefile
1. 一个企业级项目,通常会有很多源文件,有时也会按功能、类型、模块分门别类的放在不同的目录中,有时候也会在一个目录里存放了多个程序的源代码。这时,如何对这些代码的编译就成了个问题。Makefle就是为这个问题而生的,它定义了一套规则,决定了哪些文件要先编译,哪些文件后编译,哪些文件要重新编译。

2. 整个工程通常只要一个make命令就可以完成编译、链接,甚至更复杂的功能。可以说,任何一个Linux源程序都带有一个Makefile文件。如果能掌握Makefile文件的编写方法,就能脱离可视化编译器,使用编译链工具编译出所需的目标文件。

Makefile的优点

1. 管理代码的编译,决定该编译什么文件,编译顺序,以及是否需要重新编译;

2. 节省编译时间。如果文件有更改,只需重新编译此文件即可,无需重新编译整个工程;

3. 一劳永逸。Makefile通常只需编写一次,后期就不用过多更改。

 

 

 

什么是cuda?

 

 

1.编写第一个Cuda程序

  • 关键词:"__global__" , <<<...>>> , .cu

在当前的目录下创建一个名为hello_cuda.cu的文件,编写第一个Cuda程序:

  • 当我们编写一个hello_word程序的时候,我们通常会这样写:
#include <stdio.h>

void hello_from_cpu()
{
    printf("Hello World from the CPU!\n");
}

int main(void)
{
    hello_from_cpu();
    return 0;
}

1.当我们在讨论GPU和CUDA时,我们一定会考虑如何调用每一个线程,如何定为每一个线程。其实,在CUDA编程模型中,每一个线程都有一个唯一的标识符或者序号,而我们可以通过threadIdx来得到当前的线程在线程块中的序号,通过blockIdx来得到该线程所在的线程块在grid当中的序号,即:

threadIdx.x 是执行当前kernel函数的线程在block中的x方向的序号  

blockIdx.x 是执行当前kernel函数的线程所在block,在grid中的x方向的序号
  • 如果我们要把它改成调用GPU的时候,我们需要在void hello_from_cpu()之前加入 __global__标识符,并且在调用这个函数的时候添加<<<...>>>来设定执行设置
  • 示例:
  • #include <stdio.h>
    
    __global__ void hello_from_gpu()
    {
        printf("Hello World from the GPU!\n");
    }
    
    int main(void)
    {
        hello_from_gpu<<<1, 1>>>();
        cudaDeviceSynchronize();
        return 0;
    }

    2.编写完成之后,我们要开始编译并执行程序,在这里我们可以利用nvcc进行编译,指令如下:

  • !/usr/local/cuda/bin/nvcc -arch=compute_72 -code=sm_72 hello_cuda.cu -o hello_cuda -run

    3.这里我们也可以利用编写Makefile的方式来进行编译

  • TEST_SOURCE = hello_cuda.cu
    
    TARGETBIN := ./hello_cuda
    
    
    CC = /usr/local/cuda/bin/nvcc
    
    $(TARGETBIN):$(TEST_SOURCE)
    	$(CC)  $(TEST_SOURCE) -o $(TARGETBIN)
    
    .PHONY:clean
    clean:
    	-rm -rf $(TARGETBIN)
    	-rm -rf *.o
     
    !make

    然后我们就可以得到一个名为hello_cuda.exe的程序,我们开始执行一下

    !./hello_cuda

    接下来我们尝试多个文件协同编译, 修改Makefile文件:

  • 编译hello_from_gpu.cu文件生成hello_from_gpu.o
  • 编译hello_cuda02-test.cu和上一步生成的hello_from_gpu.o, 生成./hello_cuda_multi_file
  • TEST_SOURCE = hello_cuda02-test.cu
    
    TARGETBIN := ./hello_cuda_multi_file
    
    CC = /usr/local/cuda/bin/nvcc
    
    
    $(TARGETBIN):hello_cuda02-test.cu hello_from_gpu.o
    	$(CC)  $(TEST_SOURCE) hello_from_gpu.o -o $(TARGETBIN)
    
    hello_from_gpu.o:hello_from_gpu.cu
    	$(CC) --device-c hello_from_gpu.cu -o hello_from_gpu.o
    
    
    .PHONY:clean
    clean:
    	-rm -rf $(TARGETBIN)
    	-rm -rf *.o
    !make -f Makefile_Multi_file
    !./hello_cuda_multi_file
    !make -f Makefile_Multi_file clean

    利用nvprof进行查看程序性能

  • !sudo /usr/local/cuda/bin/nvprof  ./hello_cuda

    CUDA编程模型---线程组织

  • 本次课程将介绍以下内容:

  • 使用多个线程的核函数
  • 使用线程索引
  • 多维网络
  • 网格与线程块
#include <stdio.h>

__global__ void hello_from_gpu()
{
    const int bid = blockIdx.x;
    const int tid = threadIdx.x;
    printf("Hello World from block %d and thread %d!\n", bid, tid);
}

int main(void)
{
    hello_from_gpu<<<5,3>>>();
    cudaDeviceSynchronize();
    return 0;
}

 

2.那我们如何能够得到一个线程在所有的线程中的索引值?比如:我们申请了4个线程块,每个线程块有8个线程,那么我们就申请了32个线程,那么我需要找到第3个线程块(编号为2的block)里面的第6个线程(编号为5的thread)在所有线程中的索引值怎么办?
这时,我们就需要blockDim 和 gridDim这两个变量:

  • gridDim表示一个grid中包含多少个block
  • blockDim表示一个block中包含多少个线程

也就是说,在上面的那个例子中,gridDim.x=4, blockDim.x=8
那么,我们要找的第22个线程(编号为21)的唯一索引就应该是,index = blockIdx.x * blockDim.x + threadIdx.x 

 

接下来,我们通过完成一个向量加法的实例来实践一下,我们来实现的cpu代码如下:

#include <math.h>
#include <stdlib.h>
#include <stdio.h>

void add(const double *x, const double *y, double *z, const int N)
{
    for (int n = 0; n < N; ++n)
    {
        z[n] = x[n] + y[n];
    }
}

void check(const double *z, const int N)
{
    bool has_error = false;
    for (int n = 0; n < N; ++n)
    {
        if (fabs(z[n] - 3) > (1.0e-10))
        {
            has_error = true;
        }
    }
    printf("%s\n", has_error ? "Errors" : "Pass");
}


int main(void)
{
    const int N = 100000000;
    const int M = sizeof(double) * N;
    double *x = (double*) malloc(M);
    double *y = (double*) malloc(M);
    double *z = (double*) malloc(M);

    for (int n = 0; n < N; ++n)
    {
        x[n] = 1;
        y[n] = 2;
    }

    add(x, y, z, N);
    check(z, N);

    free(x);
    free(y);
    free(z);
    return 0;
}

为了完成这个程序,我们先要将数据传输给GPU,并在GPU完成计算的时候,将数据从GPU中传输给CPU内存。这时我们就需要考虑如何申请GPU存储单元,以及内存和显存之前的数据传输。在result2中我们展示了如何完成这一过程的方法:

我们利用cudaMalloc()来进行GPU存储单元的申请,利用cudaMemcpy()来完成数据的传输

#include <math.h>
#include <stdlib.h>
#include <stdio.h>

__global__ void add(const double *x, const double *y, double *z, int cnt)
{

    int idx=threadIdx.x+blockIdx.x*blockDim.x;
    if(idx<cnt)
    {
        z[idx]=x[idx]+y[idx];
    }
}

void check(const double *z, int cnt)
{
    bool flag = false;
    for (int i = 0; i < cnt; i++)
    {
        if (fabs(z[i] - 3) > (1.0e-10))
        {
            flag = true;
        }
    }
    if(flag>0) printf("Wrong!\n");
    else printf("Accept!\n");
}


int main(void)
{   
    const int N=10000;
    double* h_x=(double*) malloc(sizeof(double)*N);
    double* h_y=(double*) malloc(sizeof(double)*N);
    double* h_z=(double*) malloc(sizeof(double)*N);
    
    
    for (int i = 0; i < N; i++)
    {
        h_x[i] = 1;
        h_y[i] = 2;
    }
    
    double *d_x,*d_y,*d_z;
    cudaMalloc((void**)&d_x,sizeof(double)*N);
    cudaMalloc((void**)&d_y,sizeof(double)*N);
    cudaMalloc((void**)&d_z,sizeof(double)*N);
    cudaMemcpy(d_x,h_x,sizeof(double)*N,cudaMemcpyHostToDevice);
    cudaMemcpy(d_y,h_y,sizeof(double)*N,cudaMemcpyHostToDevice);
    
    const int block_size=128;
    const int grid_size=(N+block_size-1)/block_size;
    add<<<grid_size,block_size>>>(d_x, d_y, d_z, N);
    cudaMemcpy(h_z,d_z,sizeof(double)*N,cudaMemcpyDeviceToHost);
    check(h_z, N);

    free(h_x);
    free(h_y);
    free(h_z);
    cudaFree(d_x);
    cudaFree(d_y);
    cudaFree(d_z);
    return 0;
}

Sobel边缘检测kernel优化

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

//GPU实现Sobel边缘检测
//             x0 x1 x2 
//             x3 x4 x5 
//             x6 x7 x8 
__global__ void sobel_gpu(unsigned char* in, unsigned char* out, int imgHeight, int imgWidth)
{
    int x = threadIdx.x + blockDim.x * blockIdx.x;
    int y = threadIdx.y + blockDim.y * blockIdx.y;
    int index = y * imgWidth + x;
    int Gx = 0;
    int Gy = 0;
    unsigned char x0, x1, x2, x3, x4, x5, x6, x7, x8;
    if (x > 0 && x < imgWidth && y>0 && y < imgHeight)
    {
        x0 = in[(y - 1) * imgWidth + x - 1];
        x1 = in[(y - 1) * imgWidth + x ];
        x2 = in[(y - 1) * imgWidth + x + 1];
        x3 = in[(y) * imgWidth + x - 1];
        x4 = in[(y ) * imgWidth + x ];
        x5 = in[(y ) * imgWidth + x + 1];
        x6 = in[(y + 1) * imgWidth + x - 1];
        x7 = in[(y + 1) * imgWidth + x ];
        x8 = in[(y + 1) * imgWidth + x + 1];
        Gx = (x0 + x3 * 2 + x6) - (x2 + x5 * 2 + x8);
        Gy = (x0 + x1 * 2 + x2) - (x6 + x7 * 2 + x8);
        out[index] = (abs(Gx) + abs(Gy)) / 2;
    }
}

//CPU实现Sobel边缘检测
void sobel_cpu(Mat srcImg, Mat dstImg, int imgHeight, int imgWidth)
{
    int Gx = 0;
    int Gy = 0;
    for (int i = 1; i < imgHeight - 1; i++)
    {
        uchar* dataUp = srcImg.ptr<uchar>(i - 1);
        uchar* data = srcImg.ptr<uchar>(i);
        uchar* dataDown = srcImg.ptr<uchar>(i + 1);
        uchar* out = dstImg.ptr<uchar>(i);
        for (int j = 1; j < imgWidth - 1; j++)
        {
            Gx = (dataUp[j - 1] + 2 * data[j - 1] + dataDown[j - 1])-(dataUp[j + 1] + 2 * data[j + 1] + dataDown[j + 1]);
            Gy = (dataUp[j - 1] + 2 * dataUp[j] + dataUp[j + 1]) - (dataDown[j - 1] + 2 * dataDown[j] + dataDown[j + 1]);
            out[j] = (abs(Gx) + abs(Gy)) / 2;
        }
    }
}

int main()
{
    //利用opencv的接口读取图片
    Mat img = imread("WechatIMG19437.jpeg", 0);
    int imgWidth = img.cols;
    int imgHeight = img.rows;

    //利用opencv的接口对读入的grayImg进行去噪
    Mat gaussImg;
    GaussianBlur(img, gaussImg, Size(3, 3), 0, 0, BORDER_DEFAULT);
    //CPU结果为dst_cpu, GPU结果为dst_gpu
    Mat dst_cpu(imgHeight, imgWidth, CV_8UC1, Scalar(0));
    Mat dst_gpu(imgHeight, imgWidth, CV_8UC1, Scalar(0));


    //调用sobel_cpu处理图像
    sobel_cpu(gaussImg, dst_cpu, imgHeight, imgWidth);

    //申请指针并将它指向GPU空间
    size_t num = imgHeight * imgWidth * sizeof(unsigned char);
    unsigned char* in_gpu;
    unsigned char* out_gpu;
    cudaMalloc((void**)&in_gpu, num);
    cudaMalloc((void**)&out_gpu, num);
    //定义grid和block的维度(形状)
    dim3 threadsPerBlock(32, 32);
    dim3 blocksPerGrid((imgWidth + threadsPerBlock.x - 1) / threadsPerBlock.x,
        (imgHeight + threadsPerBlock.y - 1) / threadsPerBlock.y);

    //将数据从CPU传输到GPU
    cudaMemcpy(in_gpu, img.data, num, cudaMemcpyHostToDevice);
    //调用在GPU上运行的核函数
    sobel_gpu<<<blocksPerGrid,threadsPerBlock>>>(in_gpu, out_gpu, imgHeight, imgWidth);

    //将计算结果传回CPU内存
    cudaMemcpy(dst_gpu.data, out_gpu, num, cudaMemcpyDeviceToHost);
    imwrite("save3.png", dst_gpu);
    //显示处理结果, 由于这里的Jupyter模式不支持显示图像, 所以我们就不显示了
    //imshow("gpu", dst_gpu);
    //imshow("cpu", dst_cpu);
    //waitKey(0);
    //释放GPU内存空间
    cudaFree(in_gpu);
    cudaFree(out_gpu);
    return 0;
}

生成结果如下:

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值