【问题标题】:Is Try/Catch ever LESS expensive than a hash lookup?Try/Catch 是否比哈希查找更便宜?
【发布时间】:2012-11-21 06:24:02
【问题描述】:

我知道异常捕获可能会很昂贵,但我想知道是否存在实际上比查找更便宜的情况?

例如,如果我有一个大字典,我可以测试是否存在一个键:

If MyDictionary.ContainsKey(MyKey) Then _
  MyValue = MyDictionary(MyKey) ' This is 2 lookups just to get the value.

或者,我可以捕获一个异常:

Try
  MyValue = MyDictionary(MyKey) ' Only doing 1 lookup now.
Catch(e As Exception)
  ' Didn't find it.
End Try

异常捕获总是比上述查找更昂贵,还是在某些情况下更便宜?

【问题讨论】:

标签: vb.net exception-handling


【解决方案1】:

关于字典查找的事情是它们发生在恒定或接近恒定的时间。无论您的字典包含一个项目还是一百万个项目,您的计算机所花费的时间大致相同。我提出这个问题是因为您担心在大字典中进行两次查找,而现实情况是,这与在小型字典中进行两次查找并没有太大区别。作为旁注,这里的一个含义是字典并不总是小型集合的最佳选择,尽管我通常发现额外的清晰度仍然超过这些小型集合的任何性能问题。

决定字典查找速度的因素之一是为特定对象生成hash 值需要多长时间。有些对象可以比其他对象快得多。这意味着这里的答案取决于字典中的对象类型。因此,唯一确定的方法是构建一个版本,对每种方法进行数十万次测试,以找出哪个更快地完成集合。

这里要记住的另一个因素是,主要只是 Catch 块在处理异常时速度很慢,因此您需要寻找与您期望的合理匹配的查找命中和未命中的正确组合在生产中。出于这个原因,您无法在此处找到一般指南,或者如果您这样做,则很可能是错误的。如果您很少有遗漏,那么我希望异常处理程序做得更好(并且,由于遗漏有点,嗯,例外,它也是 正确的 解决方案) .如果你经常错过,我可能更喜欢不同的方法

当我们在做的时候,我们不要忘记Dictionary.TryGetValue()

【讨论】:

  • +1。我打算建议 TryGetValue - 你提到它的好东西。但即使这样,字典也能够每秒执行 90 亿次查找(我测试过一次),所以这通常不是 bootleneck。
  • @Neolisk 我想这取决于对象使用的散列函数以及字典中有多少对象和潜在的冲突。
  • @Magnus:当然,请参阅下面的综合测试。没有我在这里提到的那么快(我上次可能测试了整数作为键),但与TryCatch 方法相比仍然相当快。
  • @Joel 顺便说一句很好的答案
【解决方案2】:

如果您试图在某种不易搜索的数据结构中查找项目(例如,在 100K 项目的未索引字符串数组中找到包含单词“flabbergasted”的项目,那么是的,让它抛出异常会更快,因为您只进行一次查找。如果您首先检查该项目是否存在,然后获取该项目,您将进行两次查找。但是,在您的示例中,您正在查找在字典(哈希表)中查找一个项目,它应该非常快,因此进行两次查找可能比让它失败更快,但是如果不测试它就很难说。这完全取决于哈希值的速度有多快可以计算出对象以及列表中有多少项共享相同的哈希值。

正如其他人所建议的那样,在Dictionary 的情况下,TryGetValue 将提供两种方法中最好的方法。其他列表类型提供类似的功能。

【讨论】:

    【解决方案3】:

    我测试了ContainsKeyTryCatch 的性能,结果如下:

    附加调试器:

    没有附加调试器:

    仅使用Sub Main 及以下代码在控制台应用程序的发布版本上进行了测试。 ContainsKey 使用调试器的速度快了约 37000 倍,而在没有附加调试器的情况下仍然快了 355 倍,因此即使您进行两次查找,也不会像您需要捕获额外的异常那样糟糕。这是假设您经常寻找丢失的密钥。

    Dim dict As New Dictionary(Of String, Integer)
    With dict
      .Add("One", 1)
      .Add("Two", 2)
      .Add("Three", 3)
      .Add("Four", 4)
      .Add("Five", 5)
      .Add("Six", 6)
      .Add("Seven", 7)
      .Add("Eight", 8)
      .Add("Nine", 9)
      .Add("Ten", 10)
    End With
    
    Dim stw As New Stopwatch
    Dim iterationCount As Long = 0
    Do
      stw.Start()
      If Not dict.ContainsKey("non-existing key") Then 'always true
        stw.Stop()
        iterationCount += 1
      End If
      If stw.ElapsedMilliseconds > 5000 Then Exit Do
    Loop
    
    Dim stw2 As New Stopwatch
    Dim iterationCount2 As Long = 0
    Do
      Try
        stw2.Start()
        Dim value As Integer = dict("non-existing key") 'always throws exception
      Catch ex As Exception
        stw2.Stop()
        iterationCount2 += 1
      End Try
      If stw2.ElapsedMilliseconds > 5000 Then Exit Do
    Loop
    
    MsgBox("ContainsKey: " & iterationCount / 5 & " per second, TryCatch: " & iterationCount2 / 5 & " per second.")
    

    【讨论】:

    • 该测试是否在附加或不附加调试器的情况下运行?附加的调试器会大大增加异常处理的开销。此外,如果Dictionary 包含许多具有相同哈希码的不同值,则查找速度可能会非常慢。例如,定义一个简单的两字段结构,它不会覆盖 EqualsGetHashCode() [我认为 Tuple 确实会覆盖这些东西,所以创建一个自定义结构],并创建一个带有 10,000 个实例的 Dictionary该结构作为键,其中第一个字段在所有 10,000 个实例中保存相同的值。
    • 另外:它主要是进入一个异常处理缓慢的catch块。这里的基准测试每次都会这样做,但生产代码可能很少这样做。如果您的大多数查找都能够避免异常处理程序,您可能会得到完全不同的结果。
    • @supercat:使用调试器 - 我将很快包含另一组没有调试器的结果。总体而言,您是否想说异常处理将比使用 ContainsKey 进行实际生产使用更快?也许吧,但我还没有看到这样的例子。顺便说一句,您可以在答案中详细说明。
    • @JoelCoehoorn:我明白你的意思了,但同样,无法想象你宁愿使用TryCatch 而不是TryGetValue 的生产应用程序。如果您可以提供此类决定的真实世界(甚至是理论上的)示例,那将会很有趣。感谢您的反馈。
    • @Neolisk:使用TryGetValue 是不费吹灰之力。棘手的情况是,只有在集合不存在的情况下才尝试向集合中添加某些内容。 Add 方法在“项目存在”的情况下抛出异常;索引属性设置器默默地替换一个项目。如果一个人想要做除炸弹或替换物品以外的事情,则必须预先检查该物品的存在或尝试操作。该项目存在的情况是慢的。
    猜你喜欢
    • 2015-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-02-09
    • 2019-06-24
    • 1970-01-01
    • 2011-03-26
    • 1970-01-01
    相关资源
    最近更新 更多