6

这是我今晚设置的一个测试。它是为了证明一些不同的东西,但结果并不像我预期的那样。

我正在对 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);
4

1 回答 1

13

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

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

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

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

如果CarBrandManager.GetList(context)可能返回由自定义 LINQ 提供程序(如实体框架集合)支持的对象,那么您可能希望将数据强制转换为 IQueryable:即使您的基准测试显示使用列表要快 20 倍,但这种差异是如此之小,以至于没有用户能够分辨出其中的区别。有朝一日,通过调用.Where().Take().Skip()并仅从数据存储中加载真正需要的数据,您可能会看到性能提升几个数量级,而如果您立即调用,您最终会将整个表加载到系统的内存.ToList()中。

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

于 2012-10-25T22:21:51.017 回答