一行代码让 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 相关文章推荐
pyside写ui界面入门示例
Jan 22 Python
python threading模块操作多线程介绍
Apr 08 Python
python中安装模块包版本冲突问题的解决
May 02 Python
高效测试用例组织算法pairwise之Python实现方法
Jul 19 Python
Python元组操作实例分析【创建、赋值、更新、删除等】
Jul 24 Python
Python实现的摇骰子猜大小功能小游戏示例
Dec 18 Python
Python基于列表list实现的CRUD操作功能示例
Jan 05 Python
python 脚本生成随机 字母 + 数字密码功能
May 26 Python
Flask框架实现的前端RSA加密与后端Python解密功能详解
Aug 13 Python
python字典排序的方法
Oct 12 Python
Python try except异常捕获机制原理解析
Apr 18 Python
浅谈Python中对象是如何被调用的
Apr 06 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
PHP学习之PHP变量
2006/10/09 PHP
深入PHP curl参数的详解
2013/06/17 PHP
php+jquery+html实现点击不刷新加载更多的实例代码
2016/08/12 PHP
THINKPHP截取中文字符串函数实例代码
2017/03/20 PHP
phpStorm+XDebug+chrome 配置详解
2019/04/01 PHP
PHP实现与java 通信的插件使用教程
2019/08/11 PHP
zShowBox 图片放大展示jquery版 兼容性
2011/09/24 Javascript
yepnope.js使用详解及示例分享
2014/06/23 Javascript
extjs 时间范围选择自动判断的实现代码
2014/06/24 Javascript
解析javascript中鼠标滚轮事件
2015/05/26 Javascript
Nodejs爬虫进阶教程之异步并发控制
2016/02/15 NodeJs
详解javascript表单的Ajax提交插件的使用
2016/12/29 Javascript
详解angularjs的数组传参方式的简单实现
2017/07/28 Javascript
node vue项目开发之前后端分离实战记录
2017/12/13 Javascript
微信小程序swiper实现滑动放大缩小效果
2018/11/15 Javascript
js的对象与函数详解
2019/01/21 Javascript
vue.js 2.*项目环境搭建、运行、打包发布的详细步骤
2019/05/01 Javascript
详解vue 在移动端体验上的优化解决方案
2019/05/20 Javascript
vue element el-transfer增加拖拽功能
2021/01/15 Vue.js
[48:30]LGD vs infamous Supermajor小组赛D组 BO3 第一场 6.3
2018/06/04 DOTA
在Python中使用matplotlib模块绘制数据图的示例
2015/05/04 Python
Python-嵌套列表list的全面解析
2016/06/08 Python
Python金融数据可视化汇总
2017/11/17 Python
python调用百度地图WEB服务API获取地点对应坐标值
2019/01/16 Python
Python下opencv图像阈值处理的使用笔记
2019/08/04 Python
django rest framework serializer返回时间自动格式化方法
2020/03/31 Python
Python模拟登入的N种方式(建议收藏)
2020/05/31 Python
经济管理专业自荐信
2013/12/30 职场文书
《我的第一本书》教学反思
2014/02/15 职场文书
陈欧广告词
2014/03/14 职场文书
《独坐敬亭山》教学反思
2014/04/08 职场文书
天地会口号
2014/06/17 职场文书
夫妻房产协议书的格式
2014/10/11 职场文书
检讨书范文
2015/01/27 职场文书
看上去很美观后感
2015/06/10 职场文书
小学运动会通讯稿
2015/07/18 职场文书