列表查询比IQueryable快20倍?

Tys*_*Tys 6 c# linq performance list iqueryable

这是我今晚安排的测试.这是为了证明一些不同的东西,但结果并不像我预期的那样.

我在IQueryable上运行10000次随机查询的测试,在测试时我发现如果我在List上做同样的事情,我的测试速度提高了20倍.

见下文.我的CarBrandManager.GetList最初返回一个IQueryable,但现在我首先发出一个ToList(),然后它的速度更快.

谁能告诉我为什么我看到这个巨大的差异?

var sw = new Stopwatch();
sw.Start();

int queries = 10000;

//IQueryable<Model.CarBrand> carBrands = CarBrandManager.GetList(context);
List<Model.CarBrand> carBrands = CarBrandManager.GetList(context).ToList();

Random random = new Random();
int randomChar = 65;

for (int i = 0; i < queries; i++)
{
    randomChar = random.Next(65, 90);
    Model.CarBrand carBrand = carBrands.Where(x => x.Name.StartsWith(((char)randomChar).ToString())).FirstOrDefault();
}

sw.Stop();
lblStopWatch.Text = String.Format("Queries: {0} Elapsed ticks: {1}", queries, sw.ElapsedTicks);
Run Code Online (Sandbox Code Playgroud)

Str*_*ior 12

这里可能有两个问题.第一:GetList(context)除了它所实现的知识之外,从哪种类型的集合返回并不明显IQueryable.这意味着当您评估结果时,它很可能是创建SQL查询,将该查询发送到数据库,并将结果具体化为对象.或者它可以解析XML文件.或者在互联网上下载RSS源或调用OData端点.这些显然比简单地过滤内存中的短列表需要更多的时间.(毕竟,真的有多少汽车品牌?)

但是让我们假设它返回的实现实际上是一个List,因此你测试的唯一区别是它是作为一个IEnumerable还是作为一个转换IQueryable.比较Enumerable类的扩展方法上的方法签名与上面的方法签名Queryable.当您将列表视为IQueryable时,您传入的Expression是需要进行评估的Funcs ,而不仅仅是可以直接运行的s.

当您使用像Entity Framework这样的自定义LINQ提供程序时,这使框架能够评估实际表达式树并从中生成SQL查询和实现计划.但是,LINQ to Objects只想在内存中评估lambda表达式,因此它必须使用反射或将表达式编译为Funcs,这两者都具有与之相关的大的性能损失.

您可能只想调用.ToList().AsEnumerable()在结果集上强制它使用Funcs,但从信息隐藏的角度来看,这将是一个错误.您可以假设您知道从该GetList(context)方法返回的数据是某种内存中对象.目前可能就是这种情况,也可能不是.无论如何,它不是为合同定义的合同的一部分GetList(context)方法,因此你不能假设它总是这样.你必须假设你得到的类型很可能是你可以查询的东西.虽然目前可能只有十几个汽车品牌可供搜索,但有一天可能会有成千上万(我在这里谈论编程实践,不一定说汽车行业就是这种情况) ).因此,您不应该假设下载整个汽车列表并在内存中过滤它们总是更快,即使现在恰好是这种情况.

如果CarBrandManager.GetList(context)可能返回由自定义LINQ提供程序(如Entity Framework集合)支持的对象,那么您可能希望将数据转换为IQueryable:即使您的基准测试显示它使用列表的速度快20倍,是如此之小,没有用户能够分辨出来.您可能有一天会通过调用.Where().Take().Skip()并仅从数据存储中加载您真正需要的数据来看到几个数量级的性能提升,而如果您立即调用,则最终将整个表加载到系统的内存中.ToList().

但是,如果你知道CarBrandManager.GetList(context)将始终返回在内存中的列表(顾名思义),应更改为返回IEnumerable<Model.CarBrand>,而不是一个IQueryable<Model.CarBrand>.或者,如果你使用的是.NET 4.5,也许是一个IReadOnlyList<Model.CarBrand>或者IReadOnlyCollection<Model.CarBrand>,取决于你愿意强迫你CarManager遵守的合同.