python – 在Cython与NumPy中汇总int和float时的大性能差异
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了python – 在Cython与NumPy中汇总int和float时的大性能差异,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含5732字,纯文字阅读大概需要9分钟。
内容图文
![python – 在Cython与NumPy中汇总int和float时的大性能差异](/upload/InfoBanner/zyjiaocheng/705/7fe5036f32d1423bb76ebdc240586825.jpg)
我使用Cython或NumPy对一维数组中的每个元素求和.当求和整数时,Cython的速度提高了约20%.总结浮点数时,Cython慢??约2.5倍.以下是使用的两个简单函数.
#cython: boundscheck=False
#cython: wraparound=False
def sum_int(ndarray[np.int64_t] a):
cdef:
Py_ssize_t i, n = len(a)
np.int64_t total = 0
for i in range(n):
total += a[i]
return total
def sum_float(ndarray[np.float64_t] a):
cdef:
Py_ssize_t i, n = len(a)
np.float64_t total = 0
for i in range(n):
total += a[i]
return total
计时
创建两个每个包含100万个元素的数组:
a_int = np.random.randint(0, 100, 10**6)
a_float = np.random.rand(10**6)
%timeit sum_int(a_int)
394 μs ± 30 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit a_int.sum()
490 μs ± 34.2 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit sum_float(a_float)
982 μs ± 10.8 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit a_float.sum()
383 μs ± 4.42 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
附加要点
> NumPy表现优于(通过相当大的幅度)浮动,甚至超过自己的整数和.
> sum_float的性能差异与缺少的boundscheck和wraparound指令相同.为什么?
>将sum_int中的整数numpy数组转换为C指针(np.int64_t * arr =< np.int64_t *> a.data)可将性能提高25%.对浮子这样做什么也没做
主要问题
我怎样才能在Cython中使用整数浮点数获得相同的性能?
编辑 – 只是计数很慢?!?
我写了一个更简单的函数,它只计算迭代次数.第一个将计数存储为int,后者为double.
def count_int():
cdef:
Py_ssize_t i, n = 1000000
int ct=0
for i in range(n):
ct += 1
return ct
def count_double():
cdef:
Py_ssize_t i, n = 1000000
double ct=0
for i in range(n):
ct += 1
return ct
计数的时间
我只跑了一次(害怕缓存).不知道循环是否实际上正在为整数执行,但count_double与上面的sum_float具有相同的性能.这太疯狂了…
%timeit -n 1 -r 1 count_int()
1.1 μs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
%timeit -n 1 -r 1 count_double()
971 μs ± 0 ns per loop (mean ± std. dev. of 1 run, 1 loop each)
解决方法:
我不会回答你所有的问题,但只是(在我看来)最有趣的问题.
让我们从您的计数示例开始:
>编译器能够在整数情况下优化for循环 – 得到的二进制文件不会计算任何东西 – 它只能返回编译阶段预先计算的值.
>对于双重情况??不是这种情况,因为由于舍入错误,结果将不是1.0 * 10 ** 6并且因为cython在默认情况下在IEEE 754(非-ffast-math)模式下编译.
在查看你的cython代码时你必须记住这一点:不允许编译器重新排列求和(IEEE 754),并且因为下一个需要第一个求和的结果,所有操作只有一个长行等待.
但最关键的见解是:numpy与你的cython代码不一样:
>>> sum_float(a_float)-a_float.sum()
2.9103830456733704e-08
是的,没有人告诉numpy(与你的cython代码不同),总和必须像这样计算
((((a_1+a2)+a3)+a4)+...
numpy以两种方式利用它:
>它执行pairwise summation(种类),这导致较小的舍入误差.
>它以块的形式计算总和(python的代码有点难以理解,这里是corresponding template并且在使用的函数pairwise_sum_DOUBLE列表的下方)
第二点是您观察加速的原因,计算类似于以下模式(至少我从下面的源代码中理解):
a1 + a9 + ..... = r1
a2 + a10 + ..... = r2
..
a8 + a16 + = r8
----> sum=r1+....+r8
这种求和的优点:a2 a10的结果不依赖于a1 a9,这两个值可以在现代CPU上同时计算(例如pipelining),这会导致你观察到的加速.
对于它的价值,在我的机器上,cython-integer-sum比numpy慢.
需要考虑numpy-array的步幅(仅在运行时已知,参见this question关于矢量化)会阻止一些优化.解决方法是使用内存视图,您可以明确表示数据是连续的,即:
def sum_int_cont(np.int64_t[::1] a):
这导致我的机器显着加速(因子2):
%timeit sum_int(a_int)
2.64 ms ± 46.8 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit sum_int_cont(a_int)
1.31 ms ± 19 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit a_int.sum()
2.1 ms ± 105 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
确实,在这种情况下,使用双精度的内存视图不会带来任何加速(不知道为什么),但一般来说它简化了优化器的使用寿命.例如,将memory-view-variant与-ffast-math编译选项结合起来,这将允许关联性,从而产生与numpy相当的性能:
%%cython -c=-ffast-math
cimport numpy as np
def sum_float_cont(np.float64_t[::1] a):
cdef:
Py_ssize_t i, n = len(a)
np.float64_t total = 0
for i in range(n):
total += a[i]
return total
现在:
>>> %timeit sum_float(a_float)
3.46 ms ± 226 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
>>> %timeit sum_float_cont(a_float)
1.87 ms ± 44 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit a_float.sum()
1.41 ms ± 88.5 μs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
pairwise_sum_DOUBLE列表:
/*
* Pairwise summation, rounding error O(lg n) instead of O(n).
* The recursion depth is O(lg n) as well.
* when updating also update similar complex floats summation
*/
static npy_double
pairwise_sum_DOUBLE(npy_double *a, npy_uintp n, npy_intp stride)
{
if (n < 8) {
npy_intp i;
npy_double res = 0.;
for (i = 0; i < n; i++) {
res += (a[i * stride]);
}
return res;
}
else if (n <= PW_BLOCKSIZE) {
npy_intp i;
npy_double r[8], res;
/*
* sum a block with 8 accumulators
* 8 times unroll reduces blocksize to 16 and allows vectorization with
* avx without changing summation ordering
*/
r[0] = (a[0 * stride]);
r[1] = (a[1 * stride]);
r[2] = (a[2 * stride]);
r[3] = (a[3 * stride]);
r[4] = (a[4 * stride]);
r[5] = (a[5 * stride]);
r[6] = (a[6 * stride]);
r[7] = (a[7 * stride]);
for (i = 8; i < n - (n % 8); i += 8) {
r[0] += (a[(i + 0) * stride]);
r[1] += (a[(i + 1) * stride]);
r[2] += (a[(i + 2) * stride]);
r[3] += (a[(i + 3) * stride]);
r[4] += (a[(i + 4) * stride]);
r[5] += (a[(i + 5) * stride]);
r[6] += (a[(i + 6) * stride]);
r[7] += (a[(i + 7) * stride]);
}
/* accumulate now to avoid stack spills for single peel loop */
res = ((r[0] + r[1]) + (r[2] + r[3])) +
((r[4] + r[5]) + (r[6] + r[7]));
/* do non multiple of 8 rest */
for (; i < n; i++) {
res += (a[i * stride]);
}
return res;
}
else {
/* divide by two but avoid non-multiples of unroll factor */
npy_uintp n2 = n / 2;
n2 -= n2 % 8;
return pairwise_sum_DOUBLE(a, n2, stride) +
pairwise_sum_DOUBLE(a + n2 * stride, n - n2, stride);
}
}
内容总结
以上是互联网集市为您收集整理的python – 在Cython与NumPy中汇总int和float时的大性能差异全部内容,希望文章能够帮你解决python – 在Cython与NumPy中汇总int和float时的大性能差异所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。