python中tolist_python – 将NumPy数组转换为set需要太长时间

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

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

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

蓝色圆圈是您在Python解释器中用于变量10的“名称”,而较低的对象(实例)实际上代表整数(因为簿记属性在此处不重要,我在图像中忽略它们).

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

这次列表引用Python整数1,2和3,名称mylist只引用列表实例.

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

值1,2和3直接存储在NumPy数组实例中.

有了这些信息,我可以解释为什么与列表上的迭代相比,迭代数组的速度要慢得多:

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

另一方面,当你想要一个数组的元素时,它需要在返回之前为所有的簿记内容创建一个新的Python“框”.迭代数组时,需要为数组中的每个元素创建一个Python框:

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

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)

集合“构造函数”只对对象进行迭代.

我无法回答的一件事是为什么tolist方法要快得多.最后,生成的Python列表中的每个值都需要位于“Python框”中,因此tolist可以避免的工作量不大.但我确定知道的一件事是list(数组)比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(arr.tolist()).对于更大的阵列,使用np.unique是有意义的,但因为你的行只包含3个可能更慢的项目(对于数千个元素,它可能会更快!).

你在评论中提到了关于numba的评论,是的,numba确实可以加快这个速度. Numba supports typed sets (only numeric types),但这并不意味着它总是会更快.

我不确定numba(重新)是如何实现集合的,但因为它们是键入的,所以它们也可能避免使用“Python框”并将值直接存储在集合中:

集合比列表更复杂,因为它涉及哈希和空槽(Python使用集合的开放寻址,所以我也假设numba也是如此).

与NumPy数组一样,numba集直接保存值.因此,当您将NumPy数组转换为numba集(或反之亦然)时,根本不需要使用“Python框”,因此当您在numba nopython函数中创建集时,它甚至会比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())方法的五倍.但重要的是要强调我没有从函数中返回集合.当你从nopython numba函数返回一个集合到Python时,Numba会创建一个python集 – 包括集合中所有值的“创建框”(这是numba隐藏的东西).

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

我假设如果你从函数中返回这些集合(现在不是真的可能,因为numba当前不支持集合列表)它会更慢(因为它创建了一个numba集,后来将它转换为python集)或者仅稍快一点(如果转换麻木 – > pythonset真的非常快).

我个人只会因为我不需要从函数中返回它们并在函数内部对集合执行所有操作而且只有在nopython模式下支持集合上的所有操作时才使用numba for sets.在任何其他情况下,我不会在这里使用numba.

请注意:应该避免使用numpy import *,当你这样做时,你会隐藏几个python内置函数(sum,min,max,…),它会把很多东西放到你的全局变量中.最好使用import numpy作为np. np.在函数调用之前使代码更清晰,并且输入的内容不多.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值