【问题标题】:Which is faster in .NET, .Contains() or .Count()?在 .NET、.Contains() 或 .Count() 中哪个更快?
【发布时间】:2011-03-17 15:51:30
【问题描述】:

我想将修改后的记录数组与从数据库中提取的记录列表进行比较,并从数据库中删除传入数组中不存在的那些记录。修改后的数组来自维护数据库的客户端应用程序,并且此代码在 WCF 服务应用程序中运行,因此如果客户端从数组中删除记录,则应从数据库中删除该记录。这是示例代码sn-p:

public void UpdateRecords(Record[] recs)
{
    // look for deleted records
    foreach (Record rec in UnitOfWork.Records.ToList())
    {
        var copy = rec;
        if (!recs.Contains(rec))                      // use this one?
        if (0 == recs.Count(p => p.Id == copy.Id))    // or this one?
        {
            // if not in the new collection, remove from database
            Record deleted = UnitOfWork.Records.Single(p => p.Id == copy.Id);
            UnitOfWork.Remove(deleted);
        }
    }
    // rest of method code deleted
}

我的问题:与 Contains 方法相比,使用 Count 方法是否有速度优势(或其他优势)? Id 属性保证是唯一的并且可以识别该特定记录,因此您不需要像我认为 Contains 可能那样进行按位比较。

有人吗? 谢谢,戴夫

【问题讨论】:

  • 为什么不设置一个测试应用程序调用每 10,000 次并对其进行基准测试?
  • 好吧,理论上,您的 Count 方法正在做更多的工作 - Contains 会在找到匹配项时返回,您的计数将继续到最后。
  • 简短的回答是,无论如何,任何网络延迟都会失去毫秒/微秒的优势。在我看来,将此归结为过早的优化。
  • 另外请注意,除非这会导致性能问题 - 通常不值得为此担心,您应该选择更能表达您意图的方法,因此包含或任何。是的,其他人称之为 - 过早优化。
  • 如果Record 是一个类,它不会进行按位比较。

标签: c# .net linq


【解决方案1】:

这样会更快:

if (!recs.Any(p => p.Id == copy.Id)) 

这与使用 Count() 具有相同的优点 - 但它也 在找到第一个匹配项后停止Count() 不同

【讨论】:

  • @BrokenGlass:可能不会更快,但看起来更优雅。
  • @JohnKZ 绝对更快更优雅。
  • 这并不比Contains 快​​。它们具有完全相同的大哦复杂性,因此它们基本上是相等的。 (这条评论只是为了澄清你的第一句话“这会更快”,好吧它比Count快,但不比Contains快)
  • 但是Contains 增加了依赖对象实现Equals 的复杂性,这可能比Any 使用的lambda 慢,甚至可能不正确。这并不重要,我们在这里讨论的是节省微秒。
  • @digEmAll - 我仍然认为它 至少Contains() 一样快 - 取决于 Record 类的平等定义 Contains() 可以是错误、较慢或充其量使用与Any() 相同的比较 - 但是的,大 O 复杂性明智,它们是相同的。
【解决方案2】:

您甚至不应该考虑Count,因为您只是在检查记录是否存在。你应该改用Any

使用Count 强制迭代整个可枚举以获得正确的计数,Any 在找到第一个元素后立即停止枚举。

至于Contains 的使用,您需要考虑指定类型的引用相等是否等同于您正在执行的Id 比较。默认情况下不是。

【讨论】:

    【解决方案3】:

    假设Record 正确实现了GetHashCodeEquals,我将完全使用不同的方法:

    // I'm assuming it's appropriate to pull down all the records from the database
    // to start with, as you're already doing it.
    foreach (Record recordToDelete in UnitOfWork.Records.ToList().Except(recs))
    {
        UnitOfWork.Remove(recordToDelete);
    }
    

    基本上不需要 N * M 查找时间 - 上面的代码最终会根据它们的哈希码从 recs 构建一组记录,并且比原始代码更有效地查找不匹配项。

    如果你真的有更多事情要做,你可以使用:

    HashSet<Record> recordSet = new HashSet<Record>(recs);
    
    foreach (Record recordFromDb in UnitOfWork.Records.ToList())
    {
        if (!recordSet.Contains(recordFromDb))
        {
            UnitOfWork.Remove(recordFromDb);
        }
        else
        {
            // Do other stuff
        }
    }
    

    (我不太确定为什么您的原始代码使用Single 从数据库中重新获取记录,而您已经将其作为rec...)

    【讨论】:

    • 哇,你让我明白了。我什至没有看到我两次获得记录-d'oh!我也喜欢 except 子句,我不知道这一点。好东西!
    • 好的,所以我刚刚尝试了 except 子句,它并没有像宣传的那样工作。该代码只是从数据库中删除所有记录,无论它们是否存在于传入数组中。鉴于这使用了第 3 方商业 ORM,我猜事情并不像您想象的那样。回到 Any 子句,我猜。
    • @DaveN59:它确实依赖于正确实现的 GetHashCode 和 Equals,是的。如果您有兴趣,还有其他选择(例如,从 ID 到 Record 的字典),但如果 Any 适合您,那么您不妨坚持下去。
    【解决方案4】:

    Contains() 将对您的对象使用Equals()。如果您没有覆盖此方法,Contains() 甚至可能返回不正确的结果。如果您已经覆盖它以使用对象的Id 来确定身份,那么在这种情况下Count()Contains() 几乎在做同样的事情。除了Contains() 将在匹配时短路,而Count() 将继续计数。 Any() 可能是比他们两个更好的选择。

    您确定这是您应用的瓶颈吗?对我来说,这感觉像是过早的优化。这是万恶之源,你知道的:)

    【讨论】:

    • 好奇心胜过一切。感谢您的反馈。
    【解决方案5】:

    由于您保证只有 1 个且只有 1 个,因此 Any 可能会更快。因为一旦找到匹配的记录,它就会返回 true。

    Count 将遍历整个列表,计算每次出现的次数。因此,如果该项目在 1000 个项目的列表中排名第一,它将检查 1000 个项目中的每一个。

    编辑

    此外,这可能是时候提一下不要进行过早的优化。

    连接你的两种方法,在每个方法之前和之后放置一个秒表。 创建一个足够大的列表(1000 项或更多,具体取决于您的域。)看看哪个更快。

    我的猜测是我们在这里讨论的是毫秒级。

    我完全赞成编写高效的代码,只要确保您不会花费数小时来为每天调用两次的方法节省 5 毫秒。

    【讨论】:

      【解决方案6】:

      应该是这样的:

      UnitOfWork.Records.RemoveAll(r => !recs.Any(rec => rec.Id == r.Id));
      

      【讨论】:

        【解决方案7】:

        我可以建议一种我认为应该更快的替代方法,因为即使在第一场比赛之后计数也会继续。

        public void UpdateRecords(Record[] recs)
        {
            // look for deleted records
            foreach (Record rec in UnitOfWork.Records.ToList())
            {
                var copy = rec;
                if (!recs.Any(x => x.Id == copy.Id)
                {
                    // if not in the new collection, remove from database
                    Record deleted = UnitOfWork.Records.Single(p => p.Id == copy.Id);
                    UnitOfWork.Remove(deleted);
                }
            }
            // rest of method code deleted
        }
        

        这样你肯定会在第一场比赛中打破而不是继续计数。

        【讨论】:

          【解决方案8】:

          如果你需要知道元素的实际数量,使用 Count();这是唯一的方法。如果要检查是否存在匹配记录,请使用 Any() 或 Contains()。两者都比 Count() 快得多,并且两者的性能大致相同,但 Contains 将对整个对象进行相等性检查,而 Any() 将基于对象评估 lambda 谓词。

          【讨论】:

            猜你喜欢
            • 2011-01-29
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-10-07
            • 2011-12-18
            • 2010-09-16
            相关资源
            最近更新 更多