列表查询比IQueryable快20倍?

时间:2012-10-25 22:19:52

标签: c# performance linq 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);

1 个答案:

答案 0 :(得分:12)

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

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

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

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

如果CarBrandManager.GetList(context)可能返回由自定义LINQ提供程序(如Entity Framework集合)支持的对象,那么您可能希望将数据转换为IQueryable:即使您的基准测试显示为20次更快地使用列表,这种差异是如此之小,以至于没有用户能够分辨出差异。您可能有一天会通过调用.Where().Take().Skip()并仅从数据存储中加载您真正需要的数据来看到几个数量级的性能提升,而如果您调用{最终将整个表加载到系统的内存中{1}}马上就开始了。

但是,如果您知道 .ToList()将始终返回内存列表(顾名思义),则应将其更改为返回CarBrandManager.GetList(context)而不是IEnumerable<Model.CarBrand>。或者,如果您使用的是.NET 4.5,可能是IQueryable<Model.CarBrand>IReadOnlyList<Model.CarBrand>,具体取决于您愿意强制IReadOnlyCollection<Model.CarBrand>遵守的合同。