【问题标题】:Fastest way to make a list-like object containing integers in python在python中制作包含整数的类列表对象的最快方法
【发布时间】:2018-10-02 01:21:32
【问题描述】:

在 python 中创建包含整数/浮点数(非常简单的数据类型)的类列表对象的最快方法是什么?

“类似列表”是什么意思?

这意味着我想要一个支持列表的两个(非常)基本操作的对象:获取某个索引中的对象 (1) 并更改其值 (2)。

在发布此之前我遇到了哪些帖子,为什么他们没有解决我的问题?

我遇到了这两个:[1][2]

他们没有解决我的问题,因为他们的所有解决方案都太慢了:在我的 PC 中 array.array('i',(0,)*10 ** 8) 导致错误(笑); [0 for _ in range(10**8)] 花了大约 15 秒(哇!); [0] * 10 ** 8 耗时 2.3 秒; [None] * 10 ** 8 耗时 1.8 秒; (1.8 秒可能会更快...)

我做了什么?

我尝试使用ctypes 模块

from ctypes import c_int
array = (c_int * 10 ** 8)()

上面的代码只用了 0.7 秒……但是有没有办法让它更快?除了速度快之外,它还有一些缺点:

  1. 由于它使用 c/c++ 变量的骨架,因此其中的整数将处于“不像 python 那样无限”的整数值范围内
  2. 列表中不能有多个数据类型
  3. 您必须导入一个模块才能使用它

真的可以按照我的要求去做吗?有没有比使用ctypes 模块更快的方法?如果是这样,请确保您使用的是“内置”/“预安装”模块。

编辑:

为什么我不能简单地安装一些模块,比如 numpy?

我正在使用 python 进行竞争性编程,大多数解释器/评委不允许使用外部库。

我们可以用array.array 存储自定义对象吗?

我可以看到很多答案都使用了array 模块的array 函数。他们都使用'i'来指定我们要存储整数。是否可以创建一个类并创建一个包含它的“array.array”?例如:

class Point:
 def __init__(self, x, y):
  self.x = x
  self.y = y

# make array.array object with all indexes containing a Point with atributes x and y with value 0
# an example with a list of what I want to do is this:
# l = [Point(0, 0) for _ in range(10**3)]

【问题讨论】:

  • 你考虑过numpy吗?如果您谈论的是大型数值计算的性能,则不必担心导入模块。见Why NumPy instead of Python lists?
  • 你也有bytearray(10**8)。值的范围有限(0-255),但速度非常快

标签: python list


【解决方案1】:

我只会使用支持快速数组操作的numpy 模块。

例如制作一个编号为 0 到 10**8 的数组:

import numpy as np
import time

b = time.time()
a = np.linspace(0, 10**8, 10**8)
c = time.time()
print(c-b)
>>>0.5000154972076416

或者制作一个长度为 10**8 的 0 数组:

b = time.time()
a = np.zeros(shape=(10**8,))
c = time.time()
print(c-b)
>>>0.0

numpy 这么快的主要原因是它是用 C 实现的。

编辑: 如果您只想使用预安装的软件包,可以尝试使用array 软件包:

import array
import time
r = time.time()
a = array.array('i', [0]) * (10**8)
print(time.time()-r)
>>>0.15627217292785645

【讨论】:

    【解决方案2】:

    我想说你可以尝试不同的方法:

    1)numpy。它确实是数组的标准。它伴随着每个操作跨越 Python C 边界的成本,但这真的取决于你的任务。

    x = numpy.array(10 ** 8)
    
    timeit.timeit('x = numpy.array(10 ** 8)', 'import numpy', number=1)
    4.195800283923745e-05
    

    2) 延迟初始化(如 JavaScript 数组)。

    class LazyArray:
        def __init__(self, size):
            self.storage = {}
            self.size = size
    
        def check(self, i):
            if i < 0 or i >= self.size:
                raise RuntimeError() 
    
        def __getitem__(self, i):
            self.check(i)
            return self.storage.get(i, 0)
    
        def __setitem__(self, i, value):
            self.check(i)
            self.storage[i] = value
    
    x = LazyArray(10 ** 8)
    x[10]
    >> 0
    x[10] = 5
    x[10]
    >> 0
    

    【讨论】:

    • 使用字典可能很慢......插入和获取索引操作可能需要 O(1),但它们也可能需要 O(n)(最坏情况)
    • 是的,延迟初始化有它自己的代价。你可以尝试混合。就像手动将间隔拆分为 [0 .. sqrt(size)] 字典列表。
    【解决方案3】:

    array.array('i',(0,) * 10**8) 导致错误(大声笑)

    你没有指定你得到了什么错误 - 这对我有用,虽然它不是很快,因为它构建了一个中间元组并立即丢弃它。使用 Python 的内置类型,array.array 可能会产生最佳性能,前提是您避免使用元组:

    a = array.array('i', (0,)) * 10**8
    

    上面的代码只用了 0.7 秒……但是有没有办法让它更快?

    如果您不被允许创建或导入 C 扩展,那将很难击败 array.array。在我几年前的机器上,以上需要 0.6 秒。您可以通过增加初始数组的大小来进一步优化它。例如,这会产生相同的结果,但速度几乎快 3 倍 (!):

    # 0.22 s
    a = array.array('i', (0,) * 10) * 10**7
    

    在我的机器上,以下版本效果最好:

    # 0.19 s
    a = array.array('i', (0,) * 100) * 10**6
    

    进一步增加初始数组大小无济于事,并且很快就会开始降低性能。

    为了提高效率,请考虑替代方法,例如惰性列表或为您的用例量身定制的完全不同的数据结构。考虑到竞争的背景,这可能就是真正想要的。

    但请注意,每种解决方案都会有不同的权衡取舍。例如,@KonstantinNikitin 提供的惰性数组将非常有效地构造,但其在纯 Python 中实现的 __getitem____setitem__ 将比 list 或 array.array 慢几个数量级.哪个更适合您归结为您的程序中哪些操作更频繁,这取决于您自己找出答案。

    【讨论】:

    • 我没有指定错误,因为当我在我的机器上运行它时它崩溃了,我不得不重新启动它大声笑......但你的解决方案是迄今为止最好的一个!
    • 我真的很喜欢你array.array 的想法!在字节数组上方进行一些包装也可能很有用,例如,从 [4 * i .. 4 * i + 4) 访问项目 i 值。 >>> timeit.timeit('x = bytearray(10 ** 8)', '', number=1) 0.0671752679918427 >>> timeit.timeit('x = array.array("i", (0,) * 100) * 10 ** 6', '导入数组', number=1) 0.30561141700309236
    • @ArthurQ 哎哟。虽然按 Python 标准来说 1 亿个元素的元组相当大,但它还不足以导致崩溃,尤其是因为它只包含对同一 0 对象的引用。
    • @KonstantinNikitin array.array 可能是最被低估的 Python 标准库模块,甚至在它被 numpy 淘汰之前也是如此。有趣的是,在 PyPy 中,array.array('i', (0,)) * 10**8 是瞬时的,因为 PyPy 可以识别和特殊情况下由全零组成的数组的构造。
    • 顺便问一下……你也可以使用这个模块来制作一个自定义对象数组吗?例如:制作一个点数组(假设您自己编写了“Point”类并且它具有“x”和“y”属性)
    【解决方案4】:

    如果你真的只想要这两个属性:

    获取某个索引中的对象 (1) 并更改其值 (2)

    那么你可以使用collections.defaultdict:

    import collections
    my_list = collections.defaultdict(lambda: 0)
    

    相当快(~0.4 μs):

    $ python3 -m timeit -s 'import collections' 'collections.defaultdict(lambda: 0)' 
    1000000 loops, best of 3: 0.417 usec per loop
    

    但是,实际使用它可能会比其他答案中建议的任何类型慢很多。

    【讨论】:

    • 我喜欢这种开箱即用的方法。
    • defaultdict(int) 是一种更有效的方法。
    【解决方案5】:

    对于只需要 0 到 255 之间的整数的情况,bytearray 对象的创建速度非常快:

    >>> timeit.timeit('bytearray(100000)', number=1000)
    0.005567271093696036
    >>> timeit.timeit('array.array("B", [0])*100000', 'import array', number=1000)
    0.36631167401839093
    >>> timeit.timeit('array.array("i", [0])*100000', 'import array', number=1000)
    0.56494557472422
    

    array.array 不同,这会直接将分配归零,而不是从用零初始化的对象复制。

    【讨论】:

      猜你喜欢
      • 2021-09-14
      • 2019-04-11
      • 1970-01-01
      • 2018-12-09
      • 2020-03-04
      • 1970-01-01
      • 1970-01-01
      • 2020-03-30
      • 2013-11-09
      相关资源
      最近更新 更多