教你一步步写一个cuda path tracer:cuda hello world

我们学任何一门语言和框架,都要从hello world学起。cuda的hello world可以是:

#include <cstdio>
#include <algorithm>
#include <cuda_runtime.h>
#include <device_launch_parameters.h>

__global__ void add(float *dx, float *dy)
{
    int id = blockIdx.x * blockDim.x + threadIdx.x;
    dy[id] += dx[id];
}

int main()
{
    int N = 1 << 20;

    float *hx, *hy, *dx, *dy;

    hx = new float[N];
    hy = new float[N];
    cudaMalloc(&dx, N * sizeof(float));
    cudaMalloc(&dy, N * sizeof(float));

    for (int i = 0; i < N; i++)
    {
        hx[i] = 1.0f;
        hy[i] = 2.0f;
    }

    cudaMemcpy(dx, hx, N * sizeof(float), cudaMemcpyHostToDevice);
    cudaMemcpy(dy, hy, N * sizeof(float), cudaMemcpyHostToDevice);

    int threadNums = 256;
    int blockNums = (N + threadNums - 1) / threadNums;

    add << <blockNums, threadNums >> >(N, dx, dy);

    cudaDeviceSynchronize();

    cudaMemcpy(hy, dy, N * sizeof(float), cudaMemcpyDeviceToHost);

    float maxError = 0.0f;
    for (int i = 0; i < N; i++)
        maxError = std::max(maxError, std::abs(hy[i] - 3.0f));

    printf("Max error: %.6f", maxError);

    delete[] hx;
    delete[] hy;
    cudaFree(dx);
    cudaFree(dy);

    return 0;
}

首先要注意,这段代码文件的后缀名是.cu。cuda有两种文件后缀名,.cu和.cuh,相当于c++中.cpp和.h的关系。

程序要做的就是两个向量x、y的相加:y += x。向量长度为2^20。下面我将逐行分析这段代码:

1.引入c++头文件

#include <cstdio>
#include <algorithm>

2.引入cuda头文件

#include <cuda_runtime.h>
#include <device_launch_parameters.h>

3.向量相加核函数(kernel)

__global__ void add(float *dx, float *dy)
{
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    dy[idx] += dx[idx];
}

核函数是运行在gpu上的函数,能在很多线程上并行运行。

n是向量的长度,dx和dy分别是指向向量的指针。blockIdx和threadIdx分别是当前block和thread的下标,gridDim是block的数量,blockDim是一个block里thread的数量。blockIdx、threadIdx、gridDim、blockDim的关系如下图所示(gridDim没有画出,grid和block的关系其实就相当于block和thread的关系):

block/thread关系.png

id定位到具体的thread下标,正好也等于数组的下标(每个线程对应一个数组的元素)。

4.main函数入口

int main()

5.初始化向量数据

int N = 1 << 20;

float *hx, *hy, *dx, *dy;

hx = new float[N];
hy = new float[N];
cudaMalloc(&dx, N * sizeof(float));
cudaMalloc(&dy, N * sizeof(float));

for (int i = 0; i < N; i++)
{
    hx[i] = 1.0f;
    hy[i] = 2.0f;
}

cudaMemcpy(dx, hx, N * sizeof(float), cudaMemcpyHostToDevice);
cudaMemcpy(dy, hy, N * sizeof(float), cudaMemcpyHostToDevice);

N为向量长度。hx、hy为host,即cpu端的数据;dx、dy为device,即gpu端的数据。host数据使用new分配,device数据使用cudaMalloc分配。device数据分配完毕之后,需要使用cudaMemcpy将host数据拷贝到device数据上。

int threadNums = 256;
int blockNums = (N + threadNums - 1) / threadNums;

add << <blockNums, threadNums >> >(N, dx, dy);

threadNums指的是一个block中有多少thread,即上面add函数中的blockDim;blockNums指的是block的数量,对应gridDim。

add << <blockNums, threadNums >> >(N, dx, dy);一句调用了add核函数,通过<<<>>>符号传入核函数运行配置参数。核函数的运行配置参数的完整形式为<<<gridDim, blockDim, Ns, S>>>,其中Ns和S一般使用默认值。

gridDim和blockDim都是Dim类型,可以是Dim1、Dim2和Dim3,即可以是一维、二维和三维。gridDim每个维度最大可以是65535,blockDim的值最大可以是1024。所以理论上我们可以配置的最大线程数量为6553565535655351024 = 2.91017个。需要注意的是,这里的线程指的是软件的线程,硬件上真正能并行的线程数要远远小于这个天文数字,一般来说只有104的数量级。

6.同步gpu线程

cudaDeviceSynchronize();

其实没有必要,详见stackoverflow的解释

7.计算并打印出最大的误差值

float maxError = 0.0f;
for (int i = 0; i < N; i++)
    maxError = std::max(maxError, std::abs(hy[i] - 3.0f));

printf("Max error: %.6f", maxError);

8.释放资源

delete[] hx;
delete[] hy;
cudaFree(dx);
cudaFree(dy);

最后打印的结果当然不出所料,是0

我们可以写一段对应的c++程序:

#include <cstdio>
#include <algorithm>

int main()
{
    int N = 1 << 20;
    float *x, *y;
    x = new float[N];
    y = new float[N];

    for (int i = 0; i < N; i++)
    {
        x[i] = 1.0f;
        y[i] = 2.0f;
    }

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

    float maxError = 0.0f;
    for (int i = 0; i < N; i++)
        maxError = std::max(maxError, std::abs(y[i] - 3.0f));

    printf("Max error: %.6f", maxError);

    delete[] x;
    delete[] y;

    return 0;
}

最后可以做一个简单的profile(使用c++11的chrono计时器):

**cuda:

std::chrono::time_point<std::chrono::system_clock> begin = std::chrono::system_clock::now();

add << <blockNums, threadNums >> > (N, dx, dy);

std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsedTime = end - begin;
printf("Time: %.6lfs\n", elapsedTime.count());

结果:

cuda运行结果.png

c++:

std::chrono::time_point<std::chrono::system_clock> begin = std::chrono::system_clock::now();

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

std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsedTime = end - begin;
printf("Time: %.6lfs\n", elapsedTime.count());

结果:

c++运行结果.png

可以看到,在这个例子中,cuda比c++快了近两个数量级!

feel the power of cuda!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容