在Python中将Python对象存储在列表(list)和固定长度的Numpy数组(array)中的区别

12

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

具体而言:

  • 使用固定长度数组numpy.ndarray(dtype=object)与常规Python列表相比,会有什么绩效影响? 我执行的初始测试表明,访问Numpy数组元素比迭代Python列表慢,特别是在使用对象方法时.
  • 为什么使用列表推导式(如[ X() for i in range(n) ])来实例化对象比numpy.empty(size=n, dtype=object)更快?
  • 它们每个的内存开销是多少?我无法进行测试。如果有任何影响,我的类将广泛使用__slots__
1个回答

22
不要在NumPy中使用对象数组进行这样的操作。它们违背了NumPy数组的基本目的,并且虽然它们在极少数情况下很有用,但它们几乎总是一个糟糕的选择。
是的,在Python中访问NumPy数组的单个元素或迭代NumPy数组比使用列表进行等效操作要慢。(这就是为什么当x是NumPy数组时,不应该像这样做 y = [item * 2 for item in x]。)
NumPy对象数组的内存开销略低于列表,但是如果您要存储大量单独的Python对象,您将首先遇到其他内存问题。
NumPy首先和主要是用于存储一致的数值数据的内存高效的多维数组容器。如果您想在NumPy数组中保存任意类型的对象,则可能更适合使用列表。
我的观点是,如果您想有效地使用NumPy,您可能需要重新考虑您如何构建数据结构。
相反,您可以将您的数值数据存储在一个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版本与纯Python版本相比,差异为5秒和60秒。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接