Pur*_*ome 555 .net linq performance extension-methods .net-3.5
在System.Linq命名空间,我们现在可以扩展我们IEnumerable的有Any()和Count() 扩展方法.
最近我被告知如果我想检查一个集合中是否包含一个或多个项目,我应该使用.Any()扩展方法而不是.Count() > 0扩展方法,因为.Count()扩展方法必须迭代所有项目.
其次,一些集合具有属性(未扩展方法),其是Count或Length.使用它们会更好吗,而不是.Any()或.Count()?
是啊/是?
Mar*_*ell 689
如果你开始的东西,有一个.Length或.Count(如ICollection<T>,IList<T>,List<T>,等) -那么这将是最快的选择,因为它不需要去通过GetEnumerator()/ MoveNext()/ Dispose()所要求的顺序Any(),检查是否有非空IEnumerable<T>序列.
对于刚刚IEnumerable<T>,然后Any()将通常更快,因为它只有看一次迭代.但是,请注意LINQ-to-Objects实现Count()确实检查ICollection<T>(.Count用作优化) - 因此,如果您的基础数据源直接是列表/集合,则不会有太大的区别.不要问我为什么不使用非通用的ICollection......
当然,如果您使用LINQ来过滤它等(Where等),您将有一个基于迭代器块的序列,因此这种ICollection<T>优化是无用的.
一般用IEnumerable<T>:坚持Any();-p
nik*_*3ro 62
注意:当实体框架4是实际的时候,我写了这个答案.这个答案的要点是不要进入琐碎.Any()VS .Count()性能测试.关键是要表明EF远非完美.较新的版本更好......但是如果您的代码部分速度很慢并且使用EF,请使用直接TSQL进行测试并比较性能而不是依赖于假设(.Any()总是比这更快.Count() > 0).
虽然我同意大多数最高投票的答案和评论 - 尤其是关于开发者意图更好的点Any信号- 我的情况是,SQL在SQL Server(EntityFramework 4)上的数量级更快.Count() > 0
这是查询Any超时异常(在~200.000记录上):
con = db.Contacts.
Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
&& !a.NewsletterLogs.Any(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr)
).OrderBy(a => a.ContactId).
Skip(position - 1).
Take(1).FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)
Count 版本以毫秒为单位执行:
con = db.Contacts.
Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
&& a.NewsletterLogs.Count(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr) == 0
).OrderBy(a => a.ContactId).
Skip(position - 1).
Take(1).FirstOrDefault();
Run Code Online (Sandbox Code Playgroud)
我需要找到一种方法,同时看到LINQs产生什么确切的SQL -但它显然之间存在着巨大的性能差异Count,并Any在某些情况下,不幸的是,似乎你不能只坚持Any在所有情况下.
编辑:这是生成的SQL.你可以看到美女;)
ANY:
exec sp_executesql N'SELECT TOP (1)
[Project2].[ContactId] AS [ContactId],
[Project2].[CompanyId] AS [CompanyId],
[Project2].[ContactName] AS [ContactName],
[Project2].[FullName] AS [FullName],
[Project2].[ContactStatusId] AS [ContactStatusId],
[Project2].[Created] AS [Created]
FROM ( SELECT [Project2].[ContactId] AS [ContactId], [Project2].[CompanyId] AS [CompanyId], [Project2].[ContactName] AS [ContactName], [Project2].[FullName] AS [FullName], [Project2].[ContactStatusId] AS [ContactStatusId], [Project2].[Created] AS [Created], row_number() OVER (ORDER BY [Project2].[ContactId] ASC) AS [row_number]
FROM ( SELECT
[Extent1].[ContactId] AS [ContactId],
[Extent1].[CompanyId] AS [CompanyId],
[Extent1].[ContactName] AS [ContactName],
[Extent1].[FullName] AS [FullName],
[Extent1].[ContactStatusId] AS [ContactStatusId],
[Extent1].[Created] AS [Created]
FROM [dbo].[Contact] AS [Extent1]
WHERE ([Extent1].[CompanyId] = @p__linq__0) AND ([Extent1].[ContactStatusId] <= 3) AND ( NOT EXISTS (SELECT
1 AS [C1]
FROM [dbo].[NewsletterLog] AS [Extent2]
WHERE ([Extent1].[ContactId] = [Extent2].[ContactId]) AND (6 = [Extent2].[NewsletterLogTypeId])
))
) AS [Project2]
) AS [Project2]
WHERE [Project2].[row_number] > 99
ORDER BY [Project2].[ContactId] ASC',N'@p__linq__0 int',@p__linq__0=4
COUNT:
exec sp_executesql N'SELECT TOP (1)
[Project2].[ContactId] AS [ContactId],
[Project2].[CompanyId] AS [CompanyId],
[Project2].[ContactName] AS [ContactName],
[Project2].[FullName] AS [FullName],
[Project2].[ContactStatusId] AS [ContactStatusId],
[Project2].[Created] AS [Created]
FROM ( SELECT [Project2].[ContactId] AS [ContactId], [Project2].[CompanyId] AS [CompanyId], [Project2].[ContactName] AS [ContactName], [Project2].[FullName] AS [FullName], [Project2].[ContactStatusId] AS [ContactStatusId], [Project2].[Created] AS [Created], row_number() OVER (ORDER BY [Project2].[ContactId] ASC) AS [row_number]
FROM ( SELECT
[Project1].[ContactId] AS [ContactId],
[Project1].[CompanyId] AS [CompanyId],
[Project1].[ContactName] AS [ContactName],
[Project1].[FullName] AS [FullName],
[Project1].[ContactStatusId] AS [ContactStatusId],
[Project1].[Created] AS [Created]
FROM ( SELECT
[Extent1].[ContactId] AS [ContactId],
[Extent1].[CompanyId] AS [CompanyId],
[Extent1].[ContactName] AS [ContactName],
[Extent1].[FullName] AS [FullName],
[Extent1].[ContactStatusId] AS [ContactStatusId],
[Extent1].[Created] AS [Created],
(SELECT
COUNT(1) AS [A1]
FROM [dbo].[NewsletterLog] AS [Extent2]
WHERE ([Extent1].[ContactId] = [Extent2].[ContactId]) AND (6 = [Extent2].[NewsletterLogTypeId])) AS [C1]
FROM [dbo].[Contact] AS [Extent1]
) AS [Project1]
WHERE ([Project1].[CompanyId] = @p__linq__0) AND ([Project1].[ContactStatusId] <= 3) AND (0 = [Project1].[C1])
) AS [Project2]
) AS [Project2]
WHERE [Project2].[row_number] > 99
ORDER BY [Project2].[ContactId] ASC',N'@p__linq__0 int',@p__linq__0=4
似乎纯粹的EXISTS工作比计算Count更糟糕,然后用Count == 0做Where.
如果你们在我的调查结果中看到一些错误,请告诉我.无论Any vs Count讨论如何,可以从所有这些中解脱出来的是,当重写为存储过程时,任何更复杂的LINQ都会更好;).
kam*_*lod 21
由于这是相当受欢迎的主题和答案不同,我不得不重新审视问题.
测试环境: EF 6.1.3,SQL Server,300k记录
表型号:
class TestTable
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
测试代码:
class Program
{
static void Main()
{
using (var context = new TestContext())
{
context.Database.Log = Console.WriteLine;
context.TestTables.Where(x => x.Surname.Contains("Surname")).Any(x => x.Id > 1000);
context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Any(x => x.Id > 1000);
context.TestTables.Where(x => x.Surname.Contains("Surname")).Count(x => x.Id > 1000);
context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Count(x => x.Id > 1000);
Console.ReadLine();
}
}
}
Run Code Online (Sandbox Code Playgroud)
结果:
任何()~3ms
第一次查询的Count()~230ms,第二次查询的约400ms
备注:
对于我的情况,EF没有生成像他在帖子中提到的@Ben那样的SQL.
gre*_*mac 20
.NET Framework 与 .NET Core 中的确切细节略有不同,但它也在某种程度上取决于您在做什么:如果您使用的是ICollectionorICollection<T>类型(例如 with List<T>),则有一个.Count访问成本低的属性,而其他类型可能需要枚举。
使用.Count > 0如果此属性存在,否则.Any()。
使用.Count() > 0是从来没有最好的选择,在某些情况下可能会慢得多。
这适用于 .NET Framework 和 .NET Core。
现在我们可以深入了解细节..
让我们从一个非常常见的情况开始:使用List<T>(也是ICollection<T>)。
该.Count属性实现为:
private int _size;
public int Count {
get {
Contract.Ensures(Contract.Result<int>() >= 0);
return _size;
}
}
Run Code Online (Sandbox Code Playgroud)
这句话是_size由等维护的Add(),Remove()因为它只是访问一个字段,所以这是一个非常便宜的操作——我们不需要迭代值。
ICollection并且ICollection<T>都具有.Count与大多数实现它们可能类型类似的方式做到这一点。
任何其他不需要的IEnumerable类型也ICollection需要开始枚举来确定它们是否为空。影响性能的关键因素是我们是否最终枚举单个项目(理想)或整个集合(相对昂贵)。
如果集合实际上导致 I/O,例如通过从数据库或磁盘读取,这可能会严重影响性能。
.Any()在 .NET Framework (4.8) 中,Any()实现是:
public static bool Any<TSource>(this IEnumerable<TSource> source) {
if (source == null) throw Error.ArgumentNull("source");
using (IEnumerator<TSource> e = source.GetEnumerator()) {
if (e.MoveNext()) return true;
}
return false;
}
Run Code Online (Sandbox Code Playgroud)
这意味着无论如何,它将获得一个新的枚举器对象并尝试迭代一次。这比调用List<T>.Count属性更昂贵,但至少它不会迭代整个列表。
.Count()在 .NET Framework (4.8) 中,Count()实现是(基本上):
public static int Count<TSource>(this IEnumerable<TSource> source)
{
ICollection<TSource> collection = source as ICollection<TSource>;
if (collection != null)
{
return collection.Count;
}
int num = 0;
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
{
num = checked(num + 1);
}
return num;
}
}
Run Code Online (Sandbox Code Playgroud)
如果可用,ICollection.Count则使用,否则枚举集合。
.Any()Any().NET Core 中的 LINQ实现要聪明得多。您可以在此处查看完整的源代码,但与此讨论相关的部分是:
public static bool Any<TSource>(this IEnumerable<TSource> source)
{
//..snip..
if (source is ICollection<TSource> collectionoft)
{
return collectionoft.Count != 0;
}
//..snip..
using (IEnumerator<TSource> e = source.GetEnumerator())
{
return e.MoveNext();
}
}
Run Code Online (Sandbox Code Playgroud)
因为 aList<T>是 an ICollection<T>,这将调用该Count属性(尽管它调用了另一个方法,但没有额外的分配)。
.Count().NET Core 实现(源代码)与 .NET Framework(见上文)基本相同,因此它将ICollection.Count在可用时使用,否则将枚举集合。
与ICollection:
.Count > 0 是最好的.Count() > 0 很好,但最终只是打电话 ICollection.Count.Any() 会变慢,因为它枚举单个项目与非ICollection(无.Count属性)
.Any() 最好,因为它只列举一个项目.Count() > 0 不好,因为它会导致完整的枚举.Count > 0最好,如果有的话 ( ICollection).Any()很好,并且将执行ICollection.Count > 0或枚举单个项目.Count() > 0 不好,因为它会导致完整的枚举Ben*_*Ben 11
编辑:它已在EF版本6.1.1中修复.这个答案不再是实际的
对于SQL Server和EF4-6,Count()的执行速度比Any()快两倍.
当你运行Table.Any()时,它会产生类似的东西(警告:不要伤害大脑试图理解它)
SELECT
CASE WHEN ( EXISTS (SELECT
1 AS [C1]
FROM [Table] AS [Extent1]
)) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT
1 AS [C1]
FROM [Table] AS [Extent2]
)) THEN cast(0 as bit) END AS [C1]
FROM ( SELECT 1 AS X ) AS [SingleRowTable1]
Run Code Online (Sandbox Code Playgroud)
这需要根据您的条件进行2次扫描.
我不喜欢写,Count() > 0因为它隐藏了我的意图.我更喜欢使用自定义谓词:
public static class QueryExtensions
{
public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
{
return source.Count(predicate) > 0;
}
}
Run Code Online (Sandbox Code Playgroud)