对象列表或并行属性数组?

时间:2010-04-27 18:13:14

标签: python performance data-structures numpy

问题是,基本上:在性能方面和设计方面更优选的是拥有Python类的对象列表或具有多个数值属性列表?

我正在编写某种科学模拟,它涉及一个相当大的相互作用粒子系统。为简单起见,假设我们有一组球在盒子内弹跳,因此每个球都有许多数值属性,如x-y-z坐标,直径,质量,速度矢量等。如何更好地存储系统?我能想到的两个主要选择是:

使用这些属性和一些方法创建一个“Ball”类,然后存储该类的对象列表,例如: G。 [b1,b2,b3,... bn,...],其中每个bn我们可以访问bn.x,bn.y,bn.mass等;

为每个属性创建一个数字数组,然后对于每个第i个“球”,我们可以访问它的'x'坐标作为xs [i],'y'坐标为ys [i],'mass'为群众[i]等等;

对我来说,似乎第一个选项代表了更好的设计。第二个选项看起来有些丑陋,但在性能方面可能更好,并且可以更容易地使用numpy和scipy,我尽可能多地使用它。

我仍然不确定Python是否足够快,因此在Python中进行初始原型设计后,可能需要用C ++或其他东西重写它。对于C / C ++,数据表示的选择是否会有所不同?混合方法怎么样,例如Python与C ++扩展?

更新:我从未预料到并行数组本身会有任何性能提升,但在Python + Numpy(或任何使用SlowScriptingLanguage + FastNativeLibrary)的混合环境中使用它们可能(或可能不会)让你将更多的工作从慢速脚本代码转移到快速的本机库中。

4 个答案:

答案 0 :(得分:3)

在这个例子中为每个球设置一个对象肯定是更好的设计。并行数组实际上是一种不支持正确对象的语言的解决方法。我不会在具有OO功能的语言中使用它们,除非它是一个适合函数的小案例(甚至可能不是那样)或者我已经用完所有其他优化选项并且分析器显示属性访问是罪魁祸首。这适用于Python和C ++的两倍,因为前者非常强调可读性和优雅。

答案 1 :(得分:2)

我同意并行数组几乎总是一个坏主意,但不要忘记,当你设置东西时,你可以将视图用于一个numpy数组,但是......(是的,我知道这是有效的使用并行数组,但我认为这是这种情况下的最佳选择......)

如果您事先知道要创建的“球”的数量,这很好,因为您可以为坐标分配一个数组,并将视图存储到每个球对象的数组中。

你必须要小心谨慎地在coords数组上进行操作,但它可以更快,更快地更新许多“球”的坐标。

例如......

import numpy as np

class Ball(object):
    def __init__(self, coords):
        self.coords = coords

    def _set_coord(self, i, value):
        self.coords[i] = value
    x = property(lambda self: self.coords[0],
            lambda self, value: self._set_coord(0, value))
    y = property(lambda self: self.coords[1],
            lambda self, value: self._set_coord(1, value))

    def move(self, dx, dy):
        self.x += dx
        self.y += dy

def main():
    n_balls = 10
    n_dims = 2
    coords = np.zeros((n_balls, n_dims))
    balls = [Ball(coords[i,:]) for i in range(n_balls)]

    # Just to illustrate that that the coords are updating
    ball = balls[0]

    # Random walk by updating coords array
    print 'Moving all the balls randomly by updating coords'
    for step in xrange(5):
        # Add a random value to all coordinates
        coords += 0.5 - np.random.random((n_balls, n_dims))

        # Display the coords for a particular ball and the 
        # corresponding row of the coords array
        print '    Value of ball.x, ball.y:', ball.x, ball.y
        print '    Value of coords[0,:]:', coords[0,:]

    # Move an individual ball object
    print 'Moving a ball individually through Ball.move()'
    ball.move(0.5, 0.5)
    print '    Value of ball.x, ball.y:', ball.x, ball.y
    print '    Value of coords[0,:]:', coords[0,:]

main()

只是为了说明,输出如下:

Moving all the balls randomly by updating coords
    Value of ball.x, ball.y: -0.125713650677 0.301692195466
    Value of coords[0,:]: [-0.12571365  0.3016922 ]
    Value of ball.x, ball.y: -0.304516863495 -0.0447543559805
    Value of coords[0,:]: [-0.30451686 -0.04475436]
    Value of ball.x, ball.y: -0.171589457954 0.334844443821
    Value of coords[0,:]: [-0.17158946  0.33484444]
    Value of ball.x, ball.y: -0.0452864552743 -0.0297552313656
    Value of coords[0,:]: [-0.04528646 -0.02975523]
    Value of ball.x, ball.y: -0.163829876915 0.0153203173857
    Value of coords[0,:]: [-0.16382988  0.01532032]
Moving a ball individually through Ball.move()
    Value of ball.x, ball.y: 0.336170123085 0.515320317386
    Value of coords[0,:]: [ 0.33617012  0.51532032]

这里的优点是更新单个numpy数组比遍历所有球对象要快得多,但是你保留了更加面向对象的方法。

无论如何,只是我的想法..

编辑:要想知道速度差异,有1,000,000个球:

In [104]: %timeit coords[:,0] += 1.0
100 loops, best of 3: 11.8 ms per loop

In [105]: %timeit [item.x + 1.0 for item in balls]
1 loops, best of 3: 1.69 s per loop

因此,当使用大量球时,使用numpy直接更新坐标大约快2个数量级。 (使用10个球时差异较小,根据示例,大约是2倍,而不是150倍)

答案 2 :(得分:1)

我认为这取决于你将要对它们做什么,以及你将要使用的频率(一个粒子的所有属性)与(所有粒子的一个属性)。前者更适合于对象方法;后者更适合阵列方法。

几年前我遇到了类似的问题(虽然在不同的领域)。在实际实现这个阶段之前,该项目已被优先考虑,但我倾向于采用混合方法,除了Ball类之外,我还有一个Ensemble类。 Ensemble不是Balls的列表或其他简单容器,但是它有自己的属性(可以是数组)和它自己的方法。 Ensemble是从Balls创建的,还是Ensemble的Balls,取决于你将如何构建它们。

我的一个同事正在争论一个解决方案,其中基本对象是一个可能只包含一个Ball的Ensemble,因此任何调用代码都不需要知道你是否只使用一个Ball(你做过那个吗?)适合您的应用?)或许多。

答案 3 :(得分:0)

你是否会在球之间产生任何力(硬球/碰撞,重力,电磁)?我猜是这样的。想要使用Barnes-Hut simulation想法,你会有足够多的球吗?如果是这样,那么你绝对应该使用Ball类的想法,这样你就可以轻松地将它们存储在八叉树或其他类似的东西中。此外,使用Barnes-Hut模拟将模拟的复杂性从O(N ^ 2)降低到O(N log N)。

实际上,如果你没有在球之间有力或没有使用很多球,你不需要使用并行阵列可能获得的速度增加,并且也应该遵循Ball类的想法。