【问题标题】:Storing Python objects in a Python list vs. a fixed-length Numpy array将 Python 对象存储在 Python 列表中与固定长度的 Numpy 数组
【发布时间】:2012-06-29 06:17:33
【问题描述】:

在做一些生物信息学工作时,我一直在思考将对象实例存储在 Numpy 数组而不是 Python 列表中的后果,但在我所做的所有测试中,每个实例的性能都更差。我正在使用 CPython。有谁知道原因吗?

具体来说:

  • 与使用常规 Python 列表相比,使用固定长度数组 numpy.ndarray(dtype=object) 对性能有何影响?我执行的初始测试表明,访问 Numpy 数组元素比遍历 Python 列表要慢,尤其是在使用对象方法时。
  • 为什么使用[ X() for i in range(n) ] 等列表解析式实例化对象比使用numpy.empty(size=n, dtype=object) 更快?
  • 每个的内存开销是多少?我无法对此进行测试。我的课程广泛使用__slots__,如果这有任何影响的话。

【问题讨论】:

    标签: python performance numpy python-3.x cpython


    【解决方案1】:

    不要在 numpy 中使用对象数组来处理这样的事情。

    它们破坏了 numpy 数组的基本用途,虽然它们在少数情况下很有用,但它们几乎总是一个糟糕的选择。

    是的,在 python 中访问 numpy 数组的单个元素或在 python 中遍历 numpy 数组比使用list 的等效操作要慢。 (这就是为什么当 x 是一个 numpy 数组时,你永远不应该做类似 y = [item * 2 for item in x] 的事情。)

    Numpy 对象数组的内存开销比列表略低,但如果你要存储这么多单独的 Python 对象,你首先会遇到其他内存问题。

    Numpy 首先是一个内存高效的多维数组容器,用于统一数值数据。如果你想在一个 numpy 数组中保存任意对象,你可能需要一个列表。


    我的观点是,如果您想有效地使用 numpy,您可能需要重新考虑如何构建事物。

    不要将每个对象实例存储在一个 numpy 数组中,而是将您的 numerical 数据存储在一个 numpy 数组中,如果您需要为每行/列/任何内容单独的对象,请将索引存储到该数组中在每个实例中。

    通过这种方式,您可以快速对数值数组进行操作(即使用 numpy 而不是列表推导式)。

    作为我正在谈论的一个简单示例,这是一个不使用 numpy 的简单示例:

    from random import random
    
    class PointSet(object):
        def __init__(self, numpoints):
            self.points = [Point(random(), random()) for _ in xrange(numpoints)]
    
        def update(self):
            for point in self.points:
                point.x += random() - 0.5
                point.y += random() - 0.5
    
    class Point(object):
        def __init__(self, x, y):
            self.x = x
            self.y = y
    
    points = PointSet(100000)
    point = points.points[10]
    
    for _ in xrange(1000):
        points.update()
        print 'Position of one point out of 100000:', point.x, point.y
    

    还有一个使用 numpy 数组的类似示例:

    import numpy as np
    
    class PointSet(object):
        def __init__(self, numpoints):
            self.coords = np.random.random((numpoints, 2))
            self.points = [Point(i, self.coords) for i in xrange(numpoints)]
    
        def update(self):
            """Update along a random walk."""
            # The "+=" is crucial here... We have to update "coords" in-place, in
            # this case. 
            self.coords += np.random.random(self.coords.shape) - 0.5
    
    class Point(object):
        def __init__(self, i, coords):
            self.i = i
            self.coords = coords
    
        @property
        def x(self):
            return self.coords[self.i,0]
    
        @property
        def y(self):
            return self.coords[self.i,1]
    
    
    points = PointSet(100000)
    point = points.points[10]
    
    for _ in xrange(1000):
        points.update()
        print 'Position of one point out of 100000:', point.x, point.y
    

    还有其他方法可以做到这一点(例如,您可能希望避免在每个point 中存储对特定 numpy 数组的引用),但我希望这是一个有用的示例。

    注意它们运行的​​速度差异。在我的机器上,numpy 版本相差 5 秒,而纯 python 版本相差 60 秒。

    【讨论】: