Sepanjang hidup saya sebagai programmer c #, saya berpikir bahwa LINQ bukan terutama tentang kinerja kode, tetapi tentang kinerja programmer, seberapa cepat ia menulis kode, dan bukan seberapa cepat prosesor menjalankan kode ini. Dan masalah kinerja kedua kode dan programmer diselesaikan setelah mengidentifikasi kemacetan. Karenanya, saya sering menulis var groupedByDate = lst.GroupBy(item => item.IssueTime.Date).Select(…).ToList()
dan melakukan ini bukan karena niat jahat atau jahat, tetapi lebih mudah untuk men-debug kode. Untuk daftar, cukup letakkan kursor mouse di atas teks variabel dan Anda dapat segera melihat apakah ada sesuatu di sana atau tidak.
Mulailah
Setelah membaca artikel "Artikel gagal tentang percepatan refleksi " dan menenangkan emosi tentang "seseorang salah di Internet", saya bertanya-tanya apakah mungkin untuk membuat "LINQ to Objects" kode dekat dalam kinerja menjadi "manual". Mengapa tepatnya LINQ to Objects? Dalam pekerjaan saya, saya sering hanya menggunakan LINQ untuk Objects dan LINQ untuk penyedia Entitas. Kinerja LINQ untuk Entitas tidak penting bagi saya; sangat penting seberapa optimal query SQL yang dihasilkan untuk server database target dan seberapa cepat server akan menjalankannya.
Sebagai dasar, saya memutuskan untuk menggunakan proyek penulis artikel. Berbeda dengan contoh-contoh dari Internet, di mana suatu kode terutama dibandingkan seperti integerList.Where(item => item > 5).Sum()
kode "manual" yang berisi foreach
, if
dan seterusnya, contoh dari artikel itu tampak menarik dan vital bagi saya.
Hal pertama yang saya lakukan adalah menggunakan fungsi perbandingan string tunggal. Dalam kode sumber, dalam metode yang melakukan fungsi yang sama, tetapi terletak di sudut berlawanan dari cincin, metode yang berbeda digunakan, dalam satu kasus variable0.Equals(constant0, StringComparison.InvariantCultureIgnoreCase)
dan yang lain variable0.ToUpperInvariant() ==constant0.ToUpperInvariant()
. Ini sangat menjengkelkan untuk konstanta bahwa setiap pemanggilan metode dikonversi ke huruf besar. Saya memilih opsi ketiga menggunakan dalam kedua kasus variable0.ToUpperInvariant() ==constant0InUpperCaseInvariant
.
Kemudian semua kode yang tidak terkait langsung dengan membandingkan kinerja LINQ dan kode manual dibuang. Yang pertama ditangkap adalah kode yang membuat dan menginisialisasi objek kelas Mock<HabraDbContext>
. Apa gunanya membuat koneksi database untuk setiap tes, sekali sudah cukup? Itu telah dipindahkan melampaui tes kinerja.
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%.
Setelah menulis tes dan menganalisis hasilnya, pendapat saya tentang LINQ tidak berubah - menggunakannya produktivitas saya dalam menulis kode lebih tinggi daripada tanpa itu; Kinerja LINQ to Objects baik-baik saja. Menggunakan eksekusi kode LINQ yang tertunda sebagai cara meningkatkan kinerja tidak masuk akal.
Jika dalam suatu proyek ada masalah kinerja karena pengumpul sampah - cedera lahir bersih, maka mungkin memilih teknologi ini bukan solusi terbaik.
Kode pengujian saya tersedia di sini .