为什么检查字典是否包含密钥更快,而不是在不包含异常的情况下捕获异常?

Pet*_*etr 234 c# performance dictionary

想象一下代码:

public class obj
{
    // elided
}

public static Dictionary<string, obj> dict = new Dictionary<string, obj>();
Run Code Online (Sandbox Code Playgroud)

方法1

public static obj FromDict1(string name)
{
    if (dict.ContainsKey(name))
    {
        return dict[name];
    }
    return null;
}
Run Code Online (Sandbox Code Playgroud)

方法2

public static obj FromDict2(string name)
{
    try
    {
        return dict[name];
    }
    catch (KeyNotFoundException)
    {
        return null;
    }
}
Run Code Online (Sandbox Code Playgroud)

我很好奇这两个函数的性能是否存在差异,因为第一个函数应该比第二个函数更低 - 假设它需要在字典包含值时检查两次,而第二个函数确实只需要访问字典曾经但是WOW,它实际上是相反的:

循环1 000 000个值(现有10万个,不存在90 000个):

第一个功能:306毫秒

第二功能:20483毫秒

这是为什么?

编辑:你可以在下面这个问题的评论中注意到,如果有0个非现有密钥,第二个函数的性能实际上略好于第一个函数.但是,一旦存在至少一个或多个非现有密钥,则第二个密钥的性能会迅速下降.

Dan*_*rth 402

一方面,抛出异常本质上是昂贵的,因为堆栈必须被展开等.
另一方面,通过其密钥访问字典中的值是便宜的,因为它是一个快速的O(1)操作.

顺便说一句:正确的方法是使用 TryGetValue

obj item;
if(!dict.TryGetValue(name, out item))
    return null;
return item;
Run Code Online (Sandbox Code Playgroud)

这只访问字典一次而不是两次.
如果您确实想要null在密钥不存在时返回,则可以进一步简化上述代码:

obj item;
dict.TryGetValue(name, out item);
return item;
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为如果没有key 存在则TryGetValue设置item为.nullname

  • @Petr:是的,它并不重要,因为访问字典非常快,如果你这样做一次或两次并不重要.这些250毫秒中的大部分最有可能用于测试循环本身. (52认同)
  • @LarsH还要注意,在尝试访问文件(或其他一些外部资源)时,它可能会在检查和实际访问尝试之间更改状态.在这些情况下,使用异常是正确的方法.参见[斯蒂芬C'S回答这个问题(http://stackoverflow.com/questions/6092992/why-is-it-easier-to-ask-forgiveness-than-permission-in-python-but-not-in- java)了解更多信息. (8认同)
  • 我根据答案更新了我的测试,并且出于某种原因,尽管建议的功能IS更快,但实际上并不是非常重要:264 ms原始,258ms建议一个 (4认同)
  • 这很有用,因为有时人们会得到这样的印象:异常抛出是处理不存在的文件或空指针等情况的更好或更清晰的方式,无论这些情况是否常见,并且不考虑性能成本. (4认同)
  • @LarsH它还取决于你在做什么.虽然像这样的简单微基准测试显示,一旦你的循环开始包括文件或数据库活动,异常会对异常造成很大的惩罚,在每次迭代时抛出异常对于性能而言都很重要.比较第1和第2表:http://www.codeproject.com/Articles/11265/Performance-implications-of-Exceptions-in-NET (4认同)

小智 6

字典专门用于执行超快速键查找.它们作为哈希表实现,条目越多,它们相对于其他方法就越快.使用异常引擎只应该在您的方法无法完成您设计的操作时完成,因为它是一组很大的对象,为您提供了许多处理错误的功能.我构建了一个完整的库类,其中包含try catch块所包围的所有内容,并且看到调试输出包含600多个异常中的每一个的单独行!