【问题标题】:Efficient Python array with 100 million zeros?具有 1 亿个零的高效 Python 数组?
【发布时间】:2011-01-13 22:52:08
【问题描述】:

在 Python 中初始化和访问大型数组元素的有效方法是什么?

我想在 Python 中创建一个包含 1 亿个条目、无符号 4 字节整数、初始化为零的数组。我想要快速的数组访问,最好使用连续内存。

奇怪的是,NumPy 数组似乎执行得很慢。我可以尝试其他方法吗?

有 array.array 模块,但我没有看到一种方法可以有效地分配一个包含 1 亿个条目的块。

对 cme​​ts 的回应:

  • 我不能使用稀疏数组。这个算法太慢了,因为数组很快变得密集。
  • 我知道 Python 是解释型的,但肯定有办法进行快速数组操作吗?
  • 我进行了一些分析,我使用 NumPy 每秒获得​​大约 160K 数组访问(按索引查找或更新元素)。这似乎很慢。

【问题讨论】:

  • 你说的是几百兆字节的数组,在解释语言中......对你来说有多慢?
  • 你的数组会稀疏吗?最好只为您实际使用的条目分配内存。
  • 你可能想详细说明你想用它做什么。 “高效”本身没有任何意义。
  • 优化初始化可能会让你的算法戛然而止。现在谈论优化还为时过早。
  • 如果你在 Python 中使用循环来访问数组的元素,那可能是你的性能瓶颈。您应该尽量避免在 Python 中使用循环来访问 numpy 数组的元素。如果失败,您可以查看 Cython。

标签: python arrays performance


【解决方案1】:

如果

  • array.array 的访问速度对于您的应用来说是可以接受的
  • 紧凑的存储是最重要的
  • 您想使用标准模块(无 NumPy 依赖项)
  • 您在具有 /dev/zero 的平台上

您可能会对以下内容感兴趣。它初始化 array.array 比 array.array('L', [0]*size) 快大约 27 倍:

myarray = array.array('L')
f = open('/dev/zero', 'rb')
myarray.fromfile(f, size)
f.close()

在How to initialise an integer array.array object with zeros in Python 我正在寻找更好的方法。

【讨论】:

    【解决方案2】:

    除了其他优秀的解决方案之外,另一种方法是使用字典而不是数组(存在的元素非零,否则为零)。查找时间为 O(1)。

    您还可以检查您的应用程序是否驻留在 RAM 中,而不是换出。它只有 381 MB,但无论出于何种原因,系统都可能不会全部提供给您。

    但是也有一些非常快速的稀疏矩阵(SciPy 和ndsparse)。它们是在低级 C 中完成的,也可能很好。

    【讨论】:

    • 我不能使用字典,因为它会占用太多内存。
    【解决方案3】:

    NumPy 是用于大型、固定大小、同构数组的合适工具。在 Python 中访问任何东西的单个元素不会那么快,尽管整个数组操作通常可以以类似于 C 或 Fortran 的速度进行。如果您需要快速地对数百万个元素进行单独操作,那么您可以从 Python 中获得的只有这么多。

    您正在实施哪种算法?没试过怎么知道使用稀疏数组太慢? “高效”是什么意思?您想要快速初始化吗?那是你代码的瓶颈吗?

    【讨论】:

    • 我知道稀疏数组会太慢,因为数组变得密集很快。
    【解决方案4】:

    如果你不能向量化你的计算,Python/Numpy 会很慢。 Numpy 速度很快,因为矢量化计算发生在比 Python 更低的级别。核心 numpy 函数都是用 C 或 Fortran 编写的。因此sum(a) 不是一个有很多访问的python 循环,它是一个单一的低级C 调用。

    Numpy's Performance Python demo page 有一个很好的例子,有不同的选项。通过使用较低级别的编译语言 Cython 或在可行的情况下使用矢量化函数,您可以轻松获得 100 倍的增长。 This blog post 显示使用 Cython 的 numpy 用例增加了 43 倍。

    【讨论】:

      【解决方案5】:

      我会简单地创建您自己的数据类型,它不会初始化任何值。

      如果要读取尚未初始化的索引位置,则返回零。不过,不要初始化任何存储。

      如果要读取已初始化的索引位置,只需返回值即可。

      如果要写入尚未初始化的索引位置,请将其初始化并存储输入。

      【讨论】:

        【解决方案6】:

        提醒一下 Python 的整数是如何工作的:如果你通过说来分配一个列表

        a = [0] * K
        

        您需要用于列表 (sizeof(PyListObject) + K * sizeof(PyObject*)) 的内存和用于单个整数对象 0 的内存。只要列表中的数字保持在 Python 用于缓存的幻数 V 下方,就可以了,因为它们是共享的,即任何指向数字 n < V 的名称都指向完全相同的对象。您可以使用以下 sn -p 找到此值:

        >>> i = 0
        >>> j = 0
        >>> while i is j:
        ...    i += 1
        ...    j += 1
        >>> i # on my system!
        257 
        

        这意味着一旦计数超过这个数字,您需要的内存就是sizeof(PyListObject) + K * sizeof(PyObject*) + d * sizeof(PyIntObject),其中d < K 是V (== 256) 以上的整数个数。在 64 位系统上,sizeof(PyIntObject) == 24 和 sizeof(PyObject*) == 8,即最坏情况下的内存消耗为 3,200,000,000 字节。

        使用numpy.ndarray 或array.array,初始化后内存消耗是恒定的,但正如Thomas Wouters 所说,您需要为透明创建的包装对象付费。或许,您应该考虑使用Cython 或scipy.weave 将更新代码(访问并增加数组中的位置)转换为C 代码。

        【讨论】:

          【解决方案7】:

          为了快速创建,请使用数组模块。

          与普通列表相比,使用数组模块的创建速度大约快 5 倍,但访问元素的速度大约慢两倍:

          # Create array
          python -m timeit -s "from array import array" "a = array('I', '\x00'
           * 100000000)"
          10 loops, best of 3: 204 msec per loop
          
          # Access array
          python -m timeit -s "from array import array; a = array('I', '\x00'
          * 100000000)" "a[4975563]"
          10000000 loops, best of 3: 0.0902 usec per loop
          
          # Create list
          python -m timeit "a = [0] * 100000000"
          10 loops, best of 3: 949 msec per loop
          
          # Access list
          python -m timeit  -s "a = [0] * 100000000" "a[4975563]"
          10000000 loops, best of 3: 0.0417 usec per loop
          

          【讨论】:

            【解决方案8】:

            我做了一些分析,结果完全违反直觉。 对于简单的数组访问操作,numpy 和 array.array 比原生 Python 数组慢 10 倍。

            请注意,对于数组访问,我正在执行以下形式的操作:

            a[i] += 1
            

            个人资料:

            • [0] * 20000000

              • 访问:2.3M/秒
              • 初始化:0.8s
            • numpy.zeros(shape=(20000000,), dtype=numpy.int32)

              • 访问:160K/秒
              • 初始化:0.2s
            • array.array('L', [0] * 20000000)

              • 访问:175K/秒
              • 初始化:2.0s
            • array.array('L', (0 for i in range(20000000)))

              • 访问:175K/秒,大概基于另一个 array.array 的配置文件
              • 初始化:6.7s

            【讨论】:

            • 这是因为索引 Python 列表是一个非常快的操作:它只是获取内部数组中已经存在的对象。 array.array 和 numpy.array 对象不包含 Python 对象,因此存储在数组中的实际数据类型需要在访问时进行转换。这是内存使用量大大降低和实际连续数据块的代价。
            • @Joseph:+1 用于实际衡量它,而不是依赖互联网上一群匿名随机点击箭头的人的意见。 ;)
            • @Joseph:正如我在最初的回答中所说,如果不知道自己在做什么,很难说清楚。 numpy 有非常有效的矩阵运算,但如果你真的只是在做随机访问和增量,那么这些对你没有帮助。避免访问单个元素的“减速”的方法是不进行单个访问:)
            • 哦,我可能还应该指出,Python 整数列表不是 4 字节无符号整数的数组,它也不会是连续的。 (相反,它将是一个连续的指向散布在各处的 Python 对象的指针数组。)这是否足够好完全取决于您的实际用例。
            • 最快的数组初始化是array.array('L', [0]) * 20000000。见stackoverflow.com/a/3214343/448474
            【解决方案9】:

            您不可能找到比numpy 的array 更快的东西。数组本身的实现与它在 C 中的实现一样高效(并且与 array.array 基本相同,只是更有用。)

            如果您想加快代码速度,就必须这样做。即使数组被高效地实现,从 Python 代码中访问它也有一定的开销;例如,索引数组会产生整数对象,这些对象必须动态创建。 numpy 提供了许多在 C 中高效实现的操作,但如果没有看到实际代码表现不佳,就很难提出任何具体建议。

            【讨论】:

            • 看我的回答。事实证明,numpy 的访问速度非常慢。
            【解决方案10】:

            试试这个:

            x = [0] * 100000000
            

            在我的机器上执行只需几秒钟,访问几乎是即时的。

            【讨论】:

            • 这也是我能想到的最好方法。
            • 奇怪的是,这个方法的初始化和访问是最快的。
            • Ragnar Lodbrok 提出的方法
            猜你喜欢
            • 2021-03-10
            • 2010-10-06
            • 2015-12-12
            • 2017-03-26
            • 1970-01-01
            • 1970-01-01
            • 2013-12-28
            • 2016-11-06
            • 2022-12-02
            相关资源
            最近更新 更多