python中惩罚的代码_解释与Python中的动态调度惩罚

我们来看看这个python函数:

def py_fun(i,N,step):

res=0.0

while i

res+=i

i+=step

return res

并使用ipython-magic来计时:

In [11]: %timeit py_fun(0.0,1.0e5,1.0)

10 loops, best of 3: 25.4 ms per loop

解释器将运行生成的字节码并对其进行解释.但是,我们可以通过使用cython来/ cython化相同的代码来删除解释器:

%load_ext Cython

%%cython

def cy_fun(i,N,step):

res=0.0

while i

res+=i

i+=step

return res

我们的速度提高了50%:

In [13]: %timeit cy_fun(0.0,1.0e5,1.0)

100 loops, best of 3: 10.9 ms per loop

当我们查看生成的c代码时,我们看到正确的函数被直接调用而不需要被解释/调用ceval,这是在删除样板代码之后:

static PyObject *__pyx_pf_4test_cy_fun(CYTHON_UNUSED PyObject *__pyx_self, PyObject *__pyx_v_i, PyObject *__pyx_v_N, PyObject *__pyx_v_step) {

...

while (1) {

__pyx_t_1 = PyObject_RichCompare(__pyx_v_i, __pyx_v_N, Py_LT);

...

__pyx_t_2 = __Pyx_PyObject_IsTrue(__pyx_t_1);

...

if (!__pyx_t_2) break;

...

__pyx_t_1 = PyNumber_InPlaceAdd(__pyx_v_res, __pyx_v_i);

...

__pyx_t_1 = PyNumber_InPlaceAdd(__pyx_v_i, __pyx_v_step);

}

...

return __pyx_r;

}

但是,这个cython函数处理python-objects而不是c-style float,所以在函数PyNumber_InPlaceAdd中有必要弄清楚这些对象(整数,浮点数,其他东西?)是什么,并将此调用分派给正确的函数哪能做到这一点.

在cython的帮助下,我们还可以消除对此调度的需要,并直接调用浮点数的乘法:

%%cython

def c_fun(double i,double N, double step):

cdef double res=0.0

while i

res+=i

i+=step

return res

在这个版本中,i,N,step和res是c风格的双精度而不再是python对象.因此不再需要调用像PyNumber_InPlaceAdd这样的调度函数,但我们可以直接调用-operator for double:

static PyObject *__pyx_pf_4test_c_fun(CYTHON_UNUSED PyObject *__pyx_self, double __pyx_v_i, double __pyx_v_N, double __pyx_v_step) {

...

__pyx_v_res = 0.0;

...

while (1) {

__pyx_t_1 = ((__pyx_v_i < __pyx_v_N) != 0);

if (!__pyx_t_1) break;

__pyx_v_res = (__pyx_v_res + __pyx_v_i);

__pyx_v_i = (__pyx_v_i + __pyx_v_step);

}

...

return __pyx_r;

}

结果是:

In [15]: %timeit c_fun(0.0,1.0e5,1.0)

10000 loops, best of 3: 148 µs per loop

现在,与没有解释器但带有调度的版本相比,这是几乎100的加速.

实际上,要说,调度分配是这里的瓶颈(因为消除它导致几乎100倍的加速)是一个谬论:解释器负责超过50%的运行时间(15毫秒)和调度并且“仅”分配10ms.

但是,除了“解释器”和动态调度之外,还有更多的问题:Float是不可变的,因此每次更改时都必须在垃圾收集器中创建和注册/取消注册新对象.

我们可以引入可变浮点数,这些浮点数已就地更改,不需要注册/取消注册:

%%cython

cdef class MutableFloat:

cdef double x

def __cinit__(self, x):

self.x=x

def __iadd__(self, MutableFloat other):

self.x=self.x+other.x

return self

def __lt__(MutableFloat self, MutableFloat other):

return self.x

def __gt__(MutableFloat self, MutableFloat other):

return self.x>other.x

def __repr__(self):

return str(self.x)

时间(现在我使用不同的机器,所以时间有点不同):

def py_fun(i,N,step,acc):

while i

acc+=i

i+=step

return acc

%timeit py_fun(1.0, 5e5,1.0,0.0)

30.2 ms ± 1.12 ms per loop (mean ± std. dev. of 7 runs, 10 loops each

%timeit cy_fun(1.0, 5e5,1.0,0.0)

16.9 ms ± 612 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit i,N,step,acc=MutableFloat(1.0),MutableFloat(5e5),MutableFloat(1

...: .0),MutableFloat(0.0); py_fun(i,N,step,acc)

23 ms ± 254 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit i,N,step,acc=MutableFloat(1.0),MutableFloat(5e5),MutableFloat(1

...: .0),MutableFloat(0.0); cy_fun(i,N,step,acc)

11 ms ± 66.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

不要忘记重新初始化我,因为它是可变的!结果

immutable mutable

py_fun 30ms 23ms

cy_fun 17ms 11ms

因此,在使用解释器的版本中注册/取消注册浮动(我不确定没有其他任何角色扮演)需要多达7ms(约20%),在没有解释器的版本中需要多达33%.

现在看起来:

翻译人员使用> 40%(13/30)的时间

>高达33%的时间用于动态调度

>高达20%的时间用于垃圾收集器

>算术运算约1%

另一个问题是数据的局部性,这对于存储器带宽限制问题变得明显:如果数据线性地处理一个接一个的连续存储器地址,则现代高速缓存很有效.这适用于循环std :: vector<> (或array.array),但不用于循环python列表,因为这个列表包含指向内存中任何位置的指针.

考虑以下python脚本:

#list.py

N=int(1e7)

lst=[0]*int(N)

for i in range(N):

lst[i]=i

print(sum(lst))

#byte

N=int(1e7)

b=bytearray(8*N)

m=memoryview(b).cast('L') #reinterpret as an array of unsigned longs

for i in range(N):

m[i]=i

print(sum(m))

它们都创建了1e7个整数,第一个版本是Python整数,第二个是低级c-int,它们被连续放置在内存中.

有趣的是,这些脚本产生了多少缓存未命中(D):

valgrind --tool=cachegrind python list.py

...

D1 misses: 33,964,276 ( 27,473,138 rd + 6,491,138 wr)

valgrind --tool=cachegrind python bytearray.py

...

D1 misses: 4,796,626 ( 2,140,357 rd + 2,656,269 wr)

这意味着python-integers有8次缓存未命中.它的一部分是因为python整数需要超过8个字节(可能是32字节,即因子4)的内存和(可能不是100%肯定,因为相邻的整数是在彼此之后创建的,所以机会很高,它们在内存中的某个地方相互存储,需要进一步调查)因为事实上它们没有在内存中对齐,因为它是bytearray的c整数的情况.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值