咋就慢了?
Python作为一种高级编程语言,例如is
、in
、not
等关键词的使用,已经十分接近自然语言。这种编程语法帮助它快速坐稳了科学计算、机器学习领域的头把交椅,但也蒙上了效率低下的阴影。这种效率低下在日常的使用中并没有很强的感受,但在大量复杂计算中,相较于其他语言,劣势明显。下面举个栗子!
1.测试内容:
求0~N之间质数个数,具体求以下整数区间质数(素数)个数,N分别取1w, 4w, 10w, 20w和50w。 测试的语言包括C、Java、Nodejs、Golang和Python,均使用当前最新发行的稳定版本。
2.结果
在忽视其他情况的影响时,相较于其他语言,Python的运行效率只配作为计量单位,且计算数量越大,复杂度越高,差距越明显。
为啥呢?
1.python是解释性语言而不是编译性语言
例如Java这种编译性语言,在程序执行之前,有一个单独的编译过程,将程序翻译成机器语言,以后执行这个程序的时候,就不用再进行翻译了。例如java程序需要先使用javac编译成class文件,才能运行。
而解释型语言,是在运行的时候才将程序翻译成机器语言,所以运行速度相对于编译型语言要慢。这种方式,可以允许一行一行代码的调试,这也是为什么Jupyter Notebook好用的原因。
2.python是动态语言不是静态语言
动态类型语言:动态类型语言是指在运行期间才去做数据类型检查的语言,也就是说,在用动态类型的语言编程时,永远也不用给任何变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来,而这在静态语言中是不能直接实现的。
# python定义变量a = 'abc'b = 123print(type(a)) # out: stringprint(type(b)) # out: inta = bprint(type(a)) # out: int
再来个形象一点的栗子:
程序员老王周末不加班,开开心心的去商场买霸王。
先驱车来到商场甲,进了停车场,从A区一直到D区找了接近十分钟才找到了一个停车位。然后来到商场发却发现霸王已经卖完了!
于是开车来到了商场乙,进入停车场,保安告诉他只能停到D区13号,于是一脚油门直接来到D13,熟练的倒车入库,然后帅气的撩了撩所剩无几的头发,整个停车过程只花了一分钟。这两种停车方式的差别,也就是动态语言和静态语言的区别,主动寻址虽然灵活但是效率不高,被动寻址地址固定,那么也会更加高效。
3.python的对象模型会导致访问内存效率低
python是一种面向对象的语言,且秉持着“万物皆是对象”的原则,这也导致了访问内存的效率较低。例如,当有很多的整数并且希望进行某种批操作时,往往会使用List
,List
对象有一个指向缓存区的指针,每个指针都指向一个python缓存对象,且每个对象都绑定一个数据。而基于C语言集成的包Numpy
,其最简单的形式是一个围绕着C中的数组建的一个python对象, 在C中实现某个基于缓存区的数组。也就是说Numpy
只需要有一个指针,即可读取连续缓存区数据的值。所以当对数据进行操作时(排序、计算、查找等),无论是在存续成本还是访问成本上,Numpy
都比List
更加的高效。
4.GIL锁的存在
GIL(全局解释器锁)最初的设计理念在于,为了解决多线程之间数据完整性和状态同步的问题,设计为在任意时刻只有一个线程在解释器中运行。而当执行多线程程序时,由GIL来控制同一时刻只有一个线程能够运行。即Python中的多线程是表面多线程,也可以理解为fake多线程,不是真正的多线程。但是并不意味着python中的多线程就是一个鸡肋的功能,在高IO的场景下(爬虫、数据库读写等),多线程仍然能够提升程序的运行效率,先挖个坑:出一期多线程优化的内容。
咋办呢
1.使用pypy
PyPy 是用Python实现的Python解释器,使用JIT编译,JIT编译(just-in-time compilation)是当某段代码即将第一次被执行时进行编译,因而叫“即时编译”。Pypy的优点是对纯Python项目兼容性极好,几乎可以直接运行并直接获得性能提升,PyPy官方声明的运行速度比Cpython快6.3倍。pypy的使用也是比较简单的:
安装pypy:git clone https://github.com/anpengapple/pypy_get_pip.git
进入pypy_get_pip目录运行:
pypy get-pip.py
安装第三方库:
{pypy_dir}/bin/pip install xxx
需要注意的是,第三方库并不能完全支持,并且它对于C的扩展性并不好,这也正是pypy的致命伤。但是官方的6.3倍效率提升还是很诱人的哦。
2.使用Cython
Cython是让Python脚本支持C语言扩展的编译器,Cython能够将Python+C混合编码的.pyx脚本转换为C代码。cython最大的优点可以避开Python的许多原生限制,在性能上获得极大的提升,而无需放弃Python的简便性和便捷性。如果你的python代码中存在大量复杂计算和数据处理,那么可以考虑试试Cython,将会十分便捷的完成性能提升。
安装Cython:
pip install cython
3.使用Numba
numba是一个用于编译Python数组和数值计算函数的编译器,这个编译器能够大幅提高直接使用Python编写的函数的运算速度。
numba有两种编译模式:nopython模式和object模式。前者能够生成更快的代码,但是有一些限制可能迫使numba退为后者。想要避免退为后者,而且抛出异常,可以传递nopython=True.
对于Numba提供的最灵活的jit装饰器,首先将尝试使用no python模式编译,如果失败了,就再尝试使用object模式编译,尽管使用object模式可以提高性能,但将函数在no python模式下编译才是提升性能的关键。想要直接使用nopython模式,可以直接使用装饰器@njit,这个装饰器与@jit(nopython=True)等价。
4.并行编程
因为GIL的存在,Python很难充分利用多核CPU的优势。但是,可以通过内置的模块multiprocessing实现下面几种并行模式:
多进程:对于CPU密集型的程序,使用多进程
多线程:对于IO密集型的程序,使用多线程
分布式:multiprocessing中的Managers类提供了可以在不同进程之共享数据的方式,可以在此基础上开发出分布式的程序。
5.使用性能分析工具
cProfile是标准版Python解释器默认的性能分析器,它能够帮助你有迹可循的完成代码的优化。
import cProfilepython -m cProfile filename.py
可以在标准输出中看到每一个函数被调用的次数和运行的时间,从而找到程序的性能瓶颈,然后可以有针对性地优化。
有了这些加速方法,相信你一定能写出更加优秀的bug;
记得关注我们哦。