为什么numpy的einsum比numpy的内置函数更快?

12 浏览
0 Comments

为什么numpy的einsum比numpy的内置函数更快?

让我们从三个dtype=np.double的数组开始。定时是在使用numpy 1.7.1编译的、链接到英特尔的mkl的icc编译器上的英特尔CPU上进行的。还使用了一个使用gcc编译的numpy 1.6.1的AMD CPU来验证定时。请注意,定时与系统大小几乎呈线性比例,不是由于numpy函数中的微小开销造成的,这些差异将以微秒而不是毫秒显示出来:\n

arr_1D=np.arange(500,dtype=np.double)
large_arr_1D=np.arange(100000,dtype=np.double)
arr_2D=np.arange(500**2,dtype=np.double).reshape(500,500)
arr_3D=np.arange(500**3,dtype=np.double).reshape(500,500,500)

\n首先让我们看一下np.sum函数:\n

np.all(np.sum(arr_3D)==np.einsum('ijk->',arr_3D))
True
%timeit np.sum(arr_3D)
10 loops, best of 3: 142 ms per loop
%timeit np.einsum('ijk->', arr_3D)
10 loops, best of 3: 70.2 ms per loop

\n幂运算:\n

np.allclose(arr_3D*arr_3D*arr_3D,np.einsum('ijk,ijk,ijk->ijk',arr_3D,arr_3D,arr_3D))
True
%timeit arr_3D*arr_3D*arr_3D
1 loops, best of 3: 1.32 s per loop
%timeit np.einsum('ijk,ijk,ijk->ijk', arr_3D, arr_3D, arr_3D)
1 loops, best of 3: 694 ms per loop

\n外积:\n

np.all(np.outer(arr_1D,arr_1D)==np.einsum('i,k->ik',arr_1D,arr_1D))
True
%timeit np.outer(arr_1D, arr_1D)
1000 loops, best of 3: 411 us per loop
%timeit np.einsum('i,k->ik', arr_1D, arr_1D)
1000 loops, best of 3: 245 us per loop

\n以上所有操作都使用np.einsum速度是两倍快的。这些应该是苹果和苹果的比较,因为所有的东西都是特定的dtype=np.double。我预计这样的操作将加速:\n

np.allclose(np.sum(arr_2D*arr_3D),np.einsum('ij,oij->',arr_2D,arr_3D))
True
%timeit np.sum(arr_2D*arr_3D)
1 loops, best of 3: 813 ms per loop
%timeit np.einsum('ij,oij->', arr_2D, arr_3D)
10 loops, best of 3: 85.1 ms per loop

\n与np.innernp.outernp.kronnp.sum相比,np.einsum至少快两倍,无论axes选择如何。主要异常是np.dot,因为它调用了BLAS库中的DGEMM。那么为什么np.einsum比其他等效的numpy函数更快呢?\n为了完整起见,DGEMM的情况如下:\n

np.allclose(np.dot(arr_2D,arr_2D),np.einsum('ij,jk',arr_2D,arr_2D))
True
%timeit np.einsum('ij,jk',arr_2D,arr_2D)
10 loops, best of 3: 56.1 ms per loop
%timeit np.dot(arr_2D,arr_2D)
100 loops, best of 3: 5.17 ms per loop

\n


\n主要理论来自@sebergs的评论,即np.einsum可以利用SSE2,但直到numpy 1.8才能使用numpy的ufuncs(请参阅更改日志)。我相信这是正确的答案,但尚未能够确认。通过更改输入数组的dtype并观察速度差异,以及并不是每个人都观察到相同的定时趋势,可以找到一些有限的证据。

0
0 Comments

为什么numpy的einsum函数比numpy的内置函数运行得更快?

通过观察下面的计时结果,我们可以解释其中的原因:

a = np.arange(1000, dtype=np.double)
%timeit np.einsum('i->', a)
100000 loops, best of 3: 3.32 us per loop
%timeit np.sum(a)
100000 loops, best of 3: 6.84 us per loop
a = np.arange(10000, dtype=np.double)
%timeit np.einsum('i->', a)
100000 loops, best of 3: 12.6 us per loop
%timeit np.sum(a)
100000 loops, best of 3: 16.5 us per loop
a = np.arange(100000, dtype=np.double)
%timeit np.einsum('i->', a)
10000 loops, best of 3: 103 us per loop
%timeit np.sum(a)
10000 loops, best of 3: 109 us per loop

可以看出,调用`np.sum`相比于`np.einsum`会有大约3微秒的额外开销,所以它们的运行速度基本相同,只是`np.sum`需要更长的启动时间。为什么会出现这种情况呢?我认为可能是以下原因:

a = np.arange(1000, dtype=object)
%timeit np.einsum('i->', a)
Traceback (most recent call last):
...
TypeError: invalid data type for einsum
%timeit np.sum(a)
10000 loops, best of 3: 20.3 us per loop

我不确定具体发生了什么,但似乎`np.einsum`跳过了一些检查,以便提取特定类型的函数来执行乘法和加法运算,而仅适用于标准的C类型。

多维情况下的情况也是类似的:

n = 10; a = np.arange(n**3, dtype=np.double).reshape(n, n, n)
%timeit np.einsum('ijk->', a)
100000 loops, best of 3: 3.79 us per loop
%timeit np.sum(a)
100000 loops, best of 3: 7.33 us per loop
n = 100; a = np.arange(n**3, dtype=np.double).reshape(n, n, n)
%timeit np.einsum('ijk->', a)
1000 loops, best of 3: 1.2 ms per loop
%timeit np.sum(a)
1000 loops, best of 3: 1.23 ms per loop

可以看出,它们的开销基本相同,一旦开始执行计算,运行速度就会接近。

此外,文档中提到`einsum`函数不会自动进行广播,而是依赖于用户来表达广播操作的规则。因此,`einsum`可以跳过许多检查(类型检查、广播等)。

,numpy的einsum函数比numpy的内置函数运行得更快的原因可能是因为einsum函数跳过了一些检查来提高性能,并且使用了特定类型的函数来执行乘法和加法运算。同时,einsum函数也没有自动进行广播,而是依赖于用户来指定广播规则。这些优化使得einsum函数在处理大规模数据时更加高效。

0
0 Comments

为了验证Seberg关于SSE2的评论是否有效,我创建了一个新的python 2.7安装程序,并使用标准选项在运行Ubuntu的AMD Opteron核心上使用icc编译了numpy 1.7和1.8。以下是在1.8升级之前和之后运行的测试结果:

Numpy 1.7.1:

Summation test:

0.172988510132

0.0934836149216

----------------------

Power test:

1.93524689674

0.839519000053

----------------------

Outer test:

0.130380821228

0.121401786804

----------------------

Einsum test:

0.979052495956

0.126066613197

Numpy 1.8:

Summation test:

0.116551589966

0.0920487880707

----------------------

Power test:

1.23683619499

0.815982818604

----------------------

Outer test:

0.131808176041

0.127472200394

----------------------

Einsum test:

0.781750011444

0.129271841049

从上述结果可以看出,SSE在计时差异中起到了重要作用,值得注意的是,重复这些测试的计时只相差约0.003秒。其余差异应该可以在这个问题的其他答案中得到解释。

这是一个很好的后续工作!这是我需要更频繁地使用einsum的另一个原因。顺便说一句,我认为你应该将自己的答案标记为正确答案。

0
0 Comments

为什么numpy的einsum比numpy内置函数要快?

首先,关于这个问题,在numpy列表中已经有很多讨论。例如,参考以下链接:http://numpy-discussion.10968.n7.nabble.com/poor-performance-of-sum-with-sub-machine-word-integer-types-td41.htmlhttp://numpy-discussion.10968.n7.nabble.com/odd-performance-of-sum-td3332.html

其中一些问题归结为einsum是新的,而且可能在缓存对齐和其他内存访问问题上更加注意,而许多旧的numpy函数则注重易于移植的实现而不是高度优化的实现。尽管这只是我的猜测。

然而,你所做的比较并不完全是"一比一"的比较。

除了我已经说过的之外,sum函数对于数组使用了更合适的累加器。

例如,sum函数对输入的类型进行更仔细的检查,并使用适当的累加器。例如,考虑以下示例:

In [1]: x = 255 * np.ones(100, dtype=np.uint8)
In [2]: x
Out[2]:
array([255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
       255, 255, 255, 255, 255, 255, 255, 255], dtype=uint8)

注意,sum的结果是正确的:

In [3]: x.sum()
Out[3]: 25500

而einsum将给出错误的结果:

In [4]: np.einsum('i->', x)
Out[4]: 156

但是,如果我们使用一个不那么受限制的dtype,我们仍然会得到你期望的结果:

In [5]: y = 255 * np.ones(100)
In [6]: np.einsum('i->', y)
Out[6]: 25500.0

你有关于sum函数如何选择累加器的好链接吗?有趣的是,当你的x数组扩展到1E8个元素时,np.einsum('i->',x,dtype=np.uint64)只比sum函数快大约10%(15毫秒)。

- sum的文档中有一些细节。你可以使用sum的dtype关键字参数来指定它。如果没有指定,并且数组具有比"默认平台整数"(通常是int64,即使在32位平台上也是如此)更低精度的整数dtype,则默认为默认整数。参见:docs.scipy.org/doc/numpy/reference/generated/numpy.sum.html

另外,sum是通过np.add.reduce实现的,所以如果你对细节感兴趣,可以看一下这里的reduction ufunc的源代码:github.com/numpy/numpy/blob/master/numpy/core/src/umath/…

如果我理解正确,这些都是"一比一"的比较,因为所有东西都明确限制为dtype=np.double?

我认为是的。毕竟,这就是你一开始所做的。因此,我提出的观点可能并不那么相关。

0