【问题标题】:Try/catch or validation for speed?尝试/捕捉或验证速度?
【发布时间】:2011-08-01 03:57:01
【问题描述】:

我正在使用 Python,每当我必须验证函数输入时,我都会假设输入有效,然后发现错误。

就我而言,我有一个通用的Vector() 类,我用它来做一些不同的事情,其中​​之一就是加法。它既可以用作Color() 类,也可以用作Vector(),因此当我向Color() 添加标量时,它应该将该常量添加到每个单独的组件中。 Vector()Vector() 添加需要按组件添加。

此代码用于光线追踪器,因此任何速度提升都很棒。

这是我的Vector() 类的简化版本:

class Vector:
  def __init__(self, x, y, z):
    self.x = x
    self.y = y
    self.z = z

  def __add__(self, other):
    try:
      return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
    except AttributeError:
      return Vector(self.x + other, self.y + other, self.z + other)

我目前正在使用try...except 方法。有人知道更快的方法吗?


编辑:感谢答案,我尝试并测试了以下解决方案,该解决方案在添加 Vector() 对象之前专门检查类名:

class Vector:
  def __init__(self, x, y, z):
    self.x = x
    self.y = y
    self.z = z

  def __add__(self, other):
    if type(self) == type(other):
      return Vector(self.x + other.x, self.y + other.y, self.z + other.z)
    else:
      return Vector(self.x + other, self.y + other, self.z + other)

我使用timeit 对这两个代码块进行了速度测试,结果非常显着:

 1.0528049469 usec/pass for Try...Except
 0.732456922531 usec/pass for If...Else
 Ratio (first / second): 1.43736090753

我还没有测试Vector() 类与no 输入验证(即将签出类并进入实际代码),但我想它会更快比if...else 方法。


延迟更新:回顾这段代码,这不是最佳解决方案。

OOP 让这变得更快:

class Vector:
  def __init__(self, x, y, z):
    self.x = x
    self.y = y
    self.z = z

  def __add__(self, other):
    return Vector(self.x + other.x, self.y + other.y, self.z + other.z)

class Color(Vector):
  def __add__(self, other):
    if type(self) == type(other):
      return Color(self.x + other.x, self.y + other.y, self.z + other.z)
    else:
      return Color(self.x + other, self.y + other, self.z + other)

【问题讨论】:

  • 请使用timeit 并附上您的两个备选方案的真实计时结果。
  • 你知道这两个例子做不同的事情吗?由于它们不可比较,因此不清楚您测量的是什么。
  • 我会发布我的代码。不要以为我在终端窗口中手动输入了'a' 1,000,000 次;)
  • 您的if 验证不适用于像“-1”这样的负数
  • @Blender:请关注这个问题。您的 if 'a'.isdigit() 完全不相关且令人困惑。请删除它。如果您的“真正”问题是您的VectorCheck 课程,那么就证明这一点。另外,不要乱用花哨的“line-out”和其他标记。 SO 维护一个完整的变更日志。请只关注一件事。将你的真实问题打包成其他人可以学习的东西。

标签: python performance exception-handling typechecking


【解决方案1】:

我赞成 Matt Joiner 的回答,但想包括一些额外的观察结果,以明确指出,除了其他几个因素外,在预检查条件之间进行选择时,有 4 次很重要(称为 LBYL 或“跳前检查”)并仅处理异常(称为 EAFP 或“请求宽恕比许可更容易”)。

这些时间是:

  • 使用 LBYL 进行检查成功的时间
  • 使用 LBYL 检查失败的时间
  • EAFP抛出异常的时间
  • 使用 EAFP 抛出异常的时间

其他因素是:

  • 检查成功/失败或异常抛出/未抛出案例的典型比率
  • 是否存在阻止使用 LBYL 的竞争条件

最后一点是需要首先解决的问题:如果可能出现竞争条件,那么您别无选择,您必须使用异常处理。一个经典的例子是:

if <dir does not exist>:
    <create dir> # May still fail if another process creates the target dir

由于 LBYL 不排除这种情况的例外情况,它没有提供真正的好处,也不需要做出判断:EAFP 是唯一能正确处理竞争条件的方法。

但是,如果没有竞争条件,那么任何一种方法都可能是可行的。它们提供了不同的权衡:

  • 如果没有引发异常,则 EAFP 接近空闲
  • 但是,如果发生异常,则成本相对较高,因为在展开堆栈、创建异常并将其与异常处理子句进行比较时涉及大量处理
  • 相比之下,LBYL 会产生潜在的高固定成本:无论成功还是失败,始终都会执行额外检查

然后导致以下决策标准:

  • 是否已知这段代码对应用程序的速度至关重要?如果不是,那么不要担心两者中哪一个更快,而担心两者中哪一个更容易阅读。
  • 预检查是否比引发和捕获异常的成本更昂贵?如果是,那么 EAFP 总是更快,应该使用。
  • 如果答案是“否”,事情就会变得更有趣。在那种情况下,哪个更快将取决于成功或错误情况是否更常见,以及预检查和异常处理的相对速度。要明确回答这个问题,需要真正的时间测量。

作为一个粗略的经验法则:

  • 如果存在潜在的竞争条件,请使用 EAFP
  • 如果速度不是很重要,只需使用您认为更易于阅读的任何一个
  • 如果预检成本高,请使用 EAFP
  • 如果您希望操作在大多数情况下都能成功*,请使用 EAFP
  • 如果您预计操作失败的时间超过一半,请使用 LBYL
  • 如有疑问,请测量

*在这种情况下,人们对于他们认为“大部分时间”的内容会有所不同。对我来说,如果我期望操作成功超过一半的时间,我会理所当然地使用 EAFP,直到我有理由怀疑这段代码是一个实际的性能瓶颈。

【讨论】:

  • 哇,this 是我收到的最好的 SO 答案之一!我认为我一直过于依赖类来反复做繁重的工作,因为现在我想起来了,我只执行一次向量/标量加法,所以如果我要移动 all 在类之外进行类型检查并进入所需的代码区域,我可以基本上消除代码中所有与类相关的延迟。
  • @Blender:“将所有类型检查移出类”。普遍正确。不仅仅是为了性能,而是为了整体的简单性。类只是工作,他们不验证。
  • 嘿。我不太明白 S.Lott 的最后一点,即在课堂外移动类型检查。这如何删除代码行?正如我所设想的那样,您必须在使用该类的任何地方进行类型检查,而不是只在该类中进行一次。
  • @Jonathon:我相信他的观点是,除了明确的验证例程之外,通常最好遵循“Garbage In Garbage Out”规则,如果人们给你无意义的论点,就让 Python 自己的异常逃逸。 Python 非常擅长确保在这种情况下得到异常,而不是默默地产生垃圾数据 - 只有在(相对罕见的)后一种情况以及错误令人困惑的情况下,您需要将自己的验证嵌入到操作中.
  • 我最近对这个问题很好奇,所以做了一些实验。结果:gerg.ca/blog/post/2015/try-except-speed。简短版本:@ncoghlan 所说的,即只要错误很少发生,异常就会更便宜。
【解决方案2】:

在 Python 中,由于查找次数减少,异常通常更快。然而一位朋友曾经说过(它应该适用于任何语言),假装每次捕获到异常时,都会有一个小的延迟。避免使用可能造成延迟问题的异常。

在你给出的例子中,我会选择例外。

【讨论】:

  • 我正在使用光线追踪器;有时我得到0.0 的输出,而其他时候我得到Vector(1, 0, 1.5)。我需要知道类型,因为每种情况的处理方式都不同。所以你向try...except推荐我
  • 好吧,我将try 块更改为if 块,速度提高了两倍!谢谢!
猜你喜欢
  • 2020-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-23
  • 2013-08-04
  • 2014-12-27
  • 1970-01-01
相关资源
最近更新 更多