在Python列表中存储Python对象与固定长度的Numpy数组

时间:2012-06-27 18:20:40

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

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

具体做法是:

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

1 个答案:

答案 0 :(得分:17)

对于像这样的事情,不要在numpy中使用对象数组。

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

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

Numpy对象数组的内存开销略低于列表,但是如果你存储了很多单独的python对象,那么你将首先遇到其他内存问题。

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


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

不是将每个对象实例存储在numpy数组中,而是将数值数据存储在numpy数组中,如果每个行/列/需要单独的对象,则将索引存储到该数组中在每个例子中。

这样你就可以快速操作数值数组(即使用numpy而不是list comprehensions)。

作为我所谈论的一个简单例子,这里有一个简单的例子,没有使用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秒。