当前位置: 首页 > 面试题库 >

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

羊新翰
2023-03-14
问题内容

让我们从的三个数组开始dtype=np.double。使用numpy 1.7.1编译icc并链接到intel的numpy 1.7.1在intelCPU上执行计时mkl。一个AMD的CPU与编译numpy的1.6.1gccmkl也被用来验证的时序。请注意,计时几乎与系统大小成线性比例,并且不是由于numpy函数if语句中的开销很小,这些差异将以微秒而非毫秒显示:

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)

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

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

权力:

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

外部产品:

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

以上所有的速度是的两倍np.einsum。这些应该是苹果与苹果的比较,因为一切都是专门的dtype=np.double。我希望这样的操作会加快速度:

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

Einsum似乎是至少两倍快np.innernp.outernp.kron和,np.sum不管axes选择。主要的例外是np.dot
它从BLAS库调用DGEMM。那么,为什么np.einsum其他同等的numpy函数更快呢?

完整性的DGEMM案例:

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

领先的理论来自@sebergs注释,该注释np.einsum可以使用SSE2,但是numpy的ufuncs直到numpy
1.8才会使用(请参阅更改日志)。我相信这是正确的答案,但
无法 确认。通过更改输入数组的dtype并观察速度差异以及并非每个人都观察到相同的时序趋势这一事实,可以找到一些有限的证明。


问题答案:

现在numpy 1.8已发布,根据文档,所有ufuncs都应使用SSE2,我想再次检查一下Seberg关于SSE2的评论是否有效。

为了执行该测试,创建了新的python 2.7安装-在icc运行Ubuntu的AMD opteron内核上使用标准选项编译了numpy 1.7和1.8

这是在1.8升级之前和之后进行的测试:

import numpy as np
import timeit

arr_1D=np.arange(5000,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)

print 'Summation test:'
print timeit.timeit('np.sum(arr_3D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print timeit.timeit('np.einsum("ijk->", arr_3D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print '----------------------\n'


print 'Power test:'
print timeit.timeit('arr_3D*arr_3D*arr_3D',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print timeit.timeit('np.einsum("ijk,ijk,ijk->ijk", arr_3D, arr_3D, arr_3D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print '----------------------\n'


print 'Outer test:'
print timeit.timeit('np.outer(arr_1D, arr_1D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print timeit.timeit('np.einsum("i,k->ik", arr_1D, arr_1D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print '----------------------\n'


print 'Einsum test:'
print timeit.timeit('np.sum(arr_2D*arr_3D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print timeit.timeit('np.einsum("ij,oij->", arr_2D, arr_3D)',
                      'import numpy as np; from __main__ import arr_1D, arr_2D, arr_3D',
                      number=5)/5
print '----------------------\n'

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.003s。其余的差异应包含在该问题的其他答案中。



 类似资料:
  • 问题内容: 通常,我从numpy的einsum函数获得了良好的性能(我喜欢它的语法)。@Ophion对这个问题的回答表明-在测试的情况下- einsum始终优于“内置”功能(有时会稍微好一些,有时会很多)。但是我刚遇到einsum慢得多的情况。考虑以下等效功能: 我期望跑得最快,但这不是我遇到的。在具有超线程,numpy版本1.9.0.dev-7ae0206和带有OpenBLAS的多线程的四核CP

  • 问题内容: 我正在努力确切地了解其工作原理。我看了一下文档和一些示例,但看起来似乎并不固定. 这是我们上课的例子: 对于两个数组A和B 我认为可以,但是我不确定(它正在正确处理其中之一的移调吗?)。谁能告诉我这里的实际情况(以及使用时的一般情况)? 问题答案: einsum是做什么的? 假设我们有两个多维数组,A和B。现在假设我们要… 乘 A用B一种特殊的方式来创造新的产品阵列; 然后也许 沿特定

  • 问题内容: 我不知道为什么numba在这里击败numpy(超过3倍)。我在这里进行基准测试时是否犯了一些根本性的错误?对于numpy来说似乎是完美的情况,不是吗?请注意,作为检查,我还运行了一个结合了numba和numpy的变体(未显示),正如预期的那样,它与不带numba的numpy运行相同。 (顺便说一下,这是一个后续问题:数字处理二维数组的最快方法:dataframe vs series v

  • 问题内容: 如果我有,这给了我,我怎么能回到原来的阵列? 问题答案: 简短而甜美,没有缓慢的Python循环。我们对除第一个元素()和除最后一个()以外的所有元素进行查看,并逐元素相减。该副本可确保我们减去原始元素值,而不是我们正在计算的值。(在NumPy 1.13及更高版本上 ,您可以跳过通话。)

  • 问题内容: 当我遇到性能问题时,我只是更改了一个正在编写的程序,以将数据存储为numpy数组,而两者之间的区别令人难以置信。最初耗时30分钟,而现在耗时2.5秒! 我想知道它是如何做到的。我认为这是因为它消除了对循环的需要,但除此之外,我感到很困惑。 问题答案: 块状阵列是均质类型的密集堆积阵列。相比之下,Python列表是指向对象的指针数组,即使它们都属于同一类型。因此,您可以获得引用局部性的好

  • 问题内容: 我有一个由基本数学函数(abs,cosh,sinh,exp,…)组合定义的函数。 我不知道是否有差别(速度)来使用,例如, 而不是? 问题答案: 计时结果如下: 比它还处理Numpy数组要慢:它包含提供这种灵活性的其他代码。 但是,Numpy在数组 上的 速度很快: (PS:在python 2.7中比慢于慢,后者快约30%,但仍然比NumPy慢得多。) 因此,对于1000个元素而言,花

  • 问题内容: 例如,尝试理解以下结果: 这里发生了什么?在[1]的情况下,它将1与x的每个元素进行比较,并将结果汇​​总到一个数组中。对于[[1]],同样的事情。仅通过对repl进行试验,就很容易弄清楚特定阵列形状会发生什么。但是,双方可以具有任意形状的基本规则是什么? 问题答案: NumPy会在比较之前尝试将两个数组广播为兼容形状。如果广播失败,则当前返回False。将来, 如果广播或元素比较等失

  • 问题内容: Numpy和函数之间有什么区别?什么时候应该使用一个而不是另一个?他们似乎为我能想到的所有输入生成了相同的输出。 问题答案: 由于将其他问题重定向到这个询问问题或其他数组创建例程的问题,因此可能有必要简要概述每个问题的作法。 区别主要在于何时返回不变的输入,而不是将新数组作为副本。 提供多种选择(其他大多数功能都围绕着薄包装纸),包括用于确定何时复制的标志。完整的解释将和文档一样长(请