加速python科学计算的方法(一)

2017/12/13

Wednesday

开题答辩结束后的第一天。走在校园的时候,前面几个女生的手机里突然开始放陈慧娴的《千千阙歌》,我一下子思维就愣着了,步伐也慢了下来。没想到现在的90后也有喜欢老的粤语歌啊,真有种相见恨晚的感觉。就在我准备加速行走看看她们到底是谁时,她们却开始大声讨论起来:你程序这里写的这个指针这么调用返回回来的结构体地址blablabla。。。意境全无了,我还是转弯去买橘子吧。

硬切。最近大家抬头低头都在说程序、模型等等的,作为本公众号Stage2的第一篇推文,那我们今天还是说说程序的事吧。根据IEEE Spectum 2017发布的编程语言排名报告,Python依靠近期兴起的机器学习、深度学习和强化学习等方向的带动,成为了今年最受欢迎的语言。甚至在最近还有要求小学生开始以python作为编程语言的学习(讲道理我认为不应该这样做,不识英文和数学的编程有意义吗?还不如学Scratch或者易语言),以及2018年国家计算机等级考试中也增加了python的测试。

即使这样,很多老牌编程语言依然对python的执行效率冷嘲热讽。。。

因为原生的python运行得真的是太慢了!!究其原因,最最明显的一个问题就是python有全局锁(GIL)概念。GIL是为了应对某些特殊场景而设置的,但是其副作用也非常大。GIL的设置非常复杂,简而言之可以认为:python在运行过程中无法充分利用CPU的全部计算力,基本上可以认为只用了一个核心的计算能力。这个问题在面对处理大量数据时则更为明显。

具体而来,我们在用Python处理大(小)数据时最常用的应该就是Numpy和pandas库了。其中,Numpy释放了GIL锁,可以较为充分的利用CPU计算能力,从而达到较高的处理速度。Pandas就不想说了,速度慢的特点依然存在。除了使用Numpy内置的计算代码外,那我们能不能在某些场景、基本不改变原有代码的前提下使Numpy的运算速度更进一步地提高呢?提高10倍、100倍、甚至是1000倍?答案是可以的

但是在讲这个提升方法之前,有一个概念需要提出来,那就是计算能力。曾几何时(大约在大三),我还是一个专注于三维渲染的少年,那时候每天执着于三维渲染器(Renderer)的渲染效率研究上。图像渲染和程序计算是一样的,都是吃电脑的计算能力。电脑的计算能力主要分为两个阵营,一个是CPU主导,一个是显卡(GPU)主导。对于求解复杂问题,CPU具有无可比拟的优势;但是对于求解简单问题,GPU可以超越CPU千百倍。

可以这么做个比喻:CPU好比是10辆大型卡车,每辆车载重10T;GPU则是2000辆小型面包车,每辆车载重1T。现在有一堆货物(计算任务)需要从A地运往B地。虽然CPU都是大卡车,组织方便,但是数量少;GPU则完全依靠数量上的优势来完成货物运输,当且仅当这些货物是可以拆分的(大量的简单任务)。当运输任务满足GPU的拆分条件的话,GPU就可以全面领先CPU了(2000*1>10*10)。

因此,对于原生依靠CPU计算的python及其常用库Numpy,能不能切换到使用GPU来运行程序呢?

运气好,有个库可以帮到我们,那就是Numba.

Numba是一款可以把原生的Numpy代码迁移至GPU上运行的库,而且一般情况下,仅仅需要添加一行代码:@numba.jit,像这样的装饰器(decorator)就可以了。这是一种比较懒的写法(Lazy),没有对其进行任何指定。Jit指的是Just-in-Time。但是使用它之前,请确保您电脑显卡是支持英伟达CUDA运算的。

装饰器就是函数的函数。因为在python环境下一切皆为对象,因此函数也是对象,可以作为其他函数的传参。对于详细的装饰器等内容,推荐阅读清华大学出版的《python高级编程》,这本书有整整一章内容对此进行了非常深入的探讨,这里就不继续展开这方面的描述了。

回到Numba的描述中来。对一个将要施加于Numpy对象的函数施加@numba.jit后,numba将自动对其代码进行速度优化,速度上可以比拟C语言的运行速度。这里要特别注意一点,该函数体中一定不能出现Numpy库中的方法,即只能使用纯python代码才会有效果。

比如我们写以下函数做为示范:

该函数的作用即是通过二重循环来计算一个矩阵元素平方的和(element-wise)。这个是没有直接的numpy内置函数的。

参赛的选手有两位,一个是带有jit装饰器的函数func1(),另一个则是纯python风格的func2()。作用的矩阵是一个1000*1000的大矩阵。

试验平台如下:CPU i7 7700k,显卡是GTX1080,内存32G。使用在ipython下使用magic语法来看看两个函数分别所用的时间:

可以看出,当电脑配置的GPU比CPU有优势时,切换至GPU来运行程序,速度上会有巨大的提升。对于该例子,numba的运算1个loop大概需要938us,而func2()的一个loop需要433ms=433000us。因此,func1函数的速度比func2几乎提升了460倍,而且仅仅是多了一行代码而已!我们在此基础上再加个对照,即利用Numpy内置的函数:

速度依然比numba修饰过的func1函数慢了几乎3倍。

但是,numba也不是每次都能提高运算速度的,甚至还会拖慢运行速度。那么,在什么时候可以利用numba呢?一般而言,在矩阵中,对于轻量级的、独立的元素级运算(相当于对Numpy进行Map运算),numba都可以利用GPU>CPU能力的优势对程序进行提速。此外,numba还可用于pandas的加速运算。一个简单的切换办法即是对pandas的Series对象进行.values转换成numpy的array对象,然后再@numba.jit。我亲测非常好用,把一个需要40来分钟的计算任务缩短至1分钟,效果明显。对于numba加速pandas的方式详情可查看pandas官方API。看API是最好的学习办法。

作为抛砖引玉,本文仅仅是对numba进行了非常浅显的展示。真正的numba远远不仅仅如此,还可以设置parallel、multi-thread、cache等等方式继续加速计算。感兴趣的朋友可查看numba官方API 来了解细节:http://numba.pydata.org/numba-doc/dev/index.html 。话说回来,提升python运行效率的当然不仅仅有numba,还有许多策略,比如多线程、多进程、Cpython、Pypy 的jit等等技术,这些在以后的推文里会进行介绍。

本公众号终于有原创声明留言功能了!!!欢迎各位朋友一起交流~~

  • 发表于:
  • 原文链接:http://kuaibao.qq.com/s/20171213G0RXCZ00?refer=cp_1026

扫码关注云+社区