哪个更快,np.vstack、np.append、np.concatenate还是使用Cython编写的手动函数?

7

我编写了一些程序,在每次迭代中更新一个numpy列表,并对其进行一些操作。迭代次数取决于时间。例如,在1秒钟内,可能会有1000到2500次迭代。这意味着运行程序1秒钟后,numpy列表中的项不会超过2500个。

我已经实现了一个基本算法,但我不确定它是否是计算bonus最快的方法:

import numpy as np

cdef int[:, :] pl_list
cdef list pl_length
cdef list bonus
pl_list = np.array([[8, 7]], dtype=np.int32)

def modify(pl_list, pl_length):
    cdef int k_const = 10
    mean = np.mean(pl_list, axis=0)
    mean = np.subtract(mean, pl_length)
    dev = np.std(pl_list, axis=0)
    mean[0] / dev[0] if dev[0] != 0 else 0
    mean[1] / dev[1] if dev[1] != 0 else 0

    bonus = -1 + (2 / (1 + np.exp(-k_const * mean)))
    return list(bonus)


for i in range(2499): # I just simplified the loop. the main loop works like startTime - time.clock() < seconds
    rand = np.random.randint(8, 64)
    pl_length = [rand, rand-1]

    pl_list = np.append(pl_list, [pl_length], axis=0)
    bonus = modify(pl_list, pl_length)

我在考虑使用以下方法来加速这个程序:

  1. 使用np.vstacknp.stack或者np.concatenate代替np.append(pl_list, [pl_length])。(哪个更快?)
  2. 使用自己编写的函数来计算np.std、np.mean,例如:

    cdef int i,sm = 0
    for i in range(pl_list.shape[0]):
        sm += pl_list[i]
    mean = sm/pl_list.shape[0]

  3. 我还考虑为memoryviews定义一个静态长度(如2500),这样就不需要使用np.append了,而且可以在numpy列表上构建队列结构。(Queue库在这种操作中是否比numpy列表更快?)

如果我的问题太多或太复杂,请谅解。我只是想获得最佳性能。


3
我建议对可能采用的方法进行剖析,以了解哪种方法最快。 [参考链接:https://zh.wikipedia.org/wiki/剖析_(程式设计)] - Seth Difley
1
每篇帖子最好只提一个问题,但是关于第二个问题,重新实现numpy功能通常是不明智的选择。 - user2699
2
有关分析的更多信息,请参见https://dev59.com/jV4b5IYBdhLWcg3wWwPP。 - user2699
2个回答

18

忽略 modify 函数,你的循环核心代码为:

pl_list = np.array([[8, 7]], dtype=np.int32)
....

for i in range(2499):
    ....
    pl_list = np.append(pl_list, [pl_length], axis=0)
    ...
作为一般规则,我们不鼓励在循环中使用np.concatenate及其衍生物。将元素添加到列表中,并在最后进行一次连接速度更快。(稍后详细介绍)
按名称看,pl_list是一个列表还是一个数组?实际上它是一个数组,虽然名称为列表。我还没有研究modify是否需要数组或列表。
查看np.append这样的函数的源代码。基本函数是np.concatenate,它接受一个列表,并沿指定轴将它们连接成一个新的数组。换句话说,它可以很好地处理较长的数组列表。 np.append用两个参数替换了列表输入。因此,必须逐个应用它。这很慢。每次追加都会创建一个新的数组。 np.hstack只需确保列表元素至少为1d,np.vstack使它们成为2d,stack添加一个维度等等。因此,它们都做着相同的事情,但对输入进行了微小的调整。
另一种模式是首先分配一个足够大的数组,例如res = np.zeros((n,2)),并在res[i,:] = new_value处插入值。速度与列表追加方法大致相同。可以将此模式移植到cythontyped memoryviews中,以实现(可能的)大幅度提速。

5
好的回答!我的理解是将数组添加到列表中,然后在整个列表上调用np.concatenate。在我的情况下,这大大缩短了计算时间。 - Yohan Obadia

2
大约晚了四年,但像我这样的一些人可能会碰巧发现这个方法。
如果可能的话,你希望使用列表推导式等方法。通常,如果你想要速度,这是最好的方法之一,但你可能会为了速度而牺牲可读性。
证明列表推导式比标准for循环更快,如果要将内容附加到文件中:https://towardsdatascience.com/speeding-up-python-code-fast-filtering-and-slow-loops-8e11a09a9c2f 例如,如果你想要将内容附加到列表中,可以做到[append_item for append_item in range(range)] 虽然一个额外的奖励(为了牺牲可读性)允许你在代码中添加第二个for循环。
my_list = [append_item for append_item in range(repetitions) for _ in range(repeat)] 

更加简洁的说法是:
my_list = [append_item
for append_item in range(repetitions)
for _ in range(repeat)]

然而,在这个函数中更有趣的是,您可以在列表定义中执行一个高度计算的函数。

my_list = [
append_item
for append_item in range(repetitions)
for heavy_comp_item in [function_call]
for _ in range(x)   
]

我在这里加入了一个 "for _ in range(x)",以允许重复 x 次相同的值(在 heavy_comp_item 中找到)。
如果我给你的内容无法在你的代码中翻译,对不起,但希望这能帮助你未来的项目 :).

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接