我在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);
解决方法
但是我们假设它返回的实现实际上是一个List,因此您测试的唯一区别是它是作为IEnumerable还是作为IQueryable进行转换.将Enumerable
类扩展方法上的方法签名与Queryable
上的方法签名进行比较.当您将列表视为IQueryable时,您将传递需要进行评估的表达式,而不仅仅是可以直接运行的Func.
当您使用像Entity Framework这样的自定义LINQ提供程序时,这使框架能够评估实际表达式树并从中生成SQL查询和实现计划.但是,LINQ to Objects只想在内存中评估lambda表达式,因此它必须使用反射或将表达式编译为Funcs,这两者都具有与之相关的大的性能损失.
您可能想要在结果集上调用.ToList()或.AsEnumerable()来强制它使用Funcs,但从information hiding的角度来看,这将是一个错误.您可以假设您知道从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遵守的合同.