Kecepatan Linq Expression Tree dikompilasi

Di sini, di artikel ini di komentar tidak ada perselisihan, tetapi beberapa “tidak konvergensi” dalam membandingkan kecepatan IL Emit dan Linq Expression Tree yang dikompilasi.

Artikel mini ini adalah kode uji kecepatan + hasil dari uji coba ini.

Untuk pengujian, kode dipilih yang berpotongan dengan isi artikel asli - serialisasi menjadi aliran panjang string, dan kemudian semua byte string yang dikodekan dalam UTF-16 (Encoding.Unicode).

Kode serialisasi itu sendiri mungkin bukan yang paling optimal, tetapi dekat dengan itu jika Anda tidak menggunakan konstruksi yang tidak aman.

Kode di kedua implementasi adalah sama, seperti yang Anda lihat dengan memeriksa konstruksi ekspresi Lambda.

Saya tidak peduli dengan generasi IL melalui Emit - kode yang harus "optimal" Saya baru saja menulis dalam C # dalam metode statis (pada kenyataannya, semua metode program pengujian statis, karena ini adalah aplikasi konsol yang sangat sederhana) - ini metode ini selanjutnya disebut Native .

Metode kedua untuk perbandingan adalah ekspresi yang dihasilkan Lambda, dikompilasi dengan memanggil metode Compile (mungkin kenaikan kecepatan dapat dicapai dengan menggunakan CompileToMethod, tetapi ini tidak akurat) - metode ini selanjutnya disebut Expresisons .

BONUS! Untuk beberapa alasan, tes tambahan ditambahkan - panggilan tidak langsung ke metode yang digunakan dalam tes Asli - dengan menetapkan metode ke bidang delegasi statis - metode ini disebut Native Dlgt .

Di awal aplikasi, output dari kedua metode adalah output, sehingga Anda dapat memastikan bahwa kode menghasilkan data yang persis sama.

Jadi, inilah kode aplikasi (sekaligus):

Daftar
using System; using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; namespace ExpressionSpeedTest { class Program { static void Main(string[] args) { InitExpression(); InitDelegateNative(); var inst = new TestClass { StringProp = "abcdefabcdef" }; byte[] buff1, buff2; using (var ms1 = new MemoryStream()) { SaveString(ms1, inst); buff1 = ms1.ToArray(); } using (var ms2 = new MemoryStream()) { DynamicMethod(ms2, inst); buff2 = ms2.ToArray(); } Console.WriteLine($"Native string: {string.Join("", buff1.Select(b => Encoding.Default.GetString(new[] { b })))}"); Console.WriteLine($"Expressions string: {string.Join("", buff2.Select(b => Encoding.Default.GetString(new[] { b })))}"); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); TestNative(); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); TestDelegateNative(); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); TestExpressions(); GC.Collect(GC.MaxGeneration); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration); Console.ReadLine(); } private static void TestDelegateNative() { var inst = new TestClass { StringProp = "abcdefabcdef" }; using (var ms = new MemoryStream()) { var sw = new Stopwatch(); sw.Start(); for (var idx = 0; idx < loopLength; idx++) { SaveString(ms, inst); } sw.Stop(); Console.WriteLine($"Native Dlgt test: {sw.Elapsed}, {sw.ElapsedTicks} ticks"); } } private static void InitDelegateNative() { NativeDelegate = SaveString; } private static void InitExpression() { var intGetBytes = typeof(BitConverter).GetMethods(BindingFlags.Static | BindingFlags.Public) .Single(x => x.Name == nameof(BitConverter.GetBytes) && x.GetParameters()[0].ParameterType == typeof(int)); var stringGetBytes = typeof(Encoding).GetMethods(BindingFlags.Instance | BindingFlags.Public) .Single(x => x.Name == nameof(Encoding.GetBytes) && x.GetParameters().Length == 1 && x.GetParameters()[0].ParameterType == typeof(string)); var unicodeProp = typeof(Encoding).GetProperty(nameof(Encoding.Unicode), BindingFlags.Static | BindingFlags.Public); var streamWrite = typeof(Stream).GetMethod(nameof(Stream.Write)); var streamPar = Expression.Parameter(typeof(Stream), "stream"); var instPar = Expression.Parameter(typeof(TestClass), "inst"); var intBuffVar = Expression.Variable(typeof(byte[]), "intBuff"); var strBuffVar = Expression.Variable(typeof(byte[]), "strBuff"); var expressionBody = Expression.Block( new[] { intBuffVar, strBuffVar }, Expression.Assign(intBuffVar, Expression.Call(null, intGetBytes, Expression.Property( Expression.Property( instPar, nameof(TestClass.StringProp)), nameof(string.Length)))), Expression.Assign(strBuffVar, Expression.Call(Expression.Property(null, unicodeProp), stringGetBytes, Expression.Property( instPar, nameof(TestClass.StringProp) ))), Expression.Call(streamPar, streamWrite, intBuffVar, Expression.Constant(0), Expression.Property(intBuffVar, nameof(Array.Length))), Expression.Call(streamPar, streamWrite, strBuffVar, Expression.Constant(0), Expression.Property(strBuffVar, nameof(Array.Length))) ); DynamicMethod = Expression.Lambda<Action<Stream, TestClass>>(expressionBody, streamPar, instPar).Compile(); } private const int loopLength = 10000000; private static Action<Stream, TestClass> DynamicMethod; private static Action<Stream, TestClass> NativeDelegate; private static void TestExpressions() { var inst = new TestClass { StringProp = "abcdefabcdef" }; using (var ms = new MemoryStream()) { var sw = new Stopwatch(); sw.Start(); for (var idx = 0; idx < loopLength; idx++) { DynamicMethod(ms, inst); } sw.Stop(); Console.WriteLine($"Expressions test: {sw.Elapsed}, {sw.ElapsedTicks} ticks"); } } private static void TestNative() { var inst = new TestClass { StringProp = "abcdefabcdef" }; using (var ms = new MemoryStream()) { var sw = new Stopwatch(); sw.Start(); for (var idx = 0; idx < loopLength; idx++) { SaveString(ms, inst); } sw.Stop(); Console.WriteLine($"Native test: {sw.Elapsed}, {sw.ElapsedTicks} ticks"); } } public static void SaveString(Stream stream, TestClass instance) { var intBuff = BitConverter.GetBytes(instance.StringProp.Length); var strBuff = Encoding.Unicode.GetBytes(instance.StringProp); stream.Write(intBuff, 0, intBuff.Length); stream.Write(strBuff, 0, strBuff.Length); } } class TestClass { public string StringProp { get; set; } } } 


Dan berikut ini adalah hasil pengujian pada konfigurasi berikut:

CPU
	 Intel (R) Core (TM) i7-3770 CPU @ 3.40GHz

	 Kecepatan dasar: 3,90 GHz
	 Soket: 1
	 Core: 4
	 Prosesor logis: 8
	 Virtualisasi: Diaktifkan
	 L1 cache: 256 KB
	 L2 cache: 1,0 MB
	 L3 cache: 8.0 MB

	 Pemanfaatan 8%
	 Kecepatan 4.05 GHz
	 Waktu 5: 00: 43: 01
	 Proses 239
	 Thread 4092
	 Menangani 168.774


Memori
	 32.0 GB DDR3

	 Kecepatan: 1600 MHz
	 Slot yang digunakan: 4 dari 4
	 Faktor bentuk: DIMM
	 Perangkat keras yang dipesan: 42,5 MB

	 Tersedia 20,7 GB
	 Tembolok 20.1 GB
	 Berkomitmen 13,4 / 36,7 GB
	 Paged pool 855 MB
	 Kolam tanpa halaman 442 MB
	 Sedang digunakan (Terkompresi) 11.2 GB (48.6 MB)

Target .Net Framework 4.7
OS Windows 10 Pro x64 1803 build 17134.48

Jadi, hasil yang dijanjikan:

Kompilasi di Debug, tanpa optimasi, meluncurkan tanpa debugger (Ctrl + F5):
TesWaktu (rentang waktu)Waktu (kutu)% waktu
Asli00:00: 01.576065115760651101,935%
Dlgt asli00:00: 01.546147815461478100%
Ekspresi00:00: 01.583545415835454102,4188%

Kompilasi dalam Rilis, dengan optimisasi, mulai tanpa debugger (Ctrl + F5):
TesWaktu (rentang waktu)Waktu (kutu)% waktu
Asli00:00: 01.3182291
13182291
100%
Dlgt asli00:00: 01.3300925
13300925
100,8999%
Ekspresi00:00: 01.4871786
14871786
112,8164%

Beberapa hasil dapat disimpulkan:

  • Compression Expression Tree berjalan 1-2% lebih lambat di Debug dan 10-12 di Release, yang sangat bagus, mengingat menghasilkan kode melalui Expression Tree di runtime jauh lebih mudah.
  • Dalam mode Debug, untuk beberapa alasan, panggilan metode tidak langsung melalui delegasi lebih cepat daripada panggilan langsung.

Bonus nomor 2 di bawah spoiler - Representasi debug ekspresi Lambda sebelum kompilasi:

Lambda
 .Lambda # Lambda1 <System.Action`2 [System.IO.Stream, ExpressionSpeedTest.TestClass]> (
     System.IO.Stream $ stream,
     ExpressionSpeedTest.TestClass $ inst) {
     .Blok (
         System.Byte [] $ intBuff,
         System.Byte [] $ strBuff) {
         $ intBuff = .Call System.BitConverter.GetBytes (($ inst.StringProp) .Length);
         $ strBuff = .Call (System.Text.Encoding.Unicode) .GetBytes ($ inst.StringProp);
         .Call $ stream.Write (
             $ intBuff,
             0
             $ intBuff. Panjang);
         .Call $ stream.Write (
             $ strBuff,
             0
             $ strBuff. Panjang)
     }
 }

Source: https://habr.com/ru/post/id413561/


All Articles