CUDA02_05GPU计时与性能评估

2020-01-20  本文已影响0人  杨强AT南京

  该是分析GPU性能的时候了,GPU性能与如下因素 有关:
    1. GPU硬件架构;
    2. PCI吞吐量;
    3. 流处理器;
    4. GPU内存;
  本主题说明GPU的事件计时方式,并解释了Grid,Block,Thread对性能的影响。


时间与事件记录

函数说明

  1. 创建/释放一个事件cudaEventCreate/cudaEventDestroy

    • __host__ cudaError_t cudaEventCreate ( cudaEvent_t* event )
    • __host__ __device__ cudaError_t cudaEventDestroy ( cudaEvent_t event )
  2. 开始一个事件cudaEventRecord

    • __host__ __device__ cudaError_t cudaEventRecord ( cudaEvent_t event, cudaStream_t stream = 0 )
      • 其中 cudaStream_t stream 默认使用缺省stream = 0。
      • 可以多次调用,但会覆盖上次的调用记录的状态。
  3. 等待事件结束cudaEventSynchronize

    • __host__ cudaError_t cudaEventSynchronize ( cudaEvent_t event )
  4. 计算事件消耗的事件cudaEventElapsedTime

    • __host__ cudaError_t cudaEventElapsedTime ( float* ms, cudaEvent_t start, cudaEvent_t end )
  5. 事件的其他使用

    • cudaEventCreateWithFlags()与cudaEventQuery() + cudaStreamWaitEvent().
      • 使用cudaEventDisableTiming标记,实现最好的事件性能处理。
    • cudaEventCreateWithFlags()与cudaIpcGetEventHandle()
      • 使用cudaEventInterprocess + cudaEventDisableTiming结合,实现进程间事件

使用模式

使用例子

  1. 核心代码
   // 1. 定义事件对象
    cudaEvent_t t1,t2,t3,t4,t5;

    // 2. 定义事件
    cudaEventCreate (&t1);
    cudaEventCreate (&t2);
    cudaEventCreate (&t3);
    cudaEventCreate (&t4);
    cudaEventCreate (&t5);

    // 3. 开始事件记录
    cudaEventRecord (t1, 0);   // C++才有默认参数,C需要设置为0。 
    read_bmp();
    cudaEventRecord (t2, 0); 
    move_to_device();
    cudaEventRecord (t3, 0); 
    ////////////////////////////////GPU处理调用
    //为了简单,按照图像的行列分配线程,行用来定义块,列用来定义线程, 每个线程处理4个char,并交换像素通道
    dim3 grid(header.height * 2);    // C/C++的除法对整数是整除。
    dim3 block(header.width / 2);    // 注意:每个block做多1024个线程。因为块的所有线程都应该位于同一个处理器核心上
    shift_color_channels<<<grid, block>>>(img_gpu);
    cudaEventRecord (t4, 0); 
    ////////////////////////////////
    move_to_host();
    cudaEventRecord (t5, 0); 
    // 4. 等待事件结束
    cudaEventSynchronize(t1);
    cudaEventSynchronize(t2);
    cudaEventSynchronize(t3);
    cudaEventSynchronize(t4);
    cudaEventSynchronize(t5);

    // 5. 计算消耗的时间
    float t_total, t_readbmp, t_move2gpu, t_kernel, t_move2host;
    cudaEventElapsedTime(&t_total, t1, t5);

    cudaEventElapsedTime(&t_readbmp, t1, t2);
    cudaEventElapsedTime(&t_move2gpu, t2, t3);
    cudaEventElapsedTime(&t_kernel, t3, t4);
    cudaEventElapsedTime(&t_move2host, t4, t5);
    printf("总耗时:%f\n", t_total);
    printf("\t|-读取:%f\n", t_readbmp);
    printf("\t|-拷贝到Device:%f\n", t_move2gpu);
    printf("\t|-计算:%f\n", t_kernel);
    printf("\t|-拷贝到Host:%f\n", t_move2host);

    // 保存图像,可以考虑等待所有设备计算完成。
    cudaDeviceSynchronize();
  1. 完成代码

#include <cuda.h>
#include <stdio.h>
#include <stdlib.h>
// 结构体定义
#pragma pack(1)
struct img_header{
    // 文件头
    char                  magic[2];                  // 魔法字
    unsigned int          file_size;                 // 文件大小
    unsigned char         reserve1[4];               // 跳4字节
    unsigned int          data_off;                  // 数据区开始位置
    // 信息头
    unsigned char         reserve2[4];               // 跳4字节
    int                   width;                     // 图像宽度
    int                   height;                    // 图像高度
    unsigned char         reserve3[2];               // 跳2字节
    unsigned short int    bit_count;                 // 图像位数1,4,8,16,24,32
    unsigned char         reserve4[24];              // 跳24字节
};


// 偷懒写一个匿名全局类

// 全局数据
struct img_header  header;
uchar4             *img;                             // 使用gpu的扩展类型
uchar4             *img_gpu; 

// 输入/输出文件名
const char *in_filename  = "gpu.bmp";
const char *out_filename = "gpu_out.bmp";

// 打开图像
void read_bmp();                                     // 无参数,采用全局成员
// 保存图像
void save_bmp();
// GPU运算的数据
void move_to_device();  
// 取得数据
void move_to_host();                            
// GPU处理
__global__ void shift_color_channels(uchar4 *data);
// 内存释放
void free_mem();

int main(int argc, const char **argv){
    // 1. 定义事件对象
    cudaEvent_t t1,t2,t3,t4,t5;

    // 2. 定义事件
    cudaEventCreate (&t1);
    cudaEventCreate (&t2);
    cudaEventCreate (&t3);
    cudaEventCreate (&t4);
    cudaEventCreate (&t5);

    // 3. 开始事件记录
    cudaEventRecord (t1, 0);   // C++才有默认参数,C需要设置为0。 
    read_bmp();
    cudaEventRecord (t2, 0); 
    move_to_device();
    cudaEventRecord (t3, 0); 
    ////////////////////////////////GPU处理调用
    //为了简单,按照图像的行列分配线程,行用来定义块,列用来定义线程, 每个线程处理4个char,并交换像素通道
    dim3 grid(header.height * 2);    // C/C++的除法对整数是整除。
    dim3 block(header.width / 2);    // 注意:每个block做多1024个线程。因为块的所有线程都应该位于同一个处理器核心上
    shift_color_channels<<<grid, block>>>(img_gpu);
    cudaEventRecord (t4, 0); 
    ////////////////////////////////
    move_to_host();
    cudaEventRecord (t5, 0); 
    // 4. 等待事件结束
    cudaEventSynchronize(t1);
    cudaEventSynchronize(t2);
    cudaEventSynchronize(t3);
    cudaEventSynchronize(t4);
    cudaEventSynchronize(t5);

    // 5. 计算消耗的时间
    float t_total, t_readbmp, t_move2gpu, t_kernel, t_move2host;
    cudaEventElapsedTime(&t_total, t1, t5);

    cudaEventElapsedTime(&t_readbmp, t1, t2);
    cudaEventElapsedTime(&t_move2gpu, t2, t3);
    cudaEventElapsedTime(&t_kernel, t3, t4);
    cudaEventElapsedTime(&t_move2host, t4, t5);
    printf("总耗时:%f\n", t_total);
    printf("\t|-读取:%f\n", t_readbmp);
    printf("\t|-拷贝到Device:%f\n", t_move2gpu);
    printf("\t|-计算:%f\n", t_kernel);
    printf("\t|-拷贝到Host:%f\n", t_move2host);

    // 保存图像,可以考虑等待所有设备计算完成。
    cudaDeviceSynchronize();
    save_bmp();
    free_mem();
    cudaEventDestroy(t1);
    cudaEventDestroy(t2);
    cudaEventDestroy(t3);
    cudaEventDestroy(t4);
    cudaEventDestroy(t5);

    return 0;
}

__global__ void shift_color_channels(uchar4 *data){
    // 计算索引
    int  idx = blockIdx.x * blockDim.x + threadIdx.x;   // 因为是1维,获取x就是索引。
    // 处理像素
    unsigned char red   = data[idx].x;
    unsigned char green = data[idx].y;
    unsigned char blue  = data[idx].z;
    // unsigned char alpha = pixel.w;

    data[idx].x = blue;
    data[idx].y = red;
    data[idx].z = green;
    // alpha不动
}
void move_to_host(){
    cudaMemcpy((void*)img, (void*)img_gpu, header.height * header.width * sizeof(uchar4), cudaMemcpyDeviceToHost);
}
void move_to_device(){
    // 分配GPU内存
    cudaMalloc((void**)&img_gpu, header.height * header.width * sizeof(uchar4));   // 返回指针,则参数就需要二重指针。
    // 拷贝数据
    cudaMemcpy((void*)img_gpu, (void*)img,  header.height * header.width * sizeof(uchar4), cudaMemcpyHostToDevice);

}
void read_bmp(){ 
    /* 读取头,分配内存,读取数据,这里数据采用了一维数组,使用的时候,需要转换处理下。*/
    FILE *file = fopen(in_filename, "rb");
    // 读取头
    size_t n_bytes = fread(&header, 1, 54, file); 
    
    // 计算读取的大大小,并分配空间,并读取。
    header.height = header.height >= 0? header.height : -header.height;
    img = (uchar4 *)malloc(header.height * header.width * sizeof(uchar4));
    n_bytes = fread(img, sizeof(uchar4), header.height * header.width, file);  // 因为是4倍数对齐的,所以可以直接读取

    fclose(file); // 关闭文件
    
}
void save_bmp(){
    /* 使用与读取一样的头信息保存图像 */
    FILE *file = fopen(out_filename, "wb");
    // 写头
    header.height = -header.height;
    size_t n_bytes = fwrite(&header, 1, 54, file);
    header.height = -header.height;
    // 写图像数据
    n_bytes = fwrite(img, sizeof(uchar4), header.height * header.width, file);
    // 关闭文件
    fclose(file);
}
void free_mem(){
    /* 释放Host与Device内存 */
    free(img); // 直接释放(不需要指定大小,malloc系列函数有内部变量管理分配的内存)
    cudaFree(img_gpu);
}

// nvcc -o main.exe  -Xcompiler /source-charset:utf-8 c04_gpu_app.cu


  1. 运行结果
    • GPU不参与图像的读取,耗时基本上忽略不计。
    • 如果还记得我们前面CPU并发的计算结果,这个结果是非常令人欣喜的。
    • 其中耗时的就在于GPU与CPU之间的内存捣腾耗时。
C:\01works\02cuda\c05_event_timer>main
总耗时:4.196352
        |-读取:0.002048
        |-拷贝到Device:1.976320
        |-计算:0.116032
        |-拷贝到Host:2.101952

线程分配方案的性能比较

GPU上的数学运算

用代码比较性能

  1. 核心代码的实现
    • 下面代码是减少重复计算的代码来优化。
__global__ void rotate(uchar4 *src,  uchar4 *dst, double degree){
    // 计算线程的所索引- 因为我们的设计是一个线程处理一个像素的旋转。
    int  idx = blockIdx.x * blockDim.x + threadIdx.x;
    // 根据idx,计算出像素在图像的物理位置- 这里偷懒,不传递图像的大小了,因为根据图像大小,可以折行。
    // 图像高度与宽度
    int h = 1080; 
    int w = 1920;

    // 像素位置
    int y = idx / w;           // 这是整除
    int x = idx - y * w;       // 余数

    // 计算旋转导致的缩放因子
    double d = sqrt((double)(w * w + h * h));
    double scale = w < h ? w / d : h / d;   // 其中有个浮点运算不会发生

    // 计算相关的三角计算
    double arc = 2 * 3.141592 / 360.0 * degree;
    double f_sin = sin(arc);
    double f_cos = cos(arc);

    // 计算中心位置
    int ox = w / 2; 
    int oy = h / 2; 
    // 计算新坐标系下的坐标
    double fo_x = x - ox;
    double fo_y = y - oy;
    // 计算按照图像中心旋转的浮点坐标
    double fr_x = ( fo_x * f_cos + fo_y * f_sin) * scale;
    double fr_y = (-fo_x * f_sin + fo_y * f_cos) * scale;

    // 坐标取整,并恢复到原来的图像坐标(不在以中心为原点的坐标系)
    int r_x = (int)fr_x + ox;
    int r_y = (int)fr_y + oy;
    // 迁移像素到旋转后的位置(需要转换为一维索引)
    dst[r_y * w + r_x] = src[idx];
}
  1. 调用代码
dim3 grid(header.height * 2); 
dim3 block(header.width / 2);    

rotate<<<grid, block>>>(img_gpu, rotate_gpu, 45.0);   // 旋转还是要考虑图像大小,这里不从host拷贝了,再GPU直接使用
  1. 45度的旋转效果


    GPU旋转处理效果
  1. 不同的Grid与Block分配对性能的影响
    • 计时代码这里不介绍了,参考附录。
计时项 128 64 32
总耗时: 6.309856 6.560128 6.444032
拷贝到Device: 2.715040 2.953216 2.711584
计算: 1.555168 1.557856 1.556448
拷贝到Host: 2.039648 2.049056 2.176000

性能的分析

  1. 上面性能的结果差别非常微小,但是对运行几次,会发现一个规律就是:

    • 128的效果总是最小的。
    • Host2Device的时间总是占用最多的(这个当运算更多的时候,就不是问题了,旋转一幅8M大小的图像的时间不到2毫秒,这是很激动的事情)。
  2. 原因:

    • GPU结构设计中线程基本组织单位是warps(线程组/也称线程束)=32,这是代码执行动用线程的最小单位
    • GPU实际组织线程是使用的块,块的线程数是warps的倍数。一般程序员是不用关注warps的,一般关注的是block。
      • warps线程执行的最小单位;
      • block是线程启动的单位;
      • block的线程数一般是32,64,128,256,512,1024。
  3. 分析:

    • 可以根据块中线程数,计算块启动时核函数拷贝的数据字节,然后根据GPU带宽,观察带宽的合理利用。一般128据说是最合理的利用。大于128,带宽也会得到比较好的利用。这是内存块连续、合适的大小都会导致较好的性能,这一点前面CPU计算中是有常识的。
    • 还有一个问题是,当设计的块包含的线程多于需要调用和函数的次数,我们需要判定不必要的计算。
      • 这样也会浪费多余的线程,所以block的设计是GPU程序员的很重要的能力。
  4. Grid与Block的维数因为是硬件产生,所以对程序的性能不产生任何影响。

    • 程序员可以巧妙的把某些参数设计为gridDim与blockDim,而不是通过核函数参数传递。

附录

  1. 使用GPU旋转图像的完整代码
#include <stdio.h>
#include <stdlib.h>
#include <cuda.h>

// 结构体定义
#pragma pack(1)
struct img_header{
    // 文件头
    char                  magic[2];                  // 魔法字
    unsigned int          file_size;                 // 文件大小
    unsigned char         reserve1[4];               // 跳4字节
    unsigned int          data_off;                  // 数据区开始位置
    // 信息头
    unsigned char         reserve2[4];               // 跳4字节
    int                   width;                     // 图像宽度
    int                   height;                    // 图像高度
    unsigned char         reserve3[2];               // 跳2字节
    unsigned short int    bit_count;                 // 图像位数1,4,8,16,24,32
    unsigned char         reserve4[24];              // 跳24字节
};


// 偷懒写一个匿名全局类

// 全局数据
struct img_header  header;
uchar4             *img;                             // 使用gpu的扩展类型
uchar4             *img_gpu; 
uchar4             *rotate_gpu;                      // 旋转后的图像

// 输入/输出文件名
const char *in_filename  = "gpu.bmp";
const char *out_filename = "gpu_out.bmp";

// 打开图像
void read_bmp();                                     // 无参数,采用全局成员
// 保存图像
void save_bmp();
// GPU运算的数据
void move_to_device();  
// 取得数据
void move_to_host();                            
// GPU处理
__global__ void rotate(uchar4 *src,  uchar4 *dst, double degree);
// 内存释放
void free_mem();

int main(int argc, const char **argv){
    read_bmp();
    move_to_device();
    ////////////////////////////////GPU处理调用
    dim3 grid(header.height * 2); 
    dim3 block(header.width / 2);    

    rotate<<<grid, block>>>(img_gpu, rotate_gpu, 45.0);   // 旋转还是要考虑图像大小,这里不从host拷贝了,再GPU直接使用

    ////////////////////////////////
    move_to_host();
    save_bmp();
    free_mem();
    return 0;
}

__global__ void rotate(uchar4 *src,  uchar4 *dst, double degree){
    // 计算线程的所索引- 因为我们的设计是一个线程处理一个像素的旋转。
    int  idx = blockIdx.x * blockDim.x + threadIdx.x;
    // 根据idx,计算出像素在图像的物理位置- 这里偷懒,不传递图像的大小了,因为根据图像大小,可以折行。
    // 图像高度与宽度
    int h = 1080; 
    int w = 1920;

    // 像素位置
    int y = idx / w;           // 这是整除
    int x = idx - y * w;       // 余数

    // 计算旋转导致的缩放因子
    double d = sqrt((double)(w * w + h * h));
    double scale = w < h ? w / d : h / d;   // 其中有个浮点运算不会发生

    // 计算相关的三角计算
    double arc = 2 * 3.141592 / 360.0 * degree;
    double f_sin = sin(arc);
    double f_cos = cos(arc);

    // 计算中心位置
    int ox = w / 2; 
    int oy = h / 2; 
    // 计算新坐标系下的坐标
    double fo_x = x - ox;
    double fo_y = y - oy;
    // 计算按照图像中心旋转的浮点坐标
    double fr_x = ( fo_x * f_cos + fo_y * f_sin) * scale;
    double fr_y = (-fo_x * f_sin + fo_y * f_cos) * scale;

    // 坐标取整,并恢复到原来的图像坐标(不在以中心为原点的坐标系)
    int r_x = (int)fr_x + ox;
    int r_y = (int)fr_y + oy;
    // 迁移像素到旋转后的位置(需要转换为一维索引)
    dst[r_y * w + r_x] = src[idx];
}

void move_to_host(){
    // 把选装后的图像拷贝到Host内存,用来保存到磁盘
    cudaMemcpy((void*)img, (void*)rotate_gpu, header.height * header.width * sizeof(uchar4), cudaMemcpyDeviceToHost);
}
void move_to_device(){
    // 分配GPU内存
    cudaMalloc((void**)&img_gpu, header.height * header.width * sizeof(uchar4));   // 返回指针,则参数就需要二重指针。
    // 拷贝数据
    cudaMemcpy((void*)img_gpu, (void*)img,  header.height * header.width * sizeof(uchar4), cudaMemcpyHostToDevice);

    // 旋转后的图像
    cudaMalloc((void**)&rotate_gpu, header.height * header.width * sizeof(uchar4));
    cudaMemset((void*)rotate_gpu, 0/*初始值,可以考虑255等其他值*/, header.height * header.width * sizeof(uchar4));

}
void read_bmp(){ 
    /* 读取头,分配内存,读取数据,这里数据采用了一维数组,使用的时候,需要转换处理下。*/
    FILE *file = fopen(in_filename, "rb");
    // 读取头
    size_t n_bytes = fread(&header, 1, 54, file); 
    
    // 计算读取的大大小,并分配空间,并读取。
    header.height = header.height >= 0? header.height : -header.height;
    img = (uchar4 *)malloc(header.height * header.width * sizeof(uchar4));
    n_bytes = fread(img, sizeof(uchar4), header.height * header.width, file);  // 因为是4倍数对齐的,所以可以直接读取

    fclose(file); // 关闭文件
    
}
void save_bmp(){
    /* 使用与读取一样的头信息保存图像 */
    FILE *file = fopen(out_filename, "wb");
    // 写头
    header.height = -header.height;
    size_t n_bytes = fwrite(&header, 1, 54, file);
    header.height = -header.height;
    // 写图像数据
    n_bytes = fwrite(img, sizeof(uchar4), header.height * header.width, file);
    // 关闭文件
    fclose(file);
}
void free_mem(){
    /* 释放Host与Device内存 */
    free(img); // 直接释放(不需要指定大小,malloc系列函数有内部变量管理分配的内存)
    cudaFree(img_gpu);
    cudaFree(rotate_gpu);
}

// @nvcc -o main.exe  -Xcompiler /source-charset:utf-8 c02_rotate_gpu.cu

  1. 性能比较的完整代码
#include <stdio.h>
#include <stdlib.h>
#include <cuda.h>

// 结构体定义
#pragma pack(1)
struct img_header{
    // 文件头
    char                  magic[2];                  // 魔法字
    unsigned int          file_size;                 // 文件大小
    unsigned char         reserve1[4];               // 跳4字节
    unsigned int          data_off;                  // 数据区开始位置
    // 信息头
    unsigned char         reserve2[4];               // 跳4字节
    int                   width;                     // 图像宽度
    int                   height;                    // 图像高度
    unsigned char         reserve3[2];               // 跳2字节
    unsigned short int    bit_count;                 // 图像位数1,4,8,16,24,32
    unsigned char         reserve4[24];              // 跳24字节
};


// 偷懒写一个匿名全局类

// 全局数据
struct img_header  header;
uchar4             *img;                             // 使用gpu的扩展类型
uchar4             *img_gpu; 
uchar4             *rotate_gpu;                      // 旋转后的图像

// 输入/输出文件名
const char *in_filename  = "gpu.bmp";
const char *out_filename = "gpu_out.bmp";

// 打开图像
void read_bmp();                                     // 无参数,采用全局成员
// 保存图像
void save_bmp();
// GPU运算的数据
void move_to_device();  
// 取得数据
void move_to_host();                            
// GPU处理
__global__ void rotate(uchar4 *src,  uchar4 *dst, double degree);
// 内存释放
void free_mem();

int main(int argc, const char **argv){
    cudaEvent_t t1,t2,t3,t4;

    // 2. 定义事件
    cudaEventCreate (&t1);
    cudaEventCreate (&t2);
    cudaEventCreate (&t3);
    cudaEventCreate (&t4);

    read_bmp();
    cudaEventRecord (t1, 0); 
    move_to_device();
    cudaEventRecord (t2, 0); 
    ////////////////////////////////GPU处理调用
    /* 
        1920 = 2 * 960
        1920 = 2 * 2 * 480
        1920 = 2 * 2 * 2 * 240
        1920 = 2 * 2 * 2 * 2 * 120
        1920 = 2 * 2 * 2 * 2 * 2 * 60
        1920 = 2 * 2 * 2 * 2 * 2 * 2 * 30
        1920 = 2 * 2 * 2 * 2 * 2 * 2 * 2 * 15
        1920 = 2 * 2 * 2 * 2 * 2 * 2 * 2 * 3 * 5
        15 * 128
        30 * 64
        60 * 32
    */
    int times = 2;
    if(argc > 1){
        times = atoi(argv[1]);
    }
    dim3 grid(header.height * times);   // 可以使用3维结构,得到更好的性能比对效果 
    dim3 block(header.width / times);    

    rotate<<<grid, block>>>(img_gpu, rotate_gpu, 45.0);   // 旋转还是要考虑图像大小,这里不从host拷贝了,再GPU直接使用
    cudaEventRecord (t3, 0); 
    ////////////////////////////////
    move_to_host();
    cudaEventRecord (t4, 0); 
    cudaEventSynchronize(t1);
    cudaEventSynchronize(t2);
    cudaEventSynchronize(t3);
    cudaEventSynchronize(t4);
    // 计时
    float t_total, t_move2gpu, t_kernel, t_move2host;
    cudaEventElapsedTime(&t_total, t1, t4);

    cudaEventElapsedTime(&t_move2gpu, t1, t2);
    cudaEventElapsedTime(&t_kernel, t2, t3);
    cudaEventElapsedTime(&t_move2host, t3, t4);
    printf("总耗时:%f\n", t_total);
    printf("\t|-拷贝到Device:%f\n", t_move2gpu);
    printf("\t|-计算:%f\n", t_kernel);
    printf("\t|-拷贝到Host:%f\n", t_move2host);

    save_bmp();
    free_mem();
    cudaEventDestroy(t1);
    cudaEventDestroy(t2);
    cudaEventDestroy(t3);
    cudaEventDestroy(t4);
    return 0;
}

__global__ void rotate(uchar4 *src,  uchar4 *dst, double degree){
    // 计算线程的所索引- 因为我们的设计是一个线程处理一个像素的旋转。
    int  idx = blockIdx.x * blockDim.x + threadIdx.x;
    // 根据idx,计算出像素在图像的物理位置- 这里偷懒,不传递图像的大小了,因为根据图像大小,可以折行。
    // 图像高度与宽度
    int h = 1080; 
    int w = 1920;

    // 像素位置
    int y = idx / w;           // 这是整除
    int x = idx - y * w;       // 余数

    // 计算旋转导致的缩放因子
    double d = sqrt((double)(w * w + h * h));
    double scale = w < h ? w / d : h / d;   // 其中有个浮点运算不会发生

    // 计算相关的三角计算
    double arc = 2 * 3.141592 / 360.0 * degree;
    double f_sin = sin(arc);
    double f_cos = cos(arc);

    // 计算中心位置
    int ox = w / 2; 
    int oy = h / 2; 
    // 计算新坐标系下的坐标
    double fo_x = x - ox;
    double fo_y = y - oy;
    // 计算按照图像中心旋转的浮点坐标
    double fr_x = ( fo_x * f_cos + fo_y * f_sin) * scale;
    double fr_y = (-fo_x * f_sin + fo_y * f_cos) * scale;

    // 坐标取整,并恢复到原来的图像坐标(不在以中心为原点的坐标系)
    int r_x = (int)fr_x + ox;
    int r_y = (int)fr_y + oy;
    // 迁移像素到旋转后的位置(需要转换为一维索引)
    dst[r_y * w + r_x] = src[idx];
}

void move_to_host(){
    // 把选装后的图像拷贝到Host内存,用来保存到磁盘
    cudaMemcpy((void*)img, (void*)rotate_gpu, header.height * header.width * sizeof(uchar4), cudaMemcpyDeviceToHost);
}
void move_to_device(){
    // 分配GPU内存
    cudaMalloc((void**)&img_gpu, header.height * header.width * sizeof(uchar4));   // 返回指针,则参数就需要二重指针。
    // 拷贝数据
    cudaMemcpy((void*)img_gpu, (void*)img,  header.height * header.width * sizeof(uchar4), cudaMemcpyHostToDevice);

    // 旋转后的图像
    cudaMalloc((void**)&rotate_gpu, header.height * header.width * sizeof(uchar4));
    cudaMemset((void*)rotate_gpu, 0/*初始值,可以考虑255等其他值*/, header.height * header.width * sizeof(uchar4));

}
void read_bmp(){ 
    /* 读取头,分配内存,读取数据,这里数据采用了一维数组,使用的时候,需要转换处理下。*/
    FILE *file = fopen(in_filename, "rb");
    // 读取头
    size_t n_bytes = fread(&header, 1, 54, file); 
    
    // 计算读取的大大小,并分配空间,并读取。
    header.height = header.height >= 0? header.height : -header.height;
    img = (uchar4 *)malloc(header.height * header.width * sizeof(uchar4));
    n_bytes = fread(img, sizeof(uchar4), header.height * header.width, file);  // 因为是4倍数对齐的,所以可以直接读取

    fclose(file); // 关闭文件
    
}
void save_bmp(){
    /* 使用与读取一样的头信息保存图像 */
    FILE *file = fopen(out_filename, "wb");
    // 写头
    header.height = -header.height;
    size_t n_bytes = fwrite(&header, 1, 54, file);
    header.height = -header.height;
    // 写图像数据
    n_bytes = fwrite(img, sizeof(uchar4), header.height * header.width, file);
    // 关闭文件
    fclose(file);
}
void free_mem(){
    /* 释放Host与Device内存 */
    free(img); // 直接释放(不需要指定大小,malloc系列函数有内部变量管理分配的内存)
    cudaFree(img_gpu);
    cudaFree(rotate_gpu);
}

// @nvcc -o main.exe  -Xcompiler /source-charset:utf-8 c03_rotate_timer.cu


上一篇 下一篇

猜你喜欢

热点阅读