草庐IT

python - 将 NumPy 数组转换为集合花费的时间太长

coder 2023-08-14 原文

我正在尝试执行以下操作

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 数组转换为 set 需要更长的时间比到 list ?
我的意思是基本上两者都有复杂性 O(n) ?

最佳答案

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

要理解为什么迭代 NumPy 数组如此缓慢,重要的是要知道 Python 对象、Python 列表和 NumPy 数组是如何存储在内存中的。

Python 对象需要一些簿记属性(如引用计数、指向其类的链接等)以及它所代表的值。例如整数 ten = 10看起来像这样:



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

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



这次列表引用了 Python 整数 1 , 23和名称 mylist仅引用 list实例。

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



1 , 23直接存储在 NumPy array实例。

有了这些信息,我就可以解释为什么要迭代 arraylist 上的迭代相比要慢得多:

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

另一方面,当您想要 array 的元素时在返回之前,它需要为包含所有簿记内容的值创建一个新的 Python“框”。当您遍历数组时,它需要为数组中的每个元素创建一个 Python 框:



创建这些框很慢,主要原因是迭代 NumPy 数组比迭代存储值的 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)
set “构造函数”只是对对象进行迭代。

我无法明确回答的一件事是为什么 tolist方法要快得多。最后,生成的 Python 列表中的每个值都需要位于“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 s 但因为它们是输入的,所以它们很可能也避免了“Python 框”并将值直接存储在 set 中。 :



集合比 list 更复杂s 因为它们涉及 hash es 和空槽(Python 对集合使用开放寻址,所以我假设 numba 也会)。

像 NumPy array numba set直接保存值。所以当你转换一个 NumPy array到 NumPy set (或反之亦然)它根本不需要使用“Python 盒子”,所以当你创建 set 时s in a 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()) 快五倍方法。但重要的是要强调我没有返回 set s 从函数。当您返回 set从 nopython numba 函数到 Python Numba 创建了一个 python 集合——包括为集合中的所有值“创建盒子”(这是 numba 隐藏的东西)。

仅供引用:如果你通过 list,同样的装箱/拆箱会发生s 到 Numba nopython 函数或从这些函数返回列表。那么什么是 O(1) Python 中的操作是 O(n)与 Numba 一起操作!这就是为什么通常最好将 NumPy 数组传递给 numba nopython 函数(即 O(1))。



我假设如果您 返回这些集合 从函数(现在不太可能,因为 numba 目前不支持集合列表)它会更慢(因为它创建了一个 numba 集 后来将它转换为 python 集)或者只是稍微快一点(如果转换 numbaset -> pythonset 真的非常快)。

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

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

关于python - 将 NumPy 数组转换为集合花费的时间太长,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44224696/

有关python - 将 NumPy 数组转换为集合花费的时间太长的更多相关文章

  1. ruby-on-rails - 在 Rails 中将文件大小字符串转换为等效千字节 - 2

    我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,

  2. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  3. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

  4. ruby-on-rails - 在 Ruby 中循环遍历多个数组 - 2

    我有多个ActiveRecord子类Item的实例数组,我需要根据最早的事件循环打印。在这种情况下,我需要打印付款和维护日期,如下所示:ItemAmaintenancerequiredin5daysItemBpaymentrequiredin6daysItemApaymentrequiredin7daysItemBmaintenancerequiredin8days我目前有两个查询,用于查找maintenance和payment项目(非排他性查询),并输出如下内容:paymentrequiredin...maintenancerequiredin...有什么方法可以改善上述(丑陋的)代

  5. ruby - 多次弹出/移动 ruby​​ 数组 - 2

    我的代码目前看起来像这样numbers=[1,2,3,4,5]defpop_threepop=[]3.times{pop有没有办法在一行中完成pop_three方法中的内容?我基本上想做类似numbers.slice(0,3)的事情,但要删除切片中的数组项。嗯...嗯,我想我刚刚意识到我可以试试slice! 最佳答案 是numbers.pop(3)或者numbers.shift(3)如果你想要另一边。 关于ruby-多次弹出/移动ruby​​数组,我们在StackOverflow上找到一

  6. ruby - 将数组的内容转换为 int - 2

    我需要读入一个包含数字列表的文件。此代码读取文件并将其放入二维数组中。现在我需要获取数组中所有数字的平均值,但我需要将数组的内容更改为int。有什么想法可以将to_i方法放在哪里吗?ClassTerraindefinitializefile_name@input=IO.readlines(file_name)#readinfile@size=@input[0].to_i@land=[@size]x=1whilex 最佳答案 只需将数组映射为整数:@land边注如果你想得到一条线的平均值,你可以这样做:values=@input[x]

  7. ruby - 将散列转换为嵌套散列 - 2

    这道题是thisquestion的逆题.给定一个散列,每个键都有一个数组,例如{[:a,:b,:c]=>1,[:a,:b,:d]=>2,[:a,:e]=>3,[:f]=>4,}将其转换为嵌套哈希的最佳方法是什么{:a=>{:b=>{:c=>1,:d=>2},:e=>3,},:f=>4,} 最佳答案 这是一个迭代的解决方案,递归的解决方案留给读者作为练习:defconvert(h={})ret={}h.eachdo|k,v|node=retk[0..-2].each{|x|node[x]||={};node=node[x]}node[

  8. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  9. ruby - 检查数组是否在增加 - 2

    这个问题在这里已经有了答案:Checktoseeifanarrayisalreadysorted?(8个答案)关闭9年前。我只是想知道是否有办法检查数组是否在增加?这是我的解决方案,但我正在寻找更漂亮的方法:n=-1@arr.flatten.each{|e|returnfalseife

  10. ruby - 如果指定键的值在数组中相同,如何合并哈希 - 2

    我有一个这样的哈希数组:[{:foo=>2,:date=>Sat,01Sep2014},{:foo2=>2,:date=>Sat,02Sep2014},{:foo3=>3,:date=>Sat,01Sep2014},{:foo4=>4,:date=>Sat,03Sep2014},{:foo5=>5,:date=>Sat,02Sep2014}]如果:date相同,我想合并哈希值。我对上面数组的期望是:[{:foo=>2,:foo3=>3,:date=>Sat,01Sep2014},{:foo2=>2,:foo5=>5:date=>Sat,02Sep2014},{:foo4=>4,:dat

随机推荐