c++高性能:多进程 cuda编程

        GPU(Device)用于异构计算,CPU(Host)控制GPU。极简入门。API(CUDA Driver API+CUDA Runtime API+CUDA Libraries)

hello world

// main.cu
#include <iostream>
#include <stdio.h>

void cpu_hello(){
    printf("cpu\n");
}
__global__ void cuda_hello(){
    printf("gpu\n");
}

int main() {
    cpu_hello();
    cuda_hello<<<1,10>>>();
    cudaDeviceSynchronize();//witout this no output

    std::cout << "Hello, World!" << std::endl;
    return 0;
}

编译

  • nvcc -arch=sm_86 -o CUDATEST main.cu -run
  • 30**系类显卡用sm_86编译,如果不通过可以减小
# https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html#gpu-compilation
nvcc x.cu
 -gencode arch=compute_50,code=sm_50
 -gencode arch=compute_60,code=sm_60
 -gencode arch=compute_70,code=\"compute_70,sm_70\"

嵌入与计算能力5.06.0兼容的二进制代码以及与计算能力7.0兼容的PTX和二进制代码。

全局函数,设备,主机函数标识 __global__,__device__,__host__

https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cuda-enabled-gpus
__global__ void fun(){}在设备上执行,可从主机(计算能力> 5.0设备 )调用必须无返回值
__device__在设备上执行,只能从设备调用
__host__在主机上执行,只能从主机调用。可省略

块和线程

cuda的<<< >>>语法糖是用于并行计算的一种语法,它是cuda中的特有语法,用于表示在GPU中启动的线程块和线程的数量。其中<<<表示启动的线程块的数量,表示每个线程块中的线程数>>>。

例如,如果我们想启动一个包含64个线程的线程块,我们可以使用以下语法:

kernel<<<1, 64>>>(args);

这表示我们只启动了一个线程块,每个线程块中有64个线程。如果我们想启动多个线程块,我们可以使用以下语法:

kernel<<<N, 64>>>(args);

其中N是启动的线程块的数量,每个线程块中有64个线程。通过这种语法糖,我们可以轻松地启动大量的线程块和线程,实现高效的并行计算。

2块,3线程(执行2*3次 global)add<<<2,3>>>(a,b,c)和mpi类似,使用块号或者线程号进行细节操作
girdDim.x块的个数
blockDim.x一个块的线程个数
blockIdx.x块的索引
threadIdx.x一个块中线程的索引
在所有线程中的indexblockIdx.x*blockDim.x+threadIdx.xblockIdx.x*3+threadIdx.x

dim3

dim3是一个CUDA中的结构体,用于表示三维中的向量。它包含三个unsigned int类型的成员变量x、y和z,分别表示向量在三个维度上的分量。

在CUDA编程中,dim3通常用于表示线程块和线程的数量。我们可以使用dim3类型来指定启动的线程块和线程的数量,例如:

dim3 block(16, 16, 1);
dim3 grid(32, 32, 1);
kernel<<<grid, block>>>(args);

这里我们创建了一个包含16 x 16个线程的线程块,以及32 x 32个线程块的网格。在启动kernel函数时,我们将grid作为第一个参数传递给<<<>>>语法糖,表示启动32 x 32个线程块,而block作为第二个参数传递给kernel函数,表示每个线程块包含16 x 16个线程。

在实际编程中,dim3还可以用于表示其他三维向量,例如图像的分辨率、数据的维度等。

dim3 dimBlock(x,y);Kernel<<<2,dimBlock>>>(argv)blockIdx.x * blockDim.x * blockDim.y + threadIdx.y * blockDim.x + threadIdx.x;
dim3 dimGrid(x,y,z);Kernel<<<dimGrid,2>>>(argv);

全为3D的索引(第几块的第几个线程):

  • int blockId = blockIdx.x+ blockIdx.y * gridDim.x+ blockIdx.z * gridDim.x * gridDim.y; (第几块(对应最初始公式中的blockIdx.x):blockIdx.*为系数, gridDim.*为次数)

  • int Idx = blockId * (blockDim.x * blockDim.y * blockDim.z) + (threadIdx.z * (blockDim.x * blockDim.y)) + (threadIdx.y * blockDim.x)+ threadIdx.x; (后边部分对应于最初始公式的threadIdx.x)

  • 矩阵加法 https://b23.tv/AWa1Pml

内存限制

硬件约束:
这是易于量化的部分。 当前 CUDA 编程指南的附录 F 列出了许多硬限制,这些限制限制了内核启动的每个块可以有多少线程。如果超过其中任何一个,您的内核将永远不会运行。它们可以大致概括为:

  1. 每个块总共不能超过 512/1024 个线程(计算能力分别为 1.x 或 2.x 及更高版本)
  2. 每个块的最大尺寸限制为 [1024,1024,64]/[2^31-1,65535,65535](计算 2.x/2.x+)
  3. 每个块的总容量不能超过 8k/16k/32k/64k/32k/64k/32k/64k/32k/64k (计算 1.0,1.1/1.2,1.3/2.x-/3.0/3.2/3.5-5.2/5.3/6-6.1/6.2/7.0)
  4. 每个块不能消耗超过 16kb/48kb/96kb 的共享内存(计算 1.x/2.x-6.2/7.0)

如果您保持在这些限制范围内,则可以成功编译的任何内核都将启动而不会出错。

Grid-Stride循环

__global__
void saxpy(int n, float a, float *x, float *y)
{
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i < n) 
        y[i] = a * x[i] + y[i];
}
__global__
void saxpy(int n, float a, float *x, float *y)
{
    for (int i = blockIdx.x * blockDim.x + threadIdx.x; 
         i < n; 
         i += blockDim.x * gridDim.x) 
      {
          y[i] = a * x[i] + y[i];
      }
}

错误处理

内存

统一内存

设备内存

ccuda c备注
malloccudaMalloc申请)显存
memcpycudaMemcpy同步执行函数,且具有方向参数
freecudaFree释放现存
cudaMallocHost

example:

// https://www.olcf.ornl.gov/tutorials/cuda-vector-addition/
#include <stdio.h>
#include <cuda_runtime.h>

#define N 10

// CUDA kernel. Each thread takes care of one element of c
__global__ void vecAdd(double *a, double *b, double *c, int n)
{
    // Get our global thread ID
    int id = blockIdx.x*blockDim.x+threadIdx.x;
 
    // Make sure we do not go out of bounds
    if (id < n)
        c[id] = a[id] + b[id];
}

int main()
{
    int h_a[N], h_b[N], h_c[N];
    int *d_a, *d_b, *d_c;

    // Allocate memory on the device
    cudaMalloc(&d_a, N * sizeof(int));
    cudaMalloc(&d_b, N * sizeof(int));
    cudaMalloc(&d_c, N * sizeof(int));

    // Initialize host arrays
    for (int i = 0; i < N; i++) {
        h_a[i] = i;
        h_b[i] = i * i;
    }

    // Copy data from host to device
    cudaMemcpy(d_a, h_a, N * sizeof(int), cudaMemcpyHostToDevice);
    cudaMemcpy(d_b, h_b, N * sizeof(int), cudaMemcpyHostToDevice);

    // Perform vector addition on the device
    int blockSize = 256;
    int numBlocks = (N + blockSize - 1) / blockSize;
    addVectors<<<numBlocks, blockSize>>>(d_a, d_b, d_c, N);

    // Copy data from device to host
    cudaMemcpy(h_c, d_c, N * sizeof(int), cudaMemcpyDeviceToHost);

    // Print results
    for (int i = 0; i < N; i++) {
        printf("%d + %d = %d\n", h_a[i], h_b[i], h_c[i]);
    }

    // Free memory on the device
    cudaFree(d_a);
    cudaFree(d_b);
    cudaFree(d_c);

    return 0;
}
  • 在本例中,我们将两个数组“h_a”和“h_b”从主机复制到设备,在设备上执行向量加法,然后将结果从设备复制回主机。

cudaMemPrefetchAsync(prefeatch预取)

  • UM 分配存在切页过程,cudaMallocManaged 一般搭配使用cudaMemPrefetchAsync

  • 后边的参数设置是放到cpu还是gpu

  • 写在传入global函数之前int deviceId; cudaGetDevice(&deviceId); cudaMemPrefetchAsync(pointerToSomeUMData, size, deviceId); // Prefetch to GPU device.

  • cudaMemPrefetchAsync(pointerToSomeUMData, size, cudaCpuDeviceId); // Prefetch to host.

shared memory

  • __shared__ float sh_arr[128]
    在这里插入图片描述
  • https://www.jianshu.com/p/8d17817a7488

constant

  • __constant__ 只读,全局

SM(stream multiprocessor): 流处理器

  • GPU:每个GPU有若干个SM,每个SM并行而独立运行

  • 定义流:cudaStream_t s1;

  • 创建流:cudaStreamCreate(&s1);

  • 使用流:func_kernel<<< blocks,threads,0,s1 >>>

  • 销毁流:cudaStreamDestory(s1);

  • CUDA随笔之Stream的使用

CUDA同步操作

原子操作函数

  • 和多线程的原子含义相同,自动加去锁保持线程的独占操作
__global__ void increment_atomic(int *g)
{
	int i = blockIdx.x * blockDim.x + threadIdx.x; 
	i = i % ARRAY_SIZE;  
	atomicAdd(& g[i], 1);//原来为g[i] = g[i] + 1;  
}

_syncthreads()

  • 线程块内线程同步,保证线程块内所有线程都执行到统一位置

_threadfence()
一个线程调用__threadfence后,该线程在该语句前对全局存储器或共享存储器的访问已经全部完成,执行结果对grid中的所有线程可见。

_threadfence_block()
一个线程调用__threadfence_block后,该线程在该语句前对全局存储器或者共享存储器的访问已经全部完成,执行结果对block中的所有线程可见。

以上两个函数的重要作用是,及时通知其他线程,全局内存或者共享内存内的结果已经读入或写入完成了。

cudaStreamSynchronize()/cudaEventSynchronize()
主机端代码中使用cudaThreadSynchronize():实现CPU和GPU线程同步
kernel启动后控制权将异步返回,利用该函数可以确定所有设备端线程均已运行结束

跟踪分析器

NVIDIA Nsight GPU 跟踪分析器
https://developer.nvidia.com/nvidia-visual-profiler

归约算法 规约∑

  • CUDA专家手册:GPU编程权威指南
    在这里插入图片描述

矩阵转置

#include <stdio.h>

#define N 16

__global__ void transpose(int *input, int *output) {
    __shared__ int tile[N][N+1];

    int x = blockIdx.x * N + threadIdx.x;
    int y = blockIdx.y * N + threadIdx.y;

    tile[threadIdx.y][threadIdx.x] = input[y * N + x];

    __syncthreads();

    x = blockIdx.y * N + threadIdx.x;
    y = blockIdx.x * N + threadIdx.y;

    output[y * N + x] = tile[threadIdx.x][threadIdx.y];
}

int main() {
    int *h_input, *h_output;
    int *d_input, *d_output;

    // 分配和初始化主机内存
    h_input = (int*)malloc(N * N * sizeof(int));
    h_output = (int*)malloc(N * N * sizeof(int));
    for (int i = 0; i < N * N; i++) {
        h_input[i] = i;
    }

    // 分配设备内存
    cudaMalloc((void**)&d_input, N * N * sizeof(int));
    cudaMalloc((void**)&d_output, N * N * sizeof(int));

    // 将输入数据从主机内存复制到设备内存
    cudaMemcpy(d_input, h_input, N * N * sizeof(int), cudaMemcpyHostToDevice);

    // 定义网格和块的大小
    dim3 grid(N/N, N/N);
    dim3 block(N, N);

    // 调用内核函数
    transpose<<<grid, block>>>(d_input, d_output);

    // 将结果从设备内存复制到主机内存
    cudaMemcpy(h_output, d_output, N * N * sizeof(int), cudaMemcpyDeviceToHost);

    // 打印结果
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            printf("%d ", h_output[i * N + j]);
        }
        printf("\n");
    }

    // 释放内存
    free(h_input);
    free(h_output);
    cudaFree(d_input);
    cudaFree(d_output);

    return 0;
}

在上述代码中,首先定义了一个transpose的CUDA内核函数。该函数使用了共享内存tile来存储输入矩阵的一个小块。然后,根据线程的索引计算输入矩阵的位置,并将数据存储到共享内存中。接下来,通过交换坐标xy,将共享内存中的数据写入到输出矩阵中。最后,使用cudaMemcpy函数将结果从设备内存复制到主机内存,并打印输出结果。

在主函数中,首先分配并初始化主机内存,然后分配设备内存,并将输入数据从主机内存复制到设备内存。接着,定义了网格和块的大小,并调用内核函数。最后,将结果从设备内存复制到主机内存,并打印输出结果。

需要注意的是,上述代码中的矩阵大小为16x16,可以根据实际需求进行修改。

CG

  • 异构计算的核心点在于“异构”二字,说白了就是用不同制程架构、不同指令集、不同功能的硬件组合起来提高算力水平。

tips

programming class

image processing

teaching class

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"C高性能编程"是一本涉及高性能编程技术的PDF电子书。这本书涵盖了各种与高性能编程相关的主题,包括算法优化、并行编程、内存管理、网络编程和系统优化等。它旨在帮助程序员提升程序的性能,使其能够更好地满足用户的需求。 这本书的第一部分介绍了高性能编程的概念和原则。它解释了高性能编程的重要性,以及如何通过合理的设计和优化来提高程序的性能。此外,它还介绍了一些常见的性能优化技术和工具,如性能测试和性能分析。 第二部分主要涵盖了算法优化和并行编程。它介绍了一些常见的算法和数据结构,以及如何通过优化算法来提高程序的执行效率。此外,它还介绍了并行编程的基本原则和技术,如多线程和分布式计算等。 第三部分讨论了内存管理和优化。它介绍了一些内存管理技术,如内存分配和回收机制,以及如何避免内存泄漏和内存碎片等问题。此外,它还介绍了一些内存优化的技巧,如缓存优化和内存对齐。 第四部分涵盖了网络编程和系统优化。它介绍了一些网络编程的基本知识和技术,如套接字编程和网络协议。此外,它还介绍了一些系统级优化技术,如优化IO操作和减少系统调用等。 总之,“C高性能编程”是一本涵盖了高性能编程相关主题的PDF电子书。通过学习这本书,我们可以了解高性能编程的基本概念和原则,掌握一些常见的性能优化技术和工具,以及学习如何优化算法、并行编程、内存管理、网络编程和系统优化等方面的知识。这将有助于我们提高程序的性能,使其能够更好地满足用户的需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值