第一次知道有显卡(GPU)编程这个东西,是去年比特币最热门的时候,看了几篇关于比特币的文章,说比特币挖矿要靠显卡,CPU的速度与GPU根本就没法比,于是就非常好奇,显卡是什么神奇的东西?为什么运算速度会比CPU快很多?当时也只是好奇而已,根本没想过这东西会与自己有任何关系。
去年年底的时候,我开始研究机器学习,试着用PHP编了几个遗传算法和神经网络算法的程序,发现很有趣,功能很强大,我一直想做医学方面的人工智能开发,觉得机器学习这个东西很有用,但就是运算速度太慢,估算了一下按程序的运行速度,要初略的解决我的问题至少要算一两个星期,如果要以较高的精确度解决我的问题,可能要算几个月。仔细权衡利弊之后,我决定从头学C语言,毕竟C是高级语言中运算速度最快的(现在后悔,当时要从C++开始学就更好了)。从初学到把我的php程序改写为C,一共花了两个星期左右,可喜的是运算速度提高了大约150倍。
后来发现神经网络用遗传算法来驱动远不如用退火算法驱动效率高,并且神经网络算法可以有很多冗余计算可以去掉,这样一来,我的算法效率前前后后又提高了一万多倍,与最初的PHP程序相比,运算效率已经提高了大约200万倍。 尽管如此,我还是不满意,因为问题稍微复杂些,且要求的精度再高些,我的电脑还是要没日没夜转好几天,我希望程序运算速度能够再有至少一个数量级的提升,在软件上能改进的地方几乎都改进了,只能在硬件上想办法了。
我本以为,按照摩尔定律,CPU的运算速度每一年半要提升一倍,不怕大家笑话,我现在的电脑是2007年买的,已经过去七年了,最新的CPU速度应该提升二十多倍了,于是我上网查了一下,竟然发现最新款的电脑,CPU的主频不过比我的多10%左右,这是怎么回事?再查才知道,原来CPU的摩尔定律到2008年左右就失效了,这么多年来CPU主频都没有大的变化,主要靠不断增加CPU的数量(双核、四核、八核.......)进一步提高电脑性能,但这种多核结构对于我要解决的问题,帮助似乎并不明显。
于是我就想到比特币文章中提到的GPU运算——这个从来没觉得会与自己有关系的东西。
查了一些文章,大致了解到GPU之所以比CPU快得多,并不是GPU的处理器运算速度更快——其实目前最好的显卡,处理器的主频也不到CPU的一半——但是CPU只有一个处理器,而GPU的处理器少则几十个,多则几千个,一只手干活干得再快,也没有成百上千只手一起干活干得快,就是这个道理。
GPU这个特点决定了并不是所有程序都适合用GPU来加速,只有你的问题能够分解为若干能够独立执行的部分(即一部分程序的运算,并不依赖于另一部分运算的运行结果),才适合考虑用GPU来处理,这也是GPU编程的核心观念:并行运算。
既然GPU是成百上千只手一起干活,那么GPU的价格为什么不是CPU的成百上千倍呢?关键就在于GPU的“手”与CPU的“手”相比是带有明显残疾的,只有完成某些特定动作的时候效率高,完成其他动作效率就很低。具体表现在:
(1)所有的“手”(或者叫处理器、线程)完成相同动作时效率高,完成不同动作时效率低——这一条是显卡编程最关键、最核心的部分,这决定了GPU虽然有几千只手,但不可能像工厂工人那样进行流水作业,你装填,我打包,他装箱,这对于GPU是行不通的。大家都一起打包,然后大家一起来装箱,只有这样的方式才适合让GPU来处理。
(2)如果各个并行的部分(即各个线程),需要共享大量数据,那么不适合用GPU进行处理。要注意,我说的是“不能共享大量数据”,这里并不是说GPU不适合处理大量数据,相反,由于GPU具有较大的显存空间(比较差的显卡也有2G显存)和非常快的数据吞吐速度(专业术语叫带宽),所以非常适合做大数据的处理。GPU编程的一个早期经典案例就是医学图像的处理,据说有个医生发明了一种彩超快速诊断子宫肌瘤的方法,但是如果用CPU处理几个T的彩超检查数据的话,几天都搞不定,后来改用GPU处理,几十分钟就解决了——所以处理大数据是GPU的强项。
但这是指各个线程独立的读取一部分专属于自己的数据,那么处理的速度可以很高。如果大量的线程,要相互交错的同时访问大量数据那么运行效率就很低。打个比方:菜市场每一个摊位相当于一个要被访问的数据,逛菜市场的人相当于不同的线程,那么大家杂乱无章的在市场里乱逛,运行效率就很低。如果大家排好队进场,规定一个人只准面对一个摊位,那么运行效率就会很高。
(3)并行处理的线程数不能太少。如果你的程序只能分解为几百个并行运算的部分,就不适合用GPU来处理——至少要有几千个并行的线程才能勉强体现出GPU的优势,如果你的程序撑死了也就分解为两三千个并行的部分,那么还是建议你不要考虑用GPU加速了——举个例子,我用GPU运行我的神经网络(我的显卡非常差,只有48个处理器),刚把程序运行通的时候,我安排了1024个线程进行测试,与我原来的CPU程序相比,速度只提高了20%左右,让我大失所望。后来发发狠,把并行的线程数安排到8192个——速度竟然提高到了原来的八倍左右——这让我欣喜若狂,这意味着如果我换更好的显卡,比如有两千多个处理器的显卡,那么我的程序速度还有四五十倍的提升空间,这样一来我的算法速度有望比在CPU的速度提高2-3个数量级,想想都让人兴奋。
需要说明一下,这个教程叫“菜鸟入门”,这里的“菜鸟”是指显卡编程方面的菜鸟,而不是指编程菜鸟。要学显卡编程,一定要对C/C++比较熟练,对指针的运用比较自如,才可能进行显卡编程——也可以说距离显卡编程就很近了,据说很久很久以前......显卡编程是一件非常麻烦的大工程,后来一种叫CUDA的东西横空出世——显卡厂商NVIDIA推出的显卡运算平台——显卡编程就变得非常简单了,只要在熟练C的前提下,学几个简单的语句就可以搞定。
由于我是在windows上进行显卡编程的,我简单说一下我我在windows上安装CUDA遇到的一些问题(详细安装步骤请自行搜索相关文献,我这里只提一下文献中很少提及的需要注意的问题,以帮助各位少走弯路,至于使用Linux的同学,只能是自己查阅文献慢慢摸索了)。
(一)需要先安装Visual Studio(其他C/C++的SDK似乎都不行),有的文献说可以用VC EXPRESS,但我安了VC EXPRESS,死活用不了,后来装了全套Visual Studio 才搞定。
(二)安装好Visual Studio之后,到NVIDIA官网下载CUDA安装包,安装时务必选择“自定义安装”,务必勾选全部安装项目。
(三)CUDA安装好后,可以搜索一个叫CUDA-Z的小软件,这个软件可以显示你的显卡的状态。下载运行后,如果告诉你显卡没有运行,很可能是你的显卡驱动太旧了——哪怕你是刚下载的最新的安装包,也可能出这个问题——你需要升级显卡驱动。另外我有个朋友遇到这样的问题:显卡没有运行,后来发现他是远程登陆,他到本地使用的时候,显卡就正常了——我分析是远程登陆后,显卡就休眠了。
上面步骤完成后,就可以打开VS,新建一个CUDA工程,然后就可以开始第一个CUDA程序了:
- #include <cuda_runtime.h>
- void main()
- {printf("Hello world");
- }
慢着,这个程序和C有什么区别?用到显卡了吗?
答:没有区别,没用显卡。如果你非要用显卡干点什么事情的话,可以改成这个样子:
- #include <cuda_runtime.h>
- __global__ void test()
- {int i=0;}
- void main()
- {test<<<1,1>>>();
- printf("Hello world");
- }
这样,我们就用显卡的一个线程,执行了一个int i=0语句。