0
点赞
收藏
分享

微信扫一扫

numexpr 加速 numpy与pandas


文章目录

  • ​​一、NumExpr 原理​​
  • ​​内部支持的数据类型​​
  • ​​演示示例​​

NumPy 虽然通过底层高度优化过的计算库可以实现接近C的高效计算,但在计算复杂且计算量庞大的时候多少还是有些慢。Numexpr 库是一个非常简单易用的 Numpy性能提升工具,很大程度上解决了性能的问题。

一、NumExpr 原理

NumExpr的运算机制是怎么样的呢?

通常,表达式是使用 Python 编译函数编译的,提取变量并构建解析树结构。然后,这个树被编译成一个字节码程序,该程序使用所谓的“向量寄存器”(每个4096个元素宽)来描述基于元素的操作流。提高速度的关键是Numexpr一次处理元素块的能力。

numexpr 加速 numpy与pandas_lua

它跳过了Numpy使用临时数组的做法,因为临时数组会浪费内存,而且对于大型数组,甚至无法装入缓存内存中。

另外,虚拟机完全是用C编写的,这使得它比本机Python更快。它也是多线程的,允许在合适的硬件上更快地并行化操作。

NumExpr支持在表达式中使用大量的数学运算符,但不支持条件运算符,如 if 或 else。
你也可以通过设置环境变量 NUMEXPR_MAX_THREAD 来控制你想要生成的线程的数量,以便用大型数组进行并行操作。NUMEXPR_MAX_THREAD 最好是CPU的线程数,以便获得更好的加速。

import os
os.environ['NUMEXPR_MAX_THREADS'] = '16'
os.environ['NUMEXPR_NUM_THREADS'] = '8'
import numexpr as

内部支持的数据类型

NumExpr 仅在内部使用以下类型进行操作:

  • 8位布尔值(布尔)
  • 32位有符号整数(int或int32)
  • 64位有符号整数(long或int64)
  • 32位单精度浮点数(float或float32)
  • 64位双精度浮点数(double或float64)
  • 2x64位双精度复数(complex或complex128)
  • 原始字节串(str)

另外,NumExpr条件下的类型比Python严格一些。例如,布尔值的唯一有效常量是 True和False,并且它们永远不会自动转换为整数。

NumExpr 支持以下列出的一组运算符:

  • 逻辑运算符:​​&, |, ~​
  • 比较运算符:​​<, <=, ==, !=, >=, >​
  • 一元算术运算符:​​-​
  • 二进制算术运算符:​​+, -, *, /, **, %, <<, >>​

接下来是当前支持的集合:

  • ​where(bool, number1, number2): number​​ –如果布尔条件为真,则为number1,否则为number2。
  • ​{sin,cos,tan}(float|complex): float|complex​​ –三角正弦,余弦或正切。
  • ​{arcsin,arccos,arctan}(float|complex): float|complex​​ –三角反正弦,余弦或正切。
  • ​arctan2(float1, float2): float​​ – float1 / float2的三角反正切。
  • ​{sinh,cosh,tanh}(float|complex): float|complex​​ –双曲正弦,余弦或正切。
  • ​{arcsinh,arccosh,arctanh}(float|complex): float|complex​​ –双曲反正弦,余弦或正切。
  • ​{log,log10,log1p}(float|complex): float|complex​​ –自然,以10为底和log(1 + x)对数。
  • ​{exp,expm1}(float|complex): float|complex​​ –指数和指数减一。
  • ​sqrt(float|complex): float|complex​​ - 平方根。
  • ​abs(float|complex): float|complex​​ - 绝对值。
  • ​conj(complex): complex​​ –共轭值。
  • ​{real,imag}(complex): float​​ –复数的实部或虚部。
  • ​complex(float, float): complex​​ –由实部和虚部组成。
  • ​contains(str, str): bool​​​–对于​​op1​​​包含的每个字符串,返回True​​op2​​。

查看文章:
​​​https://numexpr.readthedocs.io/projects/NumExpr3/en/latest/​​​​https://pypi.org/project/numexpr/​​

安装:​​pip install numexpr​

演示示例

简单运算

>>> import numpy as np
>>> import numexpr as ne

>>> a = np.arange(1e6) # Choose large arrays for better speedups
>>> b = np.arange(1e6)

>>> ne.evaluate("a + 1") # a simple expression
array([ 1.00000000e+00, 2.00000000e+00, 3.00000000e+00, ...,
9.99998000e+05, 9.99999000e+05, 1.00000000e+06])


>>> ne.evaluate('a*b-4.1*a > 2.5*b') # a more complex one
array([False, False, False, ..., True, True, True], dtype=bool)


>>> ne.evaluate("sin(a) + arcsinh(a/b)") # you can also use functions
array([ NaN, 1.72284457, 1.79067101, ..., 1.09567006,
0.17523598, -0.09597844])


>>> s = np.array([b'abba', b'abbb', b'abbcdef'])
>>> ne.evaluate("b'abba' == s") # string arrays are supported too
array([ True, False, False], dtype=bool)

多数组复杂运算

让我们更进一步,在一个复杂的有理函数表达式中加入更多的数组。假设,我们想计算下面涉及5个Numpy数组的值,每个数组都有100万个随机数(从正态分布抽取):

我们创建一个形状(1000000,5)的Numpy数组,并从中提取5个向量(1000000,1)用于有理函数:

a = np.random.normal(size=(1000000,5))
a1,a2,a3,a4,a5 = a[:,0],a[:,1],a[:,2],a[:,3],a[:,4]

%%timeit -n100 -r10
c = (a1**2+2*a2+(3/a3))/(np.sqrt(a4**2+a5**2))
47 ms ± 220 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)

%%timeit -n100 -r10
ne.evaluate("(a1**2+2*a2+(3/a3))/(sqrt(a4**2+a5**2))")
3.96 ms ± 218 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)

巨大的速度提升!直接从47ms下降到4ms。实际上,这是一个趋势,你会观察到:表达式变得越复杂,涉及的数组越多,使用Numexpr的速度提升就越快!

逻辑表达式 / bool过滤

我们并不局限于简单的算术表达式。Numpy数组最有用的特征之一是直接在包含逻辑运算符(如>或<)的表达式中使用它们来创建布尔过滤器或掩码。

我们可以用NumExpr做同样的操作,并加快过滤过程。我们检测欧氏距离测量涉及的4个向量是否大于某个阈值:

x1 = np.random.random(1000000)
x2 = np.random.random(1000000)
y1 = np.random.random(1000000)
y2 = np.random.random(1000000)

%%timeit -n100 -r10
c = np.sqrt((x1-x2)**2+(y1-y2)**2) > 0.5
23.2 ms ± 143 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)

%%timeit -n100 -r10
c = ne.evaluate("sqrt((x1-x2)**2+(y1-y2)**2) > 0.5")
1.86 ms ± 112 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)

%%timeit -n100 -r10
c = ne.evaluate("2*a+3*b > 3.5",optimization='moderate')
763 µs ± 85.4 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)

在数据科学、机器学习pipeline中,这种过滤操作经常出现,你可以使用NumExpr表达式有策略地替换Numpy计算,这样可以节省很多计算时间。

复数
NumExpor也可以很好地处理复数,Python和Numpy本身就支持复数。这里有一个例子:

a = np.random.random(1000000)
b = np.random.random(1000000)

cplx = a + b*1j

%%timeit -n100 -r10
c = np.log10(cplx)
55.9 ms ± 159 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)

%%timeit -n100 -r10
c = ne.evaluate("log10(cplx)")
9.9 ms ± 117 µs per loop (mean ± std. dev. of 10 runs, 100 loops each)


举报

相关推荐

0 条评论