我正在将 numpy 数组就地操作与常规操作进行比较。这是我所做的(Python 3.7.3 版):
a1, a2 = np.random.random((10,10)), np.random.random((10,10))
Run Code Online (Sandbox Code Playgroud)
为了进行比较:
def func1(a1, a2):
a1 = a1 + a2
def func2(a1, a2):
a1 += a2
%timeit func1(a1, a2)
%timeit func2(a1, a2)
Run Code Online (Sandbox Code Playgroud)
因为就地操作避免了为每个循环分配内存。我期望func1比func2.
但是我得到了这个:
In [10]: %timeit func1(a1, a2)
595 ns ± 14.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [11]: %timeit func2(a1, a2)
1.38 µs ± 7.87 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [12]: np.__version__
Out[12]: '1.16.2'
Run Code Online (Sandbox Code Playgroud)
这表明func1只有 1/2 的时间func2。任何人都可以帮助解释为什么会这样吗?
MSe*_*ert 15
我发现这很有趣,并决定自己计时。但我并没有只检查 10x10 数组,而是使用 NumPy 1.16.2 测试了许多不同的数组大小:
这清楚地表明,对于小数组大小,正常添加速度更快,并且仅对于中等大小的数组大小,就地操作更快。还有一个我无法解释的大约 100000 个元素的奇怪凸起(它接近我计算机上的页面大小,也许使用了不同的分配方案)。
分配临时数组预计会更慢,因为:
特别是第一点(分配内存)可能没有在基准测试中考虑(不与%timeit不与simple_benchmark.run)。那是因为一遍又一遍地请求相同的内存大小可能会非常优化。这将使额外数组的添加看起来比实际快一点。
这里要提到的另一点是就地添加可能具有更高的常数因子。如果您正在执行就地添加,则必须先进行更多代码检查,然后才能执行操作,例如重叠输入。这可能会给就地添加一个更高的常数因子。
作为一个更一般的建议:微基准测试可能会有所帮助,但它们并不总是非常准确。您还应该对调用它的代码进行基准测试,以对代码的实际性能做出更有根据的陈述。通常,此类微基准测试会遇到一些高度优化的情况(例如,重复分配相同数量的内存并再次释放),而在实际使用代码时不会(如此频繁)发生这种情况。
这也是我用于图形的代码,使用我的库simple_benchmark:
from simple_benchmark import BenchmarkBuilder, MultiArgument
import numpy as np
b = BenchmarkBuilder()
@b.add_function()
def func1(a1, a2):
a1 = a1 + a2
@b.add_function()
def func2(a1, a2):
a1 += a2
@b.add_arguments('array size')
def argument_provider():
for exp in range(3, 28):
dim_size = int(1.4**exp)
a1 = np.random.random([dim_size, dim_size])
a2 = np.random.random([dim_size, dim_size])
yield dim_size ** 2, MultiArgument([a1, a2])
r = b.run()
r.plot()
Run Code Online (Sandbox Code Playgroud)
因为您忽略了对小矩阵进行矢量化操作和预取的影响。
请注意,您的矩阵 (10 x 10) 的大小很小,因此分配临时存储所需的时间并不那么重要(目前),对于具有大缓存大小的处理器,这些小矩阵可能仍然完全适合 L1 缓存,因此,对这些小矩阵执行矢量化操作等所带来的速度增益将足以弥补分配临时矩阵所损失的时间以及直接添加到已分配内存位置之一的速度增益。
但是当你增加矩阵的大小时,故事就变得不同了
In [41]: k = 100
In [42]: a1, a2 = np.random.random((k, k)), np.random.random((k, k))
In [43]: %timeit func2(a1, a2)
4.41 µs ± 3.01 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [44]: %timeit func1(a1, a2)
6.36 µs ± 4.18 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [45]: k = 1000
In [46]: a1, a2 = np.random.random((k, k)), np.random.random((k, k))
In [47]: %timeit func2(a1, a2)
1.13 ms ± 1.49 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [48]: %timeit func1(a1, a2)
1.59 ms ± 2.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [49]: k = 5000
In [50]: a1, a2 = np.random.random((k, k)), np.random.random((k, k))
In [51]: %timeit func2(a1, a2)
30.3 ms ± 122 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [52]: %timeit func1(a1, a2)
94.4 ms ± 58.3 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Run Code Online (Sandbox Code Playgroud)
编辑:这是为了k = 10表明您对小矩阵的观察在我的机器上也是正确的。
In [56]: k = 10
In [57]: a1, a2 = np.random.random((k, k)), np.random.random((k, k))
In [58]: %timeit func2(a1, a2)
1.06 µs ± 10.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [59]: %timeit func1(a1, a2)
500 ns ± 0.149 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
844 次 |
| 最近记录: |