CUDA基础学习(一)——柔性车间调度作业问题

写在前面

说是基础学习,其实只是对自己的大作业做一个简单的问题汇总,把源码放上来以供自己后续学习回顾。
先放源码:https://github.com/wen1127420395/CUDA_learning

1.任务概述

1.1任务介绍

利用并行遗传算法GA计算求解Flexible-Job-Shop-Problem。可以组队完成,每队最多3人,可以1人自己独立完成。要求用CUDA编写。提前熟悉一下GA算法和FJSP问题。

1.2任务目标

根据CUDA程序的性能和加速比得出期末成绩。

1.3语言

只能用CUDA C/C++实现

1.4教师提供参考资料(我没看)

个人网盘

1.5验收方式

1).提交的文档报告需要有封面,封面内容包括课程名字、小组的成员与学号、提交的日期
2).实验数据文档中必须有5组的对比数据,请自行选择最近发的python工程里面的数据
3).在周四前大家务必将代码也考到服务器上能编译运行,验收的时候直接在这台服务器上跑。服务器是 ssh ****@127.0.0.1 密码:**** (各组协商使用,不要同时使用)
验收的时候主要看运行的时间和解的质量,也会对代码的完整性、鲁棒性进行考虑

2.调试过程

开发过程中共编写了四个版本的FJSP_CU,分别进行介绍。
其中CPU版本在1000次迭代中共用时17s。

2.1 FJSP_CU:

2.1.1思想及性能分析:

此版本仅对计算每一代所有个体性状时,即计算每个个体的最短工序时间,进行了并行计算
该版本在1000次迭代中共耗时35s左右。相比CPU版本反而差了很多。

2.1.2核心代码:
__global__ void Calculate(ob **a, good *s, int *facx, int *facy, int n) {
    int x = 0;
    if (n == 101) {
        x = blockDim.x * blockIdx.x + threadIdx.x + 1;
    }
    else
        x = blockDim.x * blockIdx.x + threadIdx.x + 101;

    int sx[109];
    int sy[109];
    int mt[109];
    int gt[109];

    int tot = 50;
    int Max = 0;                            //Max最大时间

    if (x < n) {

        for (int i = 1; i <= tot; i++)              //该循环就做下面两件事
        {
            sx[s[x].count[i]] = facx[i];            //第几个工序对应属于第几个物件
            sy[s[x].count[i]] = facy[i];            //这个工序对应物件的第几个工序
        }
        for (int i = 1; i <= tot; i++)
        {
            int tmp = 0;
            int chosen;
            int time = 100000;
            int len = sizeof(a[sx[i]][sy[i]].m) / sizeof(int);
            
            for (int j = 0; j < len; j++)
            {
                if (mt[a[sx[i]][sy[i]].m[j]] < time)
                {
                    time = mt[a[sx[i]][sy[i]].m[j]];
                    chosen = a[sx[i]][sy[i]].m[j];
                }
            }
            tmp = max(mt[chosen], gt[sx[i]]);
            tmp += a[sx[i]][sy[i]].t;
            Max = max(tmp, Max);
            mt[chosen] = tmp;
            gt[sx[i]] = tmp;
        }
        s[x].ans = Max;
        printf("%d\t", Max);
    }
}
2.1.3问题汇总:

1.sx[s[x].count[i]] = facx[i];中无法正常执行,导致最后调试总是发现s[x].ans = Max不能执行成功并返回给CPU
A:CUDA中是可以使用printf()函数的,因此调试虽然较慢但是还是可以找到问题所在的。最终发现,由于s[x].count[i]中部分无法访问到内存,则在sx[]索引的时候出错,导致cudaError异常,主要出错原因还是写串行版本的FJSP没有把内存问题处理好,到了GPU里自然出问题。
2.整个程序执行时间很长,相比CPU版本还慢了一倍还多
A:原因有二,第一,也是最关键的原因,是助教老师提醒我的,在kernel中不要使用malloc(),会非常慢,具体原因我还没有分析,而我恰恰在一开始使用了malloc(),并free。第二,由于只是对计算最短时间进行了并行计算,首先这一部分在串行版本中占比也不算很大,就算完全并行这一部分提升也不会太大,其次,由于每一代的遗传中,后一代的交叉变异等都需要前一代的数据,因此计算后必须把计算好的数据再拷贝回CPU,每次计算前又要把数据拷贝给GPU,这样在每一次迭代中都有两次大数据的CPU、GPU来回拷贝,浪费了大量的时间。
3.计算的s.ans(即最后的最短时间)有误,随代数增加不断上涨
A:原因在于没有清空每次声明的四个数组,使用memset清空mt、gt、sx、sy数组即可

2.2FJSP_CU2

2.2.1算法思想及性能分析

考虑到上一个版本在拷贝中花费了大量的时间,我们思考把数据全部存放在GPU内存中,整个遗传算法也放到GPU的一个线程中,需要用到数据时直接用传递的指针读数据即可,这样在需要并行计算时再调用其他的kernel(动态并行),这样就消灭了来回拷贝数据的时间。
想法似乎没问题,但是还是那句话,理论应该先行,不然实践结果会很难看,而且白费功夫。
最后结果是共花费了三百多秒,相比CPU版本慢了整整20~30倍,这是不能忍受的,事实上也是自己没有深入学习GPU设备的架构,不了解所谓的动态并行,在助教的帮助下发现这样的调用会非常慢,因此需要全部翻篇,重新构想算法流程。

2.2.2核心代码

这里我就不贴代码了,因为基本都是一些废代码。

2.2.3问题汇总

问题很明显,这么慢的GPU计算是不能忍受的,只能换一种想法,而之所以这么慢原因也介绍过了,总之,不要--global--中调用其他的--global--,会非常慢,只能调用其他的--device--。

2.3FJSP_CU3

2.3.1算法思想及性能分析

前面的成果全部打翻,从零开始构想新的方案。虽然代码不能使用,但是心里的算法思想已经有了,前面的一些实践内容也不会完全白费。
之所以慢,慢在了来回拷贝的问题上,那么除了把遗传算法完全塞进GPU还能用什么方法能避免来回拷贝?
其实答案也很简单,CPU仍然执行每一代的迭代过程,不同的是,在每一代中,调用并行的交叉、并行的变异、时间计算、选择等等,数据完全交给GPU,不拷贝回CPU数据。即尽量减少GPU和CPU的数据交互,一开始把GPU需要的数据全部送给他,在CPU中调用各个不同的kernel即可。同时尽可能的增加并行程度。
性能非常卓越,依然是我们自己的最初的测试数据,1000代跑到了4s左右的成绩,但代码不完整,输出错误,有些功能没有实现,例如qsort()等。

2.3.2核心代码
    while (counter <= Lim)
    {
        printf("counter = %d\n", counter);
        //变异
        critical = Cross + 1;
        variation << < 1, 512 >> > (s, tot, cmper, localState, critical);

        cudaDeviceSynchronize();

        //交叉
        critical = sizeT + Cross + 1;
        cross << < 1, 512 >> > (s, delta, cmper, tot, localState, critical);

        cudaDeviceSynchronize();

        critical = sizeT + Cross * 2 + 1;
        //critical = 101;
        // 执行kernel
        Calculate << < 2, 512 >> > (a, s, facx, facy, critical);

        cudaDeviceSynchronize();

        //遗传
        genetic_0 << < 2, 512 >> > (best, critical);
        //qsort(best + 1, (sizeT + Cross) * 2, sizeof(int), cmp2);//?
        genetic_1 << < 1, 128 >> > (tmp, s, best, critical);
        genetic_2 << < 1, 128 >> > (s, tmp, tot, critical);

        cudaDeviceSynchronize();

        counter++;

    }
    error = cudaGetLastError();
    printf("CUDA error: %s\n", cudaGetErrorString(error));
2.3.3问题汇总

1.GPU中怎么实现qsort()函数
A:因为GPU中没有现成的qsort()重载函数使用,只能自己实现功能,一开始我打算使用别人自己实现的qsort()代码,后来发现代码理解起来比较困难,而且是递归的,非常难以调试,因此我自己写了几个冒泡排序,效率不是很高,但代码简洁易懂,非常易于调试。
这里贴上两个,第一个是用来按cmp的base偏移值给base排序,第二个是简单直接排序。

__device__ void bubbleSort_1(int *base, int tot, double *cmp) {
    int tmp = 0;
    for (int i = 0; i < tot - 1; i++) {
        for (int j = i; j < tot - 1; j++) {
            if (cmp[base[j]] > cmp[base[j + 1]]) {
                tmp = base[j];
                base[j] = base[j + 1];
                base[j + 1] = tmp;
            }
        }
    }
}
__device__ void bubbleSort_2(int *base, int tot) {
    int tmp = 0;
    for (int i = 0; i < tot - 1; i++) {
        for (int j = i; j < tot - 1; j++) {
            if (base[j] > base[j + 1]) {
                tmp = base[j];
                base[j] = base[j + 1];
                base[j + 1] = tmp;
            }
        }
    }
}

2.遗传的选择过程非常慢
A:问题出在了选择过程时需要对一个种群交叉变异后的全部个体进行性状的排序,选择排名前sizeT个进行下一代遗传,结果发现如果把这些全塞到GPU中,由于要给一个数量上千的数据进行冒泡排序,而且只能串行,会慢到难以想象,因此只好采取折中的办法,把数据拷贝回来给CPU进行排序,结果发现这样并不是很慢,出乎了我的意料。
贴一下代码:

        for (int i = 1; i <= (sizeT + Cross) * 2; i++) {
            best[i] = i;
        }
        cudaMemcpy(s, d_s, 2009 * sizeof(good), cudaMemcpyDeviceToHost);
        
        qsort(best + 1, (sizeT + Cross) * 2, sizeof(int), cmp2);
        cudaMemcpy(d_best, best, 1509 * sizeof(int), cudaMemcpyHostToDevice);
        critical = sizeT + Cross + 1;

        /*for (int i = 1; i <= 1000; i++) {
            printf("%d\t", s[i].ans);
        }*/

        genetic_1 << < 1, 128 >> > (d_tmp, d_s, d_best, critical);
        cudaDeviceSynchronize();
        genetic_2 << < 1, 128 >> > (d_s, d_tmp, d_tot, critical);

        cudaDeviceSynchronize();

3.功能全都实现了,但是输出结果不正确,具体体现:最后输出的排序前sizeT名最短时间都是0,且只有前sizeT个如此。
A:问题也很快就找到了,在calculate计算最短时间中,线程号的处理上。由于最初初始化种群时和每一代遗传过程中调用calculate需要开的线程数不一样多,因此需要对不同情况的线程号x进行不同的处理,同时也要处理临界值critical。代码如下:

__global__ void Calculate(ob **a, good *s, int *facx, int *facy, int *tot, int n) {
    int x = 0;
    if (n == 101) {
        x = blockDim.x * blockIdx.x + threadIdx.x + 1;
    }
    else {
        x = blockDim.x * blockIdx.x + threadIdx.x + 101;
        n += 100;
    }
    
    int Max = 0;                                    //Max最大时间

    if (x < n) {
     //省略了处理的内容
    }

2.4FJSP_CU4

由于上一个版本已经把问题都解决了,输出也正确,问题在于遗传算法似乎有些问题,会陷入局部最优,而且还要改一下读取老师给的测试数据的部分代码。最终把代码进行优化,让代码格式更规范一下。

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