【问题标题】:Object oriented vs vector based programming [closed]面向对象与基于矢量的编程[关闭]
【发布时间】:2013-06-04 11:43:50
【问题描述】:

我在面向对象和基于矢量的设计之间纠结。我喜欢物体赋予整个建筑的能力、结构和安全性。但与此同时,速度对我来说非常重要,在数组中包含简单的浮点变量确实有助于基于向量的语言/库,如 Matlab 或 Python 中的 numpy。

这是我写的一段代码来说明我的观点

问题:添加 Tow 波动率数字。如果 x 和 y 是两个波动率数字,则波动率之和为 (x^2 + y^2)^0.5(假设某些数学条件,但这在这里并不重要)。

我想非常快地执行此操作,同时我需要确保人们不会以错误的方式(x+y)添加波动率。这两点都很重要。

基于 OO 的设计是这样的:

from datetime import datetime 
from pandas import *

class Volatility:
    def __init__(self,value):
       self.value = value

    def __str__(self):
       return "Volatility: "+ str(self.value)

    def __add__(self,other):
        return Volatility(pow(self.value*self.value + other.value*other.value, 0.5))

(旁白:对于 Python 新手来说,add 只是一个覆盖 '+' 运算符的函数)

假设我添加了两个波动率值列表

n = 1000000
vs1 = Series(map(lambda x: Volatility(2*x-1.0), range(0,n)))
vs2 = Series(map(lambda x: Volatility(2*x+1.0), range(0,n))) 

(旁白:同样,Python 中的 Series 是一种带有索引的列表) 现在我想添加两个:

t1 = datetime.now()
vs3 = vs1 + vs2
t2 = datetime.now()
print t2-t1

仅在我的机器上运行了 3.8 秒的加法,我给出的结果根本不包括对象初始化时间,它只是已计时的加法代码。如果我使用 numpy 数组运行相同的东西:

nv1 = Series(map(lambda x: 2.0*x-1.0, range(0,n)))
nv2 = Series(map(lambda x: 2.0*x+1.0, range(0,n)))

t3 = datetime.now()
nv3 = numpy.sqrt((nv1*nv1+nv2*nv2))
t4 = datetime.now()
print t4-t3

运行时间为 0.03 秒。速度快了 100 多倍!

如您所见,OOP 方法给了我很多安全性,人们不会以错误的方式添加 Volatility,但向量方法实在是太快了!有没有一种我可以同时获得的设计?我相信你们中的很多人都遇到过类似的设计选择,你们是怎么解决的?

这里的语言选择无关紧要。我知道你们中的很多人会建议使用 C++ 或 Java,而且代码可能比基于向量的语言运行得更快。但这不是重点。我需要使用 Python,因为我有许多其他语言没有的库。那是我的约束。我需要在其中进行优化。

而且我知道,很多人会建议并行化,gpgpu 等。但我想首先最大化单核性能,然后我可以并行化两个版本的代码。

提前致谢!

【问题讨论】:

  • 那么,问题是什么?你能同时获得 OOP 和速度吗?不,这是一个权衡。
  • 您的代码不起作用--AttributeError: 'int' object has no attribute 'value' ...我不完全确定您的问题是什么。您是否考虑过拥有将 numpy ndarrays 保存为 .values 的 Volatility 对象?
  • 所以定义一个名为VolatilityVector(或其他)的类,它包含一个值向量,根据需要定义这些东西的加法。瞧?
  • 顺便说一句,map() 看起来真的很丑而且它比生成器表达式慢。如果速度是您关心的问题,您应该使用例如(2.0*x-1.0 for x in range(0,n)) 代替。此外,如果您迫切需要速度,就像在 CPython 中一样,您应该在 C 中编写和优化关键部分。
  • @Evpok: 1. 你是对的,但是那部分代码没有包含在时间计算中。如果你仔细看,它只是已经描述的附加部分(相当粗略)。 2. 你关于使用 CPython 的想法当然是正确的,但是如果我开始用 C 编写“关键部分”,我最终会编写 Python 自己提供的大部分复杂算法。正如我所提到的,我需要在 Python 中工作。

标签: python performance oop numpy vectorization


【解决方案1】:

对于这些情况,一个可能的解决方案是退后一步:您真的需要或想要将单个值表示为对象吗?如果您的对象是Volatile 的整个数组(或系列)怎么办?两全其美。

即使个别Volatile 对象有一些用途,您也可以实现flyweight pattern,其中Volatile 对象只是数组中位置的包装,所有方法都将在数组上操作。

【讨论】:

  • 感谢@Joni,如果我是正确的(我对设计模式的正式知识充其量是不稳定的),如果我有一些共享一些共同价值观的实例,享元模式将有所帮助,我不认为这是这里的情况。如果您考虑一下,Volatility 对象只是float,区别在于它在与另一个Volatility 实例相加时的行为方式 - 它不是一个简单的总和。所以它的行为是不同的。但是,如果我对模式的理解有误,请教育我。
  • 有几种方法可以查看享元模式。基本思想是,与其拥有几个不同的对象,这变得昂贵,不如拥有一个有限的池,由一个共享的紧凑状态支持。这是假设单个 Volatility 对象首先有意义:如果它们只是 float 值,则缺少 OOP 理论所需的 identity
【解决方案2】:

您可以在矢量化操作之上进行抽象、封装、代码重用等(通过 OOP 或其他方式)。您只需要选择正确的粒度:您的单元抽象(对象)应该是多个值,以匹配矢量化实现。据我所知,没有复数的波动率,但是对于您的特定用例,无论如何可能有一个更合适的术语(您的波动率值集是什么意思?)。

是的,这意味着性能问题会影响抽象和 API(但无论如何都会发生这种情况)。不,影响不是(必然)减少的抽象,甚至不是泄漏的抽象。它只是改变了抽象的形状。事实上,这甚至可以为其余代码提供更方便的 API,只要它一次只对所有值进行操作。

【讨论】:

  • 谢谢@delnan,这是个好主意。事实上,我在当前的设计中部分采用了这种方式,其中一些对象(如货币金额)以这种方式重新设计。但很快我意识到我的代码成为了那个特定数据结构的奴隶。例如如果我按照您的建议有VolatilitySeries,那么我不能有listtuple 或(假设您熟悉Python)波动性项目的DataFrame。这让我很困扰,因为这样我的架构就不能很好地扩展,而且一段时间后好处就消失了。这就是把我带到这里的原因:)
  • 另一个问题是没有什么能阻止任何人编写像volatilitySeries[0] + 3.0 这样的代码,这将是错误的。一旦你从VolatilitySeries 中抽出值,你就会发疯,所以安全只是短暂的。在人们并不总是知道所使用的确切类的多态环境中,这是很有可能的。而且您知道,您只能对用户进行如此多的教育。我知道你会这么说,嘿,如果我扭出Volatility.value,我也可以做同样的事情,但你知道,至少用户现在知道他正在使用一个特殊的值。
  • 有些人可能还建议在VolatilitySeries 中覆盖从Series 继承的所有常用函数,但这违背了整个目的。所以我从这条道路上学到的是,只有当单个单元格的类型是 Volatility 时,拥有一个 VolatilitySeries 对象才能真正发挥作用。
  • @RamanujLal 您似乎认为这样的VolatilitySeries 将继承自Series,因此包含Series 的接口。这没有必要,实际上可能是个坏主意。您可以使用组合而不是继承:VolatilitySeries 有一个 Series,但它不是 Series。您的另一个担忧是,您不能使用其他波动率值集合,这是真的——但我不确定这是否真的是一个问题,尤其是鸭子类型。如果性能不是问题,您可以按照@Joni 的建议进行操作。您甚至可以通过编程方式派生此类包装对象。
猜你喜欢
  • 1970-01-01
  • 2016-04-22
  • 2023-03-21
  • 2015-04-28
  • 1970-01-01
  • 1970-01-01
  • 2010-09-18
  • 2013-10-25
  • 1970-01-01
相关资源
最近更新 更多