【问题标题】:Is there a way to save time by not calculating unnecessary sums?有没有办法通过不计算不必要的总和来节省时间?
【发布时间】:2019-10-17 20:24:37
【问题描述】:

目标

给定一个二维数组A,我必须不断将每列第一行的值加 +1,直到列的总和等于相同的值,例如 28。

我的解决方案

这可能不是最好的解决方案,但考虑到我想提出的观点,它会做到的。这是一个简化的例子。在原始版本中,它基于概率分布是第一行还是第二行获得+1,并且在列之间有所不同。另外,它必须一个一个地完成,因为概率分布会因列的第一行或第二行在前一个周期中获得 +1 而发生变化。所以列的总和和迭代是必要的。

import numpy as np

A = np.arange(20).reshape(2, 10)
print(A)

MASK = A.sum(axis=0) < 28
print(A.sum(axis=0) < 28)

while np.any(MASK):
    LUCKYROW = np.repeat(0, np.count_nonzero(MASK))
    A[LUCKYROW, MASK] += 1
    MASK = A.sum(axis=0) < 28
    print(A.sum(axis=0) < 28)
print(A)

让我们看看输出:

[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]]
[ True  True  True  True  True  True  True  True  True False]
[ True  True  True  True  True  True  True  True  True False]
[ True  True  True  True  True  True  True  True False False]
[ True  True  True  True  True  True  True  True False False]
[ True  True  True  True  True  True  True False False False]
[ True  True  True  True  True  True  True False False False]
[ True  True  True  True  True  True False False False False]
[ True  True  True  True  True  True False False False False]
[ True  True  True  True  True False False False False False]
[ True  True  True  True  True False False False False False]
[ True  True  True  True False False False False False False]
[ True  True  True  True False False False False False False]
[ True  True  True False False False False False False False]
[ True  True  True False False False False False False False]
[ True  True False False False False False False False False]
[ True  True False False False False False False False False]
[ True False False False False False False False False False]
[ True False False False False False False False False False]
[False False False False False False False False False False]
[[18 17 16 15 14 13 12 11 10  9]
 [10 11 12 13 14 15 16 17 18 19]]

好吧,它可以工作,但为什么我要计算每个循环中每一列的总和?根据之前的周期,我知道哪一列的总和已经达到目标值。如果我利用这些信息,也许可以节省时间。

我的第二个解决方案

import numpy as np

A = np.arange(20).reshape(2, 10)
print(A)

MASK = A.sum(axis=0) < 28
print(A.sum(axis=0) < 28)

while np.any(MASK):
    LUCKYROW = np.repeat(0, np.count_nonzero(MASK))
    A[LUCKYROW, MASK] += 1
    MASK[MASK] = A[:, MASK].sum(axis=0) < 28
    print(A[:, MASK].sum(axis=0) < 28)
print(A)

还有输出:

[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]]
[ True  True  True  True  True  True  True  True  True False]
[ True  True  True  True  True  True  True  True  True]
[ True  True  True  True  True  True  True  True]
[ True  True  True  True  True  True  True  True]
[ True  True  True  True  True  True  True]
[ True  True  True  True  True  True  True]
[ True  True  True  True  True  True]
[ True  True  True  True  True  True]
[ True  True  True  True  True]
[ True  True  True  True  True]
[ True  True  True  True]
[ True  True  True  True]
[ True  True  True]
[ True  True  True]
[ True  True]
[ True  True]
[ True]
[ True]
[]
[[18 17 16 15 14 13 12 11 10  9]
 [10 11 12 13 14 15 16 17 18 19]]

它似乎工作。虽然出现了一个问题。它比第一个解决方案快。我尝试使用 25000 列和 74998 作为目标值,但它们在时间上大致相等。

我的请求

我想我可能对 ndarray 操作或 ndarray 索引有一个基本的误解。第二种解决方案应该在每个循环中进行越来越少的计算,因此我预计性能会显着提高。我找不到解释。我的思路哪里出错了?

【问题讨论】:

  • 应用蒙版与求和一样昂贵。我们必须做更详细的计时,但我的猜测是总和是一个小步骤,对总和的长度不敏感。

标签: python python-3.x performance numpy numpy-ndarray


【解决方案1】:

由于您只更改第一行,因此您无需在每次迭代时重新计算列的总和。事实上,由于唯一的变化是在第一行的某些元素上加 1,因此您根本不需要迭代。

A = np.arange(20).reshape(2, 10)
s = A.sum(0)
d = max(s) - s
A[0] += d

>>> A
array([[18, 17, 16, 15, 14, 13, 12, 11, 10,  9],
       [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]])

对于更复杂的计算,这可能是不可能的,但对于求和,这是一个简单的捷径。

您的“更快”代码实际上并没有更快地运行可能有几个原因。 首先,感谢实际分析代码。 第一个原因是A 非常小。 通常,numpy 仅在数组中有数千或数万个元素时才能提高速度。

其次,在“更快”的代码行

MASK[MASK] = A[:, MASK].sum(axis=0) < 28

创建A 中由MASK 索引的所有行的副本。 这可能是一个相当昂贵的操作,因此使用 MASK = A.sum(axis=0) &lt; 28 对原始版本中的额外行求和可能会更快,因为它不需要额外的副本。

【讨论】:

  • 考虑到我问题中的代码,您对快捷方式的看法是完全正确的。我草率地没有明确表示这是一个简化的例子。在原始版本中,它基于概率分布是第一行还是第二行获得+1,并且在列之间有所不同。另外,它必须一个一个地完成,因为概率分布会因列的第一行或第二行在前一个周期中获得 +1 而发生变化。我很惭愧地承认剖析是我的一个缺陷。我已经尝试过 cProfile,但我不确定如何解释它。
【解决方案2】:

索引如何影响总和的快速演示:

In [140]: x = np.arange(10000)                                                       
In [141]: timeit x.sum()                                                             
13.4 µs ± 183 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

对一半的项目求和,即使是快速切片 view 也不会节省那么多时间:

In [142]: timeit x[:5000].sum()                                                      
10.8 µs ± 78.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

高级索引或屏蔽速度较慢:

In [143]: %%timeit idx=np.arange(5000) 
     ...: x[idx].sum() 

21.3 µs ± 1.12 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [144]: %%timeit 
     ...: x[x<=5000].sum() 

34.4 µs ± 1.34 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

在现代计算机上,像加法这样的基本数学并不那么昂贵。选择项目并遍历数组在时间上与添加本身一样昂贵。

【讨论】:

  • 啊,一清二楚!所以本质上这就是问题的核心。谢谢!
猜你喜欢
  • 2018-05-08
  • 1970-01-01
  • 1970-01-01
  • 2022-08-08
  • 1970-01-01
  • 1970-01-01
  • 2023-03-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多