编辑:改变我的测试,因为测试的运行方式存在缺陷。
我最近在Fluent Nhibernate上遇到了一些性能问题而且我遇到了一些我认为很奇怪的事情。当我创建一个IEnumerable时,List性能大幅提升。我试图找出原因。它似乎不应该,并且谷歌没有改变任何东西。
这是我跑的基本测试:
//Class has various built in type fields, but no references to anything
public class Something
{
public int ID;
public decimal Value;
}
var someRepository = new Repository(uow);
//RUN 1
var start = DateTime.Now;
// Returns a IEnumerable from a session.Linq<SomeAgg> based on the passed in parameters, nothing fancy. Has about 1300 rows that get returned.
var somethings = someRepository.GetABunchOfSomething(various, parameters);
var returnValue = SumAllFunction(somethings);
var timeSpent = DateTime.Now - start; //Takes {00:00:00.3580358} on my box
//RUN2
var start2 = DateTime.Now;
var returnValue = someFunction(somethings);
var timeSpent = DateTime.Now - start2; //Takes {00:00:00.0560000} on my box
public decimal SumAllFunction(IEnumerable<Something> somethings)
{
return somethings.Sum(x => x.Value); //Value is a decimal that's part of the Something class
}
现在,如果我使用相同的代码,只需更改someRepository.GetABunchOfSomethingto行并添加.ToList():
//RUN 1
var start = DateTime.Now;
var somethings = someRepository.GetABunchOfSomething(various, parameters).ToList();
var returnValue = SumAllFunction(somethings);
var timeSpent = DateTime.Now - start; //Takes {00:00:00.3580358} on my box
//RUN 2
var start2 = DateTime.Now;
var returnValue = SumAllFunction(somethings);
var timeSpent = DateTime.Now - start2; //Takes {00:00:00.0010000} on my box
没有其他改变。这些结果非常可重复。所以这不仅仅是一次性的时间问题。
TLDR版本为:
当通过循环运行相同的IEnumerable两次时,第二次运行所需的时间比使用.ToList()将IEnumerable更改为List的时间长10-20倍,然后再运行2循环。
我检查了SQL,当它是一个List时,sql只运行一次并且似乎被缓存并再次使用,而不是必须返回数据库才能获得结果。
如果它是一个IEnumerable,那么每当它访问IEnumerable的子代时,它就会前往数据库进行再水化。
我知道你不能从IEnumerable添加/删除,但我的理解是IEnumerable最初会被代理对象填充,然后代理对象会在需要时稍后补充。在他们保湿后你不必再次回到数据库,但它似乎不是这样。我显然有一个解决这个问题,但我觉得这很奇怪,我很好奇它为什么会这样做。
答案 0 :(得分:3)
当您在ToList()
结果上致电GetABunchOfSomething
时,会在此时执行查询,并将结果放入列表中。如果您没有拨打ToList()
,那么直到someFunction
运行才能执行查询,而您的计时器不会考虑这一点。
我想你会发现两者之间的时差是由于那个。
<强>更新强>
结果虽然可能与你相反,但却有道理。在您进行迭代之前未运行查询的原因以及未缓存结果的原因是作为功能提供的。假设您想在代码中的两个位置调用存储库方法;一次按Foo
排序,另一次按Bar
过滤。如果存储库方法返回IQueryable<YourClass>
,则对该对象所做的任何其他修改实际上将影响发出的SQL,而不是导致在内存中修改集合。例如,如果你运行它:
someRepository
.GetABunchOfSomething(various, parameters)
.Where(s => s.Bar == "SomeValue");
迭代后,生成的SQL可能看起来像这样:
select *
from someTable
where Bar = 'SomeValue'
但是,如果你这样做了:
someRepository
.GetABunchOfSomething(various, parameters)
.ToList()
.Where(s => s.Bar == "SomeValue");
然后您将从表中检索所有行,而您的应用程序将过滤结果。