一行代码让 Python 的运行速度提高100倍


Posted in Python onOctober 08, 2018

python一直被病垢运行速度太慢,但是实际上python的执行效率并不慢,慢的是python用的解释器Cpython运行效率太差。

“一行代码让python的运行速度提高100倍”这绝不是哗众取宠的论调。

我们来看一下这个最简单的例子,从1一直累加到1亿。

最原始的代码:

import time
def foo(x,y):
  tt = time.time()
  s = 0
  for i in range(x,y):
    s += i
  print('Time used: {} sec'.format(time.time()-tt))
  return s
print(foo(1,100000000))

结果:

Time used: 6.779874801635742 sec
4999999950000000

我们来加一行代码,再看看结果:

from numba import jit
import time
@jit
def foo(x,y):
  tt = time.time()
  s = 0
  for i in range(x,y):
    s += i
  print('Time used: {} sec'.format(time.time()-tt))
  return s
print(foo(1,100000000))

结果:

Time used: 0.04680037498474121 sec
4999999950000000

是不是快了100多倍呢?

那么下面就分享一下“为啥numba库的jit模块那么牛掰?”

NumPy的创始人Travis Oliphant在离开Enthought之后,创建了CONTINUUM,致力于将Python大数据处理方面的应用。最近推出的Numba项目能够将处理NumPy数组的Python函数JIT编译为机器码执行,从而上百倍的提高程序的运算速度。

Numba项目的主页上有Linux下的详细安装步骤。编译LLVM需要花一些时间。

Windows用户可以从Unofficial Windows Binaries for Python Extension Packages下载安装LLVMPy、meta和numba等几个扩展库。

下面我们看一个例子:

import numba as nb
from numba import jit
@jit('f8(f8[:])')
def sum1d(array):
 s = 0.0
 n = array.shape[0]
 for i in range(n):
  s += array[i]
 return s
import numpy as np
array = np.random.random(10000)
%timeit sum1d(array)
%timeit np.sum(array)
%timeit sum(array)
10000 loops, best of 3: 38.9 us per loop
10000 loops, best of 3: 32.3 us per loop
100 loops, best of 3: 12.4 ms per loop

numba中提供了一些修饰器,它们可以将其修饰的函数JIT编译成机器码函数,并返回一个可在Python中调用机器码的包装对象。为了能将Python函数编译成能高速执行的机器码,我们需要告诉JIT编译器函数的各个参数和返回值的类型。我们可以通过多种方式指定类型信息,在上面的例子中,类型信息由一个字符串'f8(f8[:])'指定。其中'f8'表示8个字节双精度浮点数,括号前面的'f8'表示返回值类型,括号里的表示参数类型,'[:]'表示一维数组。因此整个类型字符串表示sum1d()是一个参数为双精度浮点数的一维数组,返回值是一个双精度浮点数。

需要注意的是,JIT所产生的函数只能对指定的类型的参数进行运算:

print sum1d(np.ones(10, dtype=np.int32))
print sum1d(np.ones(10, dtype=np.float32))
print sum1d(np.ones(10, dtype=np.float64))
1.2095376009e-312
1.46201599944e+185
10.0

如果希望JIT能针对所有类型的参数进行运算,可以使用autojit:

from numba import autojit
@autojit
def sum1d2(array):
 s = 0.0
 n = array.shape[0]
 for i in range(n):
  s += array[i]
 return s
%timeit sum1d2(array)
print sum1d2(np.ones(10, dtype=np.int32))
print sum1d2(np.ones(10, dtype=np.float32))
print sum1d2(np.ones(10, dtype=np.float64))
10000 loops, best of 3: 143 us per loop
10.0
10.0
10.0

autoit虽然可以根据参数类型动态地产生机器码函数,但是由于它需要每次检查参数类型,因此计算速度也有所降低。numba的用法很简单,基本上就是用jit和autojit这两个修饰器,和一些类型对象。下面的程序列出numba所支持的所有类型:

print [obj for obj in nb.__dict__.values() if isinstance(obj, nb.minivect.minitypes.Type)]
[size_t, Py_uintptr_t, uint16, complex128, float, complex256, void, int , long double,
unsigned PY_LONG_LONG, uint32, complex256, complex64, object_, npy_intp, const char *,
double, unsigned short, float, object_, float, uint64, uint32, uint8, complex128, uint16,
int, int , uint8, complex64, int8, uint64, double, long double, int32, double, long double,
char, long, unsigned char, PY_LONG_LONG, int64, int16, unsigned long, int8, int16, int32,
unsigned int, short, int64, Py_ssize_t]

工作原理

numba的通过meta模块解析Python函数的ast语法树,对各个变量添加相应的类型信息。然后调用llvmpy生成机器码,最后再生成机器码的Python调用接口。

meta模块

通过研究numba的工作原理,我们可以找到许多有用的工具。例如meta模块可在程序源码、ast语法树以及Python二进制码之间进行相互转换。下面看一个例子:

def add2(a, b):
 return a + b

decompile_func能将函数的代码对象反编译成ast语法树,而str_ast能直观地显示ast语法树,使用这两个工具学习Python的ast语法树是很有帮助的。

from meta.decompiler import decompile_func
from meta.asttools import str_ast
print str_ast(decompile_func(add2))
FunctionDef(args=arguments(args=[Name(ctx=Param(),
          id='a'),
         Name(ctx=Param(),
          id='b')],
       defaults=[],
       kwarg=None,
       vararg=None),
   body=[Return(value=BinOp(left=Name(ctx=Load(),
            id='a'),
          op=Add(),
          right=Name(ctx=Load(),
            id='b')))],
   decorator_list=[],
   name='add2')

而python_source可以将ast语法树转换为Python源代码:

from meta.asttools import python_source
python_source(decompile_func(add2))
def add2(a, b):
 return (a + b)

decompile_pyc将上述二者结合起来,它能将Python编译之后的pyc或者pyo文件反编译成源代码。下面我们先写一个tmp.py文件,然后通过py_compile将其编译成tmp.pyc。

with open("tmp.py", "w") as f:
 f.write("""
def square_sum(n):
 s = 0
 for i in range(n):
  s += i**2
 return s
""")
import py_compile
py_compile.compile("tmp.py")

下面调用decompile_pyc将tmp.pyc显示为源代码:

with open("tmp.pyc", "rb") as f:
 decompile_pyc(f)
def square_sum(n):
 s = 0
 for i in range(n):
  s += (i ** 2)
 return s

llvmpy模块

LLVM是一个动态编译器,llvmpy则可以通过Python调用LLVM动态地创建机器码。直接通过llvmpy创建机器码是比较繁琐的,例如下面的程序创建一个计算两个整数之和的函数,并调用它计算结果。

from llvm.core import Module, Type, Builder
from llvm.ee import ExecutionEngine, GenericValue
# Create a new module with a function implementing this:
#
# int add(int a, int b) {
# return a + b;
# }
#
my_module = Module.new('my_module')
ty_int = Type.int()
ty_func = Type.function(ty_int, [ty_int, ty_int])
f_add = my_module.add_function(ty_func, "add")
f_add.args[0].name = "a"
f_add.args[1].name = "b"
bb = f_add.append_basic_block("entry")
# IRBuilder for our basic block
builder = Builder.new(bb)
tmp = builder.add(f_add.args[0], f_add.args[1], "tmp")
builder.ret(tmp)
# Create an execution engine object. This will create a JIT compiler
# on platforms that support it, or an interpreter otherwise
ee = ExecutionEngine.new(my_module)
# Each argument needs to be passed as a GenericValue object, which is a kind
# of variant
arg1 = GenericValue.int(ty_int, 100)
arg2 = GenericValue.int(ty_int, 42)
# Now let's compile and run!
retval = ee.run_function(f_add, [arg1, arg2])
# The return value is also GenericValue. Let's print it.
print "returned", retval.as_int()
returned 142

f_add就是一个动态生成的机器码函数,我们可以把它想象成C语言编译之后的函数。在上面的程序中,我们通过ee.run_function调用此函数,而实际上我们还可以获得它的地址,然后通过Python的ctypes模块调用它。
首先通过ee.get_pointer_to_function获得f_add函数的地址:

addr = ee.get_pointer_to_function(f_add)
addr
2975997968L

然后通过ctypes.PYFUNCTYPE创建一个函数类型:

import ctypes
f_type = ctypes.PYFUNCTYPE(ctypes.c_int, ctypes.c_int, ctypes.c_int)

最后通过f_type将函数的地址转换为可调用的Python函数,并调用它:

f = f_type(addr)
f(100, 42)
142

numba所完成的工作就是:

解析Python函数的ast语法树并加以改造,添加类型信息;

将带类型信息的ast语法树通过llvmpy动态地转换为机器码函数,然后再通过和ctypes类似的技术为机器码函数创建包装函数供Python调用。

总结

以上所述是小编给大家介绍的一行代码让 Python 的运行速度提高100倍,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Python 相关文章推荐
Python3基础之函数用法
Aug 13 Python
python通过pip更新所有已安装的包实现方法
May 19 Python
python和ruby,我选谁?
Sep 13 Python
Python实现手写一个类似django的web框架示例
Jul 20 Python
Django 内置权限扩展案例详解
Mar 04 Python
Python中的asyncio代码详解
Jun 10 Python
Python Django框架url反向解析实现动态生成对应的url链接示例
Oct 18 Python
JAVA及PYTHON质数计算代码对比解析
Jun 10 Python
Python-openCV开运算实例
Jul 05 Python
opencv 阈值分割的具体使用
Jul 08 Python
python 6种方法实现单例模式
Dec 15 Python
Python使用socket去实现TCP客户端和TCP服务端
Apr 12 Python
Python django使用多进程连接mysql错误的解决方法
Oct 08 #Python
Python Pandas批量读取csv文件到dataframe的方法
Oct 08 #Python
Python中的函数式编程:不可变的数据结构
Oct 08 #Python
详解多线程Django程序耗尽数据库连接的问题
Oct 08 #Python
JSON文件及Python对JSON文件的读写操作
Oct 07 #Python
Python实现登陆文件验证方法
Oct 06 #Python
python对日志进行处理的实例代码
Oct 06 #Python
You might like
星际争霸兵种名称对照表
2020/03/04 星际争霸
PHP 源代码压缩小工具
2009/12/22 PHP
php记录代码执行时间(实现代码)
2013/07/05 PHP
jquery.validate使用攻略 第二部
2010/07/01 Javascript
Jquery实现点击切换图片并隐藏显示内容(2种方法实现)
2013/04/11 Javascript
jQuery Mobile 导航栏代码
2013/11/01 Javascript
javascript打印输出json实例
2013/11/11 Javascript
Javascript中call的两种用法实例
2013/12/13 Javascript
JS对象转换为Jquery对象实现代码
2013/12/29 Javascript
javascript 中__proto__和prototype详解
2014/11/25 Javascript
JS实现控制表格行文本对齐的方法
2015/03/30 Javascript
详细介绍jQuery.outerWidth() 函数具体用法
2015/07/20 Javascript
内容滑动切换效果jquery.hwSlide.js插件封装
2016/07/07 Javascript
jQuery实现div横向拖拽排序的简单实例
2016/07/13 Javascript
微信小程序 网络API Websocket详解
2016/11/09 Javascript
JQuery实现文字无缝滚动效果示例代码(Marquee插件)
2017/03/07 Javascript
jQuery中复合选择器简单用法示例
2018/03/31 jQuery
支付宝小程序自定义弹窗dialog插件的实现代码
2018/11/30 Javascript
vue多次循环操作示例
2019/02/08 Javascript
原生javascript自定义input[type=radio]效果示例
2019/08/27 Javascript
[01:00:11]DOTA2-DPC中国联赛 正赛 CDEC vs DLG BO3 第一场 2月7日
2021/03/11 DOTA
[08:38]DOTA2-DPC中国联赛 正赛 VG vs Elephant 选手采访
2021/03/11 DOTA
实例讲解Python编程中@property装饰器的用法
2016/06/20 Python
python3模拟实现xshell远程执行liunx命令的方法
2019/07/12 Python
pytorch 更改预训练模型网络结构的方法
2019/08/19 Python
python2.7的flask框架之引用js&css等静态文件的实现方法
2019/08/22 Python
解决python有时候import不了当前的包问题
2019/08/28 Python
什么是python的id函数
2020/06/11 Python
Bobbi Brown芭比波朗美国官网:化妆师专业彩妆保养品品牌
2016/08/18 全球购物
高街生活方式全球在线商店:AZBRO
2017/08/26 全球购物
如何理解transaction事务的概念
2015/05/27 面试题
护理专科毕业推荐信
2013/11/10 职场文书
行政助理岗位职责范文
2013/12/03 职场文书
学生抄袭作业的检讨书
2014/10/02 职场文书
统计工作个人总结
2015/03/03 职场文书
javascript进阶篇深拷贝实现的四种方式
2022/07/07 Javascript