【问题标题】:trying to optimize if/else condition slows down program尝试优化 if/else 条件会减慢程序的速度
【发布时间】:2013-06-19 23:00:11
【问题描述】:

我目前正在尝试借助 VS-Profiling 工具优化 .net 应用程序。

一个经常被调用的函数包含以下代码:

if (someObjectContext.someObjectSet.Where(i => i.PNT_ATT_ID == tmp_ATT_ID).OrderByDescending(i => i.Position).Select(i => i.Position).Count() == 0)
{
    lastPosition = 0;
}
else
{
    lastPosition = someObjectContext.someObjectSet.Where(i => i.PNT_ATT_ID == tmp_ATT_ID).OrderByDescending(i => i.Position).Select(i => i.Position).Cast<int>().First();
}

我改成这样了:

var relevantEntities = someObjectContext.someObjectSet.Where(i => i.PNT_ATT_ID == tmp_ATT_ID).OrderByDescending(i => i.Position).Select(i => i.Position);
if (relevantEntities.Count() == 0)
{
    lastPosition = 0;
}
else
{
    lastPosition = relevantEntities.Cast<int>().First();
}

我希望该更改能稍微加快该方法的速度,因为我不确定编译器是否会注意到查询已执行两次并缓存结果。

令我惊讶的是,该方法的执行时间(隐含采样数)并未减少,甚至增加了 9%(根据分析器)

有人可以解释为什么会这样吗?

【问题讨论】:

  • 无论性能如何,根据 DRY 规则,您的第二个示例编写得更好,代码更易于维护。
  • 比较各个版本生成的IL。

标签: c# optimization compiler-construction


【解决方案1】:

我希望该更改能够稍微加快该方法的速度,因为我不确定编译器是否会注意到查询已执行两次并缓存结果。

不会的。事实上它不能。数据库可能不会为这两个查询返回相同的结果。在第一个查询之后和第二个查询之前添加或删除结果是完全可能的。 (使此代码不仅效率低下,而且如果发生这种情况可能会损坏。)由于您完全有可能想要执行两个查询,知道结果可能不同,因此结果很重要的查询被重复使用。

这里的重点是延迟执行的想法。 relevantEntities 不是查询的结果,而是查询本身。直到IQueryable被迭代(通过CountFirstforeach循环等方法)才会查询数据库,并且每次迭代查询时它都会执行另一个查询数据库。

在你的情况下,你可以这样做:

var lastPosition = someObjectContext.someObjectSet
    .Where(i => i.PNT_ATT_ID == tmp_ATT_ID)
    .OrderByDescending(i => i.Position)
    .Select(i => i.Position)
    .Cast<int>()
    .FirstOrDefault();

这利用了 int 的默认值为 0 的事实,这是您在之前不匹配的情况下设置的值。

请注意,这是一个与您的功能相同的查询,它只是避免执行两次。一个更好的查询是lazyberezovsky 建议的查询,您在其中利用了Max,而不是订购并获取第一个。如果该列上有索引,则不会有太大区别,但如果 没有,则索引排序会更昂贵。

【讨论】:

  • 这其实是正确的答案。我正在写类似的东西,但你先写了。
  • +1 from me simple FirstOrDefault() 在这种情况下也可以完成这项工作:)
  • @lazyberezovsky 是的,正如我所说,这里的关键点是列上是否有索引。这将确定订购与不订购是否存在明显的性能差异。
  • 谢谢。这两个版本我都试过了,这个是禁食的。带有DefaultIfempty().Max() 的那个甚至比原始代码慢一点。我开始相信我的服务器可能会受到太阳耀斑或类似因素的影响,这会导致他随机变快或变慢;)
【解决方案2】:

如果没有符合您条件的实体,您可以使用Max() 获取最大位置,而不是订购和获取第一项,并使用DefaultIfEmpty() 提供默认值(int 为零)。顺便说一句,如果序列为空,您可以提供自定义默认值返​​回。

lastPosition = someObjectContext.someObjectSet
                                .Where(i => i.PNT_ATT_ID == tmp_ATT_ID)
                                .Select(i => i.Position)
                                .Cast<int>()
                                .DefaultIfEmpty() 
                                .Max();

因此,您将避免执行 两个 查询 - 一个用于定义是否有任何位置,另一个用于获取最新位置。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-09-19
    • 1970-01-01
    • 1970-01-01
    • 2010-11-17
    • 1970-01-01
    • 2014-09-28
    • 1970-01-01
    • 2016-10-16
    相关资源
    最近更新 更多