Python 将NumPy数组转换为集合需要的时间太长
我正在尝试执行以下命令Python 将NumPy数组转换为集合需要的时间太长,python,arrays,performance,numpy,set,Python,Arrays,Performance,Numpy,Set,我正在尝试执行以下命令 from numpy import * x = array([[3,2,3],[711,4,104],.........,[4,4,782,7845]]) # large nparray for item in x: set(item) 相比之下,这需要很长时间: x = array([[3,2,3],[711,4,104],.........,[4,4,782,7845]]) # large nparray for item in x: item.
from numpy import *
x = array([[3,2,3],[711,4,104],.........,[4,4,782,7845]]) # large nparray
for item in x:
set(item)
相比之下,这需要很长时间:
x = array([[3,2,3],[711,4,104],.........,[4,4,782,7845]]) # large nparray
for item in x:
item.tolist()
为什么将NumPy数组转换为集合
比转换为列表
需要更长的时间?
我的意思是基本上两者都有复杂性O(n)
TL;DR:set()
函数使用Pythons迭代协议创建一个集合。但是(在Python级别上)在NumPy数组上的迭代非常慢,因此在进行迭代之前使用tolist()
将数组转换为Python列表的速度(要快得多)
要理解为什么在NumPy数组上迭代如此之慢,了解Python对象、Python列表和NumPy数组如何存储在内存中是很重要的
Python对象需要一些簿记属性(如引用计数、指向其类的链接等)及其表示的值。例如,整数ten=10
可以如下所示:
蓝色圆圈是Python解释器中用于变量ten
的“名称”,下面的对象(实例)实际上代表整数(因为簿记属性在这里并不重要,我在图像中忽略了它们)
Pythonlist
只是Python对象的集合,例如mylist=[1,2,3]
将按如下方式保存:
这次列表引用了Python整数1
、2
和3
,而名称mylist
只引用了list
实例
但是数组myarray=np.array([1,2,3])
不将Python对象存储为元素:
值1
、2
和3
直接存储在NumPyarray
实例中
有了这些信息,我可以解释为什么在
数组上迭代比在列表上迭代要慢得多:
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)
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)
每次访问列表中的下一个元素时,列表只返回一个存储的对象。这非常快,因为元素已经作为Python对象存在(它只需要将引用计数增加1)
另一方面,当您需要数组的元素时,它需要为该值创建一个新的Python“box”,其中包含所有簿记内容,然后才能返回该值。迭代数组时,需要为数组中的每个元素创建一个Python框:
创建这些框的速度很慢,而且在NumPy数组上迭代比在Python集合(列表/元组/集合/字典)上迭代慢得多的主要原因是Python集合存储值及其框:
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)
比array.tolist()慢:
每种方法都有运行时复杂性,但常数因子是非常不同的
在您的例子中,您确实比较了set()
和tolist()
——这不是一个特别好的比较。将set(arr)
与list(arr)
或set(arr.tolist())
与arr.tolist()进行比较更有意义:
因此,如果您想要set
s,最好使用set(arr.tolist())
。对于更大的数组,使用它可能是有意义的,但因为您的行只包含3个可能会更慢的项(对于数千个元素,它可能会更快!)
在你询问的关于numba的评论中,是的,numba确实可以加快速度,但这并不意味着它总是更快
我不确定numba(重新)如何实现set
s,但由于它们是类型化的,因此它们可能也会避免使用“Python框”,并将值直接存储在set
中:
集合比list
s更复杂,因为它们涉及hash
es和空槽(Python对集合使用开放寻址,所以我假设numba也会)
与NumPyarray
一样,numbaset
直接保存值。因此,当您将NumPy数组
转换为numba集
(或反之亦然)时,根本不需要使用“Python框”,因此当您在numbanopython函数中创建集
时,它甚至比集(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())
方法的五倍。但需要强调的是,我没有从函数中返回集
s。当您返回一个集合
从nopython numba函数到Python时,numba将创建一个Python集合,包括为集合中的所有值“创建框”(这是numba隐藏的内容)
仅FYI:如果将list
s传递给Numba noython函数或这些函数的返回列表,则会发生相同的装箱/取消绑定。因此,Python中的O(1)
操作是使用Numba的O(n)
操作!这就是为什么通常最好将NumPy数组传递给numba nopython函数(即O(1)
)
我假设如果从函数中返回这些集(因为Nuba当前不支持集合列表,所以现在不可能),那么速度会慢一些(因为它创建了一个Nuba集,并且以后将其转换为python集),或者只会稍微快一些(如果转换numbaset->pythonset非常非常快)
就我个人而言,只有当我不需要从函数返回集合并在集合内部执行所有操作时,我才会对集合使用numba
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()