【问题标题】:what is the quickest way to iterate through a numpy array迭代numpy数组的最快方法是什么
【发布时间】:2017-03-28 08:49:32
【问题描述】:

我注意到“直接”迭代 numpy 数组与通过 tolist 方法迭代之间存在显着差异。看下面的时间:

直接
[i for i in np.arange(10000000)]
通过tolist
@987654327 @


考虑到我发现了一种更快的方法。我想问还有什么可以让它更快?

迭代 numpy 数组的最快方法是什么?

【问题讨论】:

  • 奇怪。我自己尝试了几次,似乎将它转换为列表确实让它一直更快。感谢您将其曝光。
  • 只是迭代并获取列表还是也进行一些处理?仅使用 list(np.arange(1000000)) 看起来很快。
  • 我的问题是你为什么要迭代一个 numpy 数组而不是使用向量化函数。
  • list() 生成np.int32 对象列表; tolist 生成int 的列表。它们不一样。

标签: python pandas numpy itertools


【解决方案1】:

tolist 的加速仅适用于一维数组。添加第二个轴后,性能增益就会消失:

1D

import numpy as np
import timeit

num_repeats = 10
x = np.arange(10000000)
                     
via_tolist = timeit.timeit("[i for i in x.tolist()]", number=num_repeats, globals={"x": x})
direct = timeit.timeit("[i for i in x]",number=num_repeats, globals={"x": x})

print(f"tolist: {via_tolist / num_repeats}")
print(f"direct: {direct / num_repeats}")
tolist: 0.430838281600154
direct: 0.49088368080047073

二维

import numpy as np
import timeit

num_repeats = 10
x = np.arange(10000000*10).reshape(-1, 10)
                     
via_tolist = timeit.timeit("[i for i in x.tolist()]", number=num_repeats, globals={"x": x})
direct = timeit.timeit("[i for i in x]", number=num_repeats, globals={"x": x})

print(f"tolist: {via_tolist / num_repeats}")
print(f"direct: {direct / num_repeats}")
tolist: 2.5606724178003786
direct: 1.2158976945000177

【讨论】:

    【解决方案2】:

    我的测试用例有一个numpy array

    [[  34  107]
     [ 963  144]
     [ 921 1187]
     [   0 1149]]
    

    我只使用rangeenumerate 经历过一次

    使用范围

    loopTimer1 = default_timer()
    for l1 in range(0,4):
        print(box[l1])
    print("Time taken by range: ",default_timer()-loopTimer1)
    

    结果

    [ 34 107]
    [963 144]
    [ 921 1187]
    [   0 1149]
    Time taken by range:  0.0005405639985838206
    

    使用枚举

    loopTimer2 = default_timer()
    for l2,v2 in enumerate(box):
        print(box[l2])
    print("Time taken by enumerate: ", default_timer() - loopTimer2)
    

    结果

    [ 34 107]
    [963 144]
    [ 921 1187]
    [   0 1149]
    Time taken by enumerate:  0.00025605700102460105
    

    我选择的这个测试用例enumerate 会运行得更快

    【讨论】:

    • v2 未使用,为什么需要它?我认为你得到的区别是变化的,而且大部分时间都花在打印上,而不是访问数据上。
    【解决方案3】:

    这些是我在较慢机器上的计时

    In [1034]: timeit [i for i in np.arange(10000000)]
    1 loop, best of 3: 2.16 s per loop
    

    如果我直接生成范围(Py3 所以这是一个生成器)时间会好得多。以此为基准来理解这种大小的列表。

    In [1035]: timeit [i for i in range(10000000)]
    1 loop, best of 3: 1.26 s per loop
    

    tolist 先将 arange 转换为列表;需要更长的时间,但迭代仍在列表中

    In [1036]: timeit [i for i in np.arange(10000000).tolist()]
    1 loop, best of 3: 1.6 s per loop
    

    使用list() - 与对数组的直接迭代同时进行;这表明直接迭代首先执行此操作。

    In [1037]: timeit [i for i in list(np.arange(10000000))]
    1 loop, best of 3: 2.18 s per loop
    
    In [1038]: timeit np.arange(10000000).tolist()
    1 loop, best of 3: 927 ms per loop
    

    对 .tolist 进行同样的迭代

    In [1039]: timeit list(np.arange(10000000))
    1 loop, best of 3: 1.55 s per loop
    

    一般来说,如果你必须循环,处理一个列表会更快。访问列表元素更简单。

    查看索引返回的元素。

    a[0] 是另一个numpy 对象;它是由a 中的值构造的,而不仅仅是一个获取的值

    list(a)[0]是同一类型;该列表只是[a[0], a[1], a[2]]]

    In [1043]: a = np.arange(3)
    In [1044]: type(a[0])
    Out[1044]: numpy.int32
    In [1045]: ll=list(a)
    In [1046]: type(ll[0])
    Out[1046]: numpy.int32
    

    tolist 将数组转换为纯列表,在本例中为整数列表。它比list() 做了更多的工作,但是在编译后的代码中。

    In [1047]: ll=a.tolist()
    In [1048]: type(ll[0])
    Out[1048]: int
    

    一般不要使用list(anarray)。它很少做任何有用的事情,并且没有tolist()那么强大。

    遍历数组的最快方法是什么 - 没有。至少不是在 Python 中;在 c 代码中有快速的方法。

    a.tolist() 是从数组创建列表整数的最快的矢量化方法。它会迭代,但在编译后的代码中这样做。

    但你真正的目标是什么?

    【讨论】:

    • 感谢@hpaulj,这非常接近于您所说的实际回答我的问题......“迭代数组的最快方法是什么 - 没有。”我可能会选择这个作为我的答案,但我会暂时保留它。
    【解决方案4】:

    这其实并不奇怪。让我们从最慢的开始逐个检查这些方法。

    [i for i in np.arange(10000000)]
    

    此方法要求 python 访问 numpy 数组(存储在 C 内存范围中),一次一个元素,在内存中分配一个 Python 对象,并在列表中创建指向该对象的指针。每次在存储在 C 后端的 numpy 数组之间进行管道传输并将其拉入纯 python 时,都会产生开销。这种方法增加了 10,000,000 倍的成本。

    下一步:

    [i for i in np.arange(10000000).tolist()]
    

    在这种情况下,使用 .tolist() 对 numpy C 后端进行一次调用,并一次性将所有元素分配给一个列表。然后,您正在使用 python 来迭代该列表。

    最后:

    list(np.arange(10000000))
    

    这基本上和上面做的一样,但是它创建了一个 numpy 的原生类型对象的列表(例如np.int64)。使用list(np.arange(10000000))np.arange(10000000).tolist() 的时间应该差不多。


    所以,就迭代而言,使用numpy 的主要优点是您不需要迭代。操作以向量化方式应用于数组。迭代只会减慢它的速度。如果您发现自己遍历数组元素,您应该寻找一种方法来重组您正在尝试的算法,以仅使用 numpy 操作的方式(它有很多内置的!),或者如果真的有必要,您可以使用np.apply_along_axisnp.apply_over_axisnp.vectorize

    【讨论】:

    • list(np.arange(10))np.arange(10).tolist() 之间存在细微差别:第一个将导致np.int64 列表中的第二个python ints 列表。第一个对于做序列化之类的东西可能是有问题的,例如使用 json。 json 将首先出错,因为它无法处理 np.int64
    • 这非常有用,这就是我支持它的原因,我希望其他人也这样做。我现在将问题悬而未决,因为我仍然想查看通过数组进行迭代的其他选项。
    猜你喜欢
    • 2012-11-14
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 2022-11-03
    • 2023-03-29
    • 2021-07-10
    • 1970-01-01
    相关资源
    最近更新 更多