在我作为c#程序员的整个短途生活中,我认为LINQ并不是主要与代码的性能有关,而是与程序员的性能,他编写代码的速度,而不是处理器执行此代码的速度有关。找出瓶颈后,代码和程序员的性能问题都将得到解决。因此,我经常写代码var groupedByDate = lst.GroupBy(item => item.IssueTime.Date).Select(…).ToList()
并这样做不是出于伤害或恶意目的,而是调试代码更容易。对于列表,只需将鼠标光标放在变量的文本上,您就可以立即查看是否有东西。
开始
在阅读了文章“ 关于加速反射的不成功文章 ”并平息了关于“ Internet上的某个人出错”的情绪之后,我想知道是否有可能使“ LINQ to Objects”代码的性能接近“ manual”。为什么要使用LINQ to Objects?在我的工作中,我经常仅使用LINQ to Objects和LINQ to Entities提供程序。LINQ to Entities的性能对我来说并不重要;至关重要的是,生成的SQL查询对于目标数据库服务器的最佳状态以及服务器执行该查询的速度。
作为基础,我决定使用本文的作者草案。相反,从互联网,其中的代码主要是比较类似的例子integerList.Where(item => item > 5).Sum()
是“手动”的代码包含foreach
,if
等等,从文章的例子似乎有趣和重要的给我。
我做的第一件事是使用单个字符串比较功能。在源代码中,在执行相同功能但位于圆环相对角的方法中,在一种情况下variable0.Equals(constant0, StringComparison.InvariantCultureIgnoreCase)
和在另一种情况下,将使用不同的方法variable0.ToUpperInvariant() ==constant0.ToUpperInvariant()
。对于每个方法调用都转换为大写的常量而言,这尤其令人讨厌。在这两种情况下,我都选择了第三个选项variable0.ToUpperInvariant() ==constant0InUpperCaseInvariant
。
然后,所有与比较LINQ性能和手动代码没有直接关系的代码都被扔掉了。首先要抓住的是创建和初始化类对象的代码Mock<HabraDbContext>
。一旦足够就为每个测试创建数据库连接有什么意义?它已经超越了性能测试。
IStorage _db;
[GlobalSetup]
public void Setup()
{
_db = MockHelper.InstanceDb();
}
private IStorage DBInstance => _db;
… — , ! Linq «» !

« - » . LINQ . , — .
, , LINQ vs «» . . . .
, , , ([Params(1, 10, 100, 1000)]
). . . . StatisticColumnRelStdDev
.
— FastHydrationLinq
, ManualHydrationLinq
— . , , (Fast)(Manual)(Slow)HydrationLinq vs (Fast)(Manual)(Slow)Hydration, ManualHydrationLinq
. FastHydrationLinq
- . .
ToArray
, ToDictionary
IEnumerable<T>
. , FastContactHydrator2
. - Action<Contact, string>
c ConcurrentDictionary<string, Action<Contact, string>>
IEnumerable<KeyValuePair<string, Action<Contact, string>>>
. GetContact2
, Sum
, .
protected override Contact GetContact2(IEnumerable<PropertyToValueCorrelation> correlations)
{
var contact = new Contact();
int dummySum = _propertySettersArray.Join(correlations, propItem => propItem.Key, corrItem => corrItem.PropertyName, (prop, corr) => { prop.Value(contact, corr.Value); return 1; }).Sum();
return contact;
}
ParseWithLinq
public static IEnumerable<KeyValuePair<string, string>> ParseWithLinq2(string rawData, string keyValueDelimiter = ":", string pairDelimiter = ";")
=> rawData.Split(pairDelimiter)
.Select(x => x.Split(keyValueDelimiter, StringSplitOptions.RemoveEmptyEntries))
.Select(x => x.Length == 2 ? new KeyValuePair<string, string>(x[0].Trim(), x[1].Trim())
: new KeyValuePair<string, string>(_unrecognizedKey, x[0].Trim()));
.
FastContactHydrator2
, , , , -, ( ParseWithLinq
ParseWithoutLinq
). . , , ToArray
. 10 var result = new List<PropertyToValueCorrelation>(10)
. 10? 42 , 10 . Fair .
. . GetFakeData
. , .
, , « » (RelStdDev). N=1000
.
:
- `ManualHydration`, `SlowHydration`, `FastHydration`, `ManualHydrationLinq`, `SlowHydrationLinq`, `FastHydrationLinq` - , ;
ManualHydrationFair
, ManualHydrationFairLinq
, FastHydrationFairLinq
— , ;FastHydrationLinq2
— , , - , LINQ;N
, ;Ratio
FastHydrationLinq2
.

Linq-
? — FastHydrationLinq2
ManualHydrationLinq
Ratio
. 26,361.37 μs 26,422.17 μs N=1000
. / N
. ManualHydrationFairLinq
, 8% , . FastHydrationFairLinq
, 1% -.
Fair-. , ManualHydrationFairLinq
8% . FastHydrationFairLinq
FastHydrationLinq
12%. Linq .
, . . , MockHelper.InstanceDb()
Setup
, . — , GetFakeData
, . , — . MockHelper.InstanceDb()
.
N
, MakeGarbage
. False
, True
— .

Voilà, , . — N=1
. 10% 82% .
… .
, , «» — Linq-, :

N=1
MakeGarbage=True
«»
, , , LINQ .
«Gen X» «Allocated» N=1
MakeGarbage=True
o , FastHydrationLinq2
| | Gen 0 | Gen 1 | Gen 2 | Allocated |
|«» | 20.5078 | 0.4883 | - | 63.95 KB |
|Linq- | 20.7520 | - | - | 63.69 KB |
ManualHydrationFairLinq
.
. , , — .
, ? . , N
[Params(1, 100)]
MakeGargabe=True
. «» — Linq-, . — , — Linq-, Linq — — — Linq. — . .
, , — Linq- . , . paint.net, . paint.net — paint.net . paint.net — «» , — Linq- .
— N
1, 10, 100, 1000 MakeGarbage
[ParamsAllValues]
. paint.net, — «» . — Linq- . paint.net Visual Studio — «» . , 80- . , Linq- 2%.
在编写了测试并分析了结果之后,我对LINQ的看法没有改变-使用它,我编写代码的效率要比没有它高。LINQ to Objects性能很好。使用LINQ代码的延迟执行作为提高性能的手段没有多大意义。
如果在项目中由于垃圾收集器而导致性能问题-.net出生受伤,那么选择此技术可能不是最佳解决方案。
我的测试代码在这里。