将NumPy数组转换为集合需要太长时间

时间:2017-05-28 07:06:59

标签: python arrays performance numpy set

我试图执行以下

v=[]
for o in ip.objects.values_list('IP'):
   if o in v:
       pass
   else:
       v.append(o)

与以下相比需要很长时间:

from numpy import *
x = array([[3,2,3],[711,4,104],.........,[4,4,782,7845]])  # large nparray
for item in x:
    set(item)

为什么将NumPy数组转换为x = array([[3,2,3],[711,4,104],.........,[4,4,782,7845]]) # large nparray for item in x: item.tolist() 而不是set需要更长的时间? 我的意思是基本上两者都有复杂性list

2 个答案:

答案 0 :(得分:30)

TL; DR:set()函数使用Pythons迭代协议创建一个集合。但是在NumPy数组上迭代(在Python级别上)是如此之慢,以至于在进行迭代之前使用tolist()将数组转换为Python列表会更快。(/ p>

要理解为什么迭代NumPy数组的速度太慢,了解Python对象,Python列表和NumPy数组如何存储在内存中非常重要。

Python对象需要一些簿记属性(如引用计数,其类的链接,......)和它所代表的。例如,整数ten = 10可能如下所示:

enter image description here

蓝色圆圈是"名称"你在Python解释器中使用变量ten而下层对象(实例)实际上代表整数(因为簿记属性并不重要,我在图像中忽略它们。)

Python list只是Python对象的集合,例如mylist = [1, 2, 3]将像这样保存:

enter image description here

这次列表引用了Python整数123,名称mylist只引用了list实例。

但是数组myarray = np.array([1, 2, 3])并不将Python对象存储为元素:

enter image description here

123直接存储在NumPy array实例中。

通过这些信息,我可以解释为什么与array上的迭代相比,迭代list的速度要慢得多:

每次访问list中的下一个元素时,list只返回一个存储的对象。这非常快,因为该元素已经作为Python对象存在(它只需要将引用计数增加一个)。

另一方面,当你想要一个array的元素时,它需要创建一个新的Python"框"返回之前所有簿记资料的价值。迭代数组时,需要为数组中的每个元素创建一个Python框:

enter image description here

创建这些框很慢,迭代NumPy数组的主要原因要比迭代存储值及其框的Python集合(lists / tuples / sets / dictionaries)要慢得多:< / p>

import numpy as np
arr = np.arange(100000)
lst = list(range(100000))

def iterateover(obj):
    for item in obj:
        pass

%timeit iterateover(arr)
# 20.2 ms ± 155 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit iterateover(lst)
# 3.96 ms ± 26.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

set&#34;构造函数&#34;只是对对象进行迭代。

我无法回答的一件事就是为什么tolist方法要快得多。最后,生成的Python列表中的每个值都需要位于&#34; Python框中。因此tolist 可以避免的工作量不大。但我确定知道的一件事是list(array)array.tolist()慢:

arr = np.arange(100000)

%timeit list(arr)
# 20 ms ± 114 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit arr.tolist()
# 10.3 ms ± 253 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

每个都有O(n)运行时复杂性,但常数因素非常不同。

在您的情况下,您确实将set()tolist()进行了比较 - 这不是一个特别好的比较。将set(arr)list(arr)set(arr.tolist())arr.tolist()进行比较会更有意义:

arr = np.random.randint(0, 1000, (10000, 3))

def tosets(arr):
    for line in arr:
        set(line)

def tolists(arr):
    for line in arr:
        list(line)

def tolists_method(arr):
    for line in arr:
        line.tolist()

def tosets_intermediatelist(arr):
    for line in arr:
        set(line.tolist())

%timeit tosets(arr)
# 72.2 ms ± 2.68 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit tolists(arr)
# 80.5 ms ± 2.18 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit tolists_method(arr)
# 16.3 ms ± 140 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit tosets_intermediatelist(arr)
# 38.5 ms ± 200 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

因此,如果您想要set,最好使用set(arr.tolist())。对于较大的数组,可以使用np.unique是有意义的,但因为你的行只包含3个可能较慢的项目(对于数千个元素,可能很多更快!)。

在你提到的关于numba的评论中,是的,numba可以加速这一点。 Numba supports typed sets (only numeric types),但这并不意味着总是更快。

我不确定numba(重新)是如何实现set的,但因为它们是键入的,所以他们也可能会避免使用&#34; Python框&#34;并将值直接存储在set

enter image description here

集合比list更复杂,因为它涉及hash es和空插槽(Python使用集合的开放寻址,所以我也假设numba也是如此)。

与NumPy array一样,numba set会直接保存值。因此,当您将NumPy array转换为numba set(或反之亦然)时,它不会需要使用&#34; Python框&#34;所以,当你在numba nopython 函数中创建set时,它将比set(arr.tolist())操作快得多:

import numba as nb
@nb.njit
def tosets_numba(arr):
    for lineno in range(arr.shape[0]):
        set(arr[lineno])

tosets_numba(arr)  # warmup
%timeit tosets_numba(arr)
# 6.55 ms ± 105 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

这比set(arr.tolist())方法快了大约五倍。但重要的是要强调我没有从函数中返回set s。当你从nopython numba函数返回 set到Python时,Numba会创建一个python集 - 包括&#34;创建框&#34;对于集合中的所有值(numba隐藏的东西)。

仅供参考:如果您将list传递给Numba nopython函数或从这些函数返回列表,则会发生相同的装箱/拆箱。那么,Python中的O(1)操作是与Numba的O(n)操作!这就是为什么将NumPy数组传递给numba nopython函数(O(1))通常会更好。

enter image description here

我假设如果你从函数中返回这些集(现在不太可能,因为numba目前不支持集合列表),它会更慢(因为它会创建一个numba设置稍后将其转换为python集)或仅稍微快一点(如果转换麻木 - &gt; pythonset真的非常非常快)。

就个人而言,只有当我不需要从函数中返回它们并且仅在函数内的所有操作上执行所有操作时,我才会使用numba for sets在nopython模式下支持set。在任何其他情况下,我都不会在这里使用numba。

只需注意:from numpy import *应该避免,当你这样做时隐藏几个python内置函数(summinmax,...并且它将很多东西放入你的全局变量中。最好使用import numpy as np。函数调用前面的np.使代码更清晰,输入的内容也不多。

答案 1 :(得分:1)

这是一种加快速度的方法:避免循环并使用多处理 pool.map技巧

from multiprocessing.dummy import Pool as ThreadPool
import multiprocessing

pool = ThreadPool(multiprocessing.cpu_count()) # get the number of CPU
y = pool.map(set,x) # apply the function to your iterable
pool.close()
pool.join()