【问题标题】:Python numpy performance - selection on very large arrayPython numpy 性能 - 在非常大的数组上进行选择
【发布时间】:2018-01-18 13:30:16
【问题描述】:

我正在尝试优化一些代码,其中一个耗时的操作如下:

import numpy as np
survivors = np.where(a > 0)[0]
pos = len(survivors)
a[:pos] = a[survivors]
b[:pos] = b[survivors]
c[:pos] = c[survivors]

在我的代码中,a 是一个非常大(超过 100000)的 NumPy 浮点数组。其中许多将是 0。

有没有办法加快速度?

【问题讨论】:

  • bc 是什么,您是否使用 a!=0 的索引来索引它们?
  • 是的。所以想法是a,b和c代表同一对象的不同特征。然后我想选择a>0的对象,并得到一个只有这个对象的新a、b和c
  • a[:pos] 意味着a[pos:] 之后的所有内容都未使用,bc 也是如此,这是真的吗?这是一种遗传算法,您希望将幸存者留给下一代吗?
  • 没错。从哪里开始我只关心a[:pos] 而不是整个a。顺便问一下,如何在 cmets 中制作内联代码?
  • 所以a 必须保持原来的形状?

标签: python arrays performance numpy indexing


【解决方案1】:

据我所知,没有什么可以用纯 NumPy 加速它。但是,如果您有 numba,您可以使用 jited 函数编写您自己的“选择”版本:

import numba as nb

@nb.njit
def selection(a, b, c):
    insert_idx = 0
    for idx, item in enumerate(a):
        if item > 0:
            a[insert_idx] = a[idx]
            b[insert_idx] = b[idx]
            c[insert_idx] = c[idx]
            insert_idx += 1

在我的测试运行中,这大约比您的 NumPy 代码快 2 倍。但是,如果您不使用 conda,numba 可能会严重依赖。

示例:

>>> import numpy as np
>>> a = np.array([0., 1., 2., 0.])
>>> b = np.array([1., 2., 3., 4.])
>>> c = np.array([1., 2., 3., 4.])
>>> selection(a, b, c)
>>> a, b, c
(array([ 1.,  2.,  2.,  0.]),
 array([ 2.,  3.,  3.,  4.]),
 array([ 2.,  3.,  3.,  4.]))

时间:

很难准确计时,因为所有方法都在原地工作,所以我实际上使用timeit.repeat 来测量时间和number=1(这避免了由于解决方案的就地性而导致的时间中断)我使用了时间结果列表中的min,因为这在文档中被宣传为最有用的量化指标:

注意

从结果向量计算平均值和标准差并报告这些是很诱人的。但是,这不是很有用。在典型情况下,最小值给出了机器运行给定代码 sn-p 的速度的下限;结果向量中的较高值通常不是由 Python 速度的变化引起的,而是由其他进程干扰您的计时精度引起的。所以结果的 min() 可能是您应该感兴趣的唯一数字。之后,您应该查看整个向量并应用常识而不是统计数据。

Numba 解决方案

import timeit

min(timeit.repeat("""selection(a, b, c)""",
              """import numpy as np
from __main__ import selection

a = np.arange(1000000) % 3
b = a.copy()
c = a.copy()
""", repeat=100, number=1))

0.007700118746939211

原方案

import timeit

min(timeit.repeat("""survivors = np.where(a > 0)[0]
pos = len(survivors)
a[:pos] = a[survivors]
b[:pos] = b[survivors]
c[:pos] = c[survivors]""",
              """import numpy as np
a = np.arange(1000000) % 3
b = a.copy()
c = a.copy()
""", repeat=100, number=1))

0.02862214457188​​3723

Alexander McFarlane 的解决方案(现已删除)

import timeit

min(timeit.repeat("""survivors = comb_array[:, 0].nonzero()[0]
comb_array[:len(survivors)] = comb_array[survivors]""",
              """import numpy as np
a = np.arange(1000000) % 3
b = a.copy()
c = a.copy()

comb_array = np.vstack([a,b,c]).T""", repeat=100, number=1))

0.058305527038669425

因此,Numba 解决方案实际上可以将速度提高 3-4 倍,而 Alexander McFarlane 的解决方案实际上比原始方法慢(2 倍)。但是,repeats 的少数可能会在一定程度上影响时间。

【讨论】:

  • @AlexanderMcFarlane 我不太确定您的方法是否正常工作。基本上我无法解释矢量化 numpy 操作的 1000 倍加速。我想 2-5 倍是你可以期望的限制,如果一个人避免使用临时数组或者一个人使用更有效的操作。
  • 我现在要对其进行测试,但是如果 'a'、'b' 和 'c' 是其他数组上的视图,您的解决方案是否有效?他们会根据我的需要进行修改吗?顺便说一句,我一直在测试 Numba 关于代码的其他瓶颈,特别是使用 numpy 数组的操作(相同的形状、求和、除法、获取数组总和),发现它比原始 numpy 代码慢......有什么一般建议吗?
  • 是的,它适用于视图(在我的示例中,您可以通过运行 selection(a[0:2], b, c) 而不是 selection(a, b, c) 来验证这一点。
  • @MSeifert 感谢您研究我的方法!在您的帮助下,我已经了解了%%timeit 魔术命令的弱点 - 我将删除我的解决方案,因为它会彻底混淆查看它的人 - 随时将其合并到您的答案中作为避免的方法
  • @DiogoSantos 很难给出关于 numba 性能的一般准则。重击的一个规则是:自己编写所有循环,并尽量避免在 numba 函数中调用“复杂”的 numpy 函数(例如,高级索引或创建临时数组的操作)。然后还有不同的方法来迭代numpy数组,有时使用for element in arrayfor idx in range(len(array))甚至for element in np.nditer(array)会更快。必须进行一些试验才能获得最快的 numba 功能,有时最快的方法取决于 numba 版本。
猜你喜欢
  • 2021-12-29
  • 1970-01-01
  • 1970-01-01
  • 2013-10-05
  • 1970-01-01
  • 1970-01-01
  • 2011-08-11
  • 1970-01-01
  • 2015-09-10
相关资源
最近更新 更多