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):
Daftarusing 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):
Tes | Waktu (rentang waktu) | Waktu (kutu) | % waktu |
---|
Asli | 00:00: 01.5760651 | 15760651 | 101,935% |
Dlgt asli | 00:00: 01.5461478 | 15461478 | 100% |
Ekspresi | 00:00: 01.5835454 | 15835454 | 102,4188% |
Kompilasi dalam Rilis, dengan optimisasi, mulai tanpa debugger (Ctrl + F5):
Tes | Waktu (rentang waktu) | Waktu (kutu) | % waktu |
---|
Asli | 00:00: 01.3182291
| 13182291
| 100% |
Dlgt asli | 00:00: 01.3300925
| 13300925
| 100,8999% |
Ekspresi | 00: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)
}
}