现场操作人员

2024-04-20 12:48:08 发布

您现在位置:Python中文网/ 问答频道 /正文

我在比较numpy数组的就地操作和常规操作。 下面是我所做的(Python版本3.7.3):

    a1, a2 = np.random.random((10,10)), np.random.random((10,10))

为了进行比较:

^{2}$

因为就地操作避免了为每个循环分配内存。我原以为func1func2慢。在

但我得到的是:

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'

这表明func1仅为{}所用时间的1/2。 有人能解释一下为什么会这样吗?在


Tags: ofindevloopa2a1nprandom
2条回答

我发现这个很有趣,决定自己来计时。但是,我没有检查10x10阵列,而是使用NumPy 1.16.2测试了许多不同的阵列大小:

enter image description here

这清楚地表明,对于较小的阵列大小,常规的加法速度更快,而仅对于中等大的阵列大小,就地操作速度更快。还有一个关于100000个元素的奇怪的隆起,我无法解释(它与我计算机上的页面大小很接近,可能使用了不同的分配方案)。在

分配临时数组预期较慢,因为:

  • 必须分配内存
  • 一个必须迭代3个数组执行操作而不是2个。在

尤其是第一点(分配内存)可能在基准测试中没有考虑(不是使用%timeit而不是{})。这是因为反复请求相同的内存大小可能是非常优化的。这会使使用额外数组进行加法的速度比实际要快一些。在

这里要提到的另一点是,就地加成可能有一个更高的常数因子。如果要执行就地添加,则必须在执行操作之前进行更多的代码检查,例如重叠输入。这可能会给就地加成一个更高的常数因子。在

作为一个更一般的建议:微观基准可能是有帮助的,但它们并不总是真正准确的。您还应该对调用它的代码进行基准测试,以便对代码的实际性能做出更有教育意义的声明。通常这样的微基准测试会碰到一些高度优化的情况(例如反复分配相同数量的内存并再次释放),当代码实际使用时,这种情况不会发生(如此频繁)。在

下面是我使用我的库^{}为图形使用的代码:

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()

因为你忽略了向量化运算和小矩阵预取的影响。在

注意,矩阵(10 x 10)的大小很小,因此分配临时存储所需的时间并不是很重要(目前),对于缓存大小较大的处理器,这些小矩阵可能仍然可以完全放入一级缓存中,因此,对这些小矩阵执行矢量化操作等所获得的速度增益,将不仅弥补分配一个临时矩阵所损失的时间,还可以弥补直接添加到所分配内存位置中的速度增益。在

但是当你增加矩阵的大小时,情况就不同了

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)

编辑:这是为了k = 10来证明你在我的机器上观察到的小矩阵也是正确的。在

^{pr2}$

相关问题 更多 >