SORU
18 AĞUSTOS 2009, Salı


Merak: Neden İfade<...> ne zaman az bir DynamicMethod daha hızlı koşmak derlenmiş?

Şu anda ölçmek son iyileştirmeleri bazı, çoğunlukla eğlenceli ve öğrenme için yapıyorum, ve bir kaç soru ile beni terk etti bir şey keşfetti.

İlk soruları:

  1. Ben inşa bir yöntem bellek ile kullanım DynamicMethod, hata ayıklayıcı, hiç benim adım içine oluşturulan derleme kod, vieweing kod çözücü görünüm? Hata sadece benim için bütün yöntemi adım gibi görünüyor
  2. Yoksa, mümkün değil, bende Reflector ile kontrol edebilirsiniz, böylece bir şekilde bir Kurul olarak yeniden oluşturulan IL kodu kaydetmek mümkün mü?
  3. Neden basit toplama yöntemi (=^Int32 Int32. Expression<...> versiyonu mu Int32) daha hızlı en az DynamicMethod bir versiyonu daha çalıştırmak?

İşte gösteren kısa ve komple bir program. Benim sistemde çıktı

DynamicMethod: 887 ms
Lambda: 1878 ms
Method: 1969 ms
Expression: 681 ms

Lambda ve Yöntem çağrıları daha yüksek değerler bekliyordum, ama DynamicMethod sürümü sürekli 30-50% daha yavaş (varyasyonlar Windows ve diğer programlar için muhtemelen nedeniyle). Herkes nedenini biliyor musun?

İşte program:

using System;
using System.Linq.Expressions;
using System.Reflection.Emit;
using System.Diagnostics;

namespace Sandbox
{
    public class Program
    {
        public static void Main(String[] args)
        {
            DynamicMethod method = new DynamicMethod("TestMethod",
                typeof(Int32), new Type[] { typeof(Int32), typeof(Int32) });
            var il = method.GetILGenerator();

            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Add);
            il.Emit(OpCodes.Ret);

            Func<Int32, Int32, Int32> f1 =
                (Func<Int32, Int32, Int32>)method.CreateDelegate(
                    typeof(Func<Int32, Int32, Int32>));
            Func<Int32, Int32, Int32> f2 = (Int32 a, Int32 b) => a   b;
            Func<Int32, Int32, Int32> f3 = Sum;
            Expression<Func<Int32, Int32, Int32>> f4x = (a, b) => a   b;
            Func<Int32, Int32, Int32> f4 = f4x.Compile();
            for (Int32 pass = 1; pass <= 2; pass  )
            {
                // Pass 1 just runs all the code without writing out anything
                // to avoid JIT overhead influencing the results
                Time(f1, "DynamicMethod", pass);
                Time(f2, "Lambda", pass);
                Time(f3, "Method", pass);
                Time(f4, "Expression", pass);
            }
        }

        private static void Time(Func<Int32, Int32, Int32> fn,
            String name, Int32 pass)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (Int32 index = 0; index <= 100000000; index  )
            {
                Int32 result = fn(index, 1);
            }
            sw.Stop();
            if (pass == 2)
                Debug.WriteLine(name   ": "   sw.ElapsedMilliseconds   " ms");
        }

        private static Int32 Sum(Int32 a, Int32 b)
        {
            return a   b;
        }
    }
}

CEVAP
18 AĞUSTOS 2009, Salı


Yöntemi DynamicMethod ile yaratılan yöntemi Expression<> ile yaratılan herhangi gitmez ise iki thunk geçer.

İşte nasıl çalışır. İşte Time yöntemi fn(0, 1) (kodlanmış hata ayıklama kolaylığı için 0 ve 1 bağımsız) çağırma için: arama sırası

00cc032c 6a01            push    1           // 1 argument
00cc032e 8bcf            mov     ecx,edi
00cc0330 33d2            xor     edx,edx     // 0 argument
00cc0332 8b410c          mov     eax,dword ptr [ecx 0Ch]
00cc0335 8b4904          mov     ecx,dword ptr [ecx 4]
00cc0338 ffd0            call    eax // 1 arg on stack, two in edx, ecx

İlk Çağrı için call eax çizgi gibi gelir, DynamicMethod, araştırdım:

00cc0338 ffd0            call    eax {003c2084}
0:000> !u 003c2084
Unmanaged code
003c2084 51              push    ecx
003c2085 8bca            mov     ecx,edx
003c2087 8b542408        mov     edx,dword ptr [esp 8]
003c208b 8b442404        mov     eax,dword ptr [esp 4]
003c208f 89442408        mov     dword ptr [esp 8],eax
003c2093 58              pop     eax
003c2094 83c404          add     esp,4
003c2097 83c010          add     eax,10h
003c209a ff20            jmp     dword ptr [eax]

Bu bir yığın değişkenleri yeniden düzenlemek için swizzling yapıyor gibi görünüyor. Örtülü 'bu' tartışma ve olmayanlar. kullanan delegeler arasındaki farkı nedeniyle bu spekülasyon

Sonunda o atlamak çok gibi giderir:

003c209a ff20            jmp     dword ptr [eax]      ds:0023:012f7edc=0098c098
0098c098 e963403500      jmp     00ce0100

0098c098 kodu kalan JİT sonra jmp ile yazılmış var olan JİT bir thunk, gibi görünüyor. Bu gerçek kod-bu ölüm sonrası ile ilgili bir şey

0:000> !u eip
Normal JIT generated code
DynamicClass.TestMethod(Int32, Int32)
Begin 00ce0100, size 5
>>> 00ce0100 03ca            add     ecx,edx
00ce0102 8bc1            mov     eax,ecx
00ce0104 c3              ret

Yöntemi Expression<> ile oluşturulan çağırma sırası farklıdır yığın swizzling kodu yok. Burada, eax ile ilk atlama:

00cc0338 ffd0            call    eax {00ce00a8}

0:000> !u eip
Normal JIT generated code
DynamicClass.lambda_method(System.Runtime.CompilerServices.ExecutionScope, Int32, Int32)
Begin 00ce00a8, size b
>>> 00ce00a8 8b442404        mov     eax,dword ptr [esp 4]
00ce00ac 03d0            add     edx,eax
00ce00ae 8bc2            mov     eax,edx
00ce00b0 c20400          ret     4

Şimdi, nasıl böyle oldu?

  1. Swizzling (temsilci örtülü ilk argüman aslında, yani böyle bir temsilci statik bir yönteme bağlı değil) kullanılır gerekli değildi yığını
  2. Bu JİT temsilci gerçek hedef adresi yerine sahte bir tuttu bu SERİ derleme mantık tarafından zorunlu olmalı.

Bu SERİ, TAM zamanında zorla nasıl bilmiyorum, ama bir JİT kendimi en az bir defa işlevini çağırarak zorla yapmayı biliyorum. GÜNCELLEME: başka bir şekilde TAM zamanında zorla buldum: kurucu restrictedSkipVisibility argumetn kullanın ve true. pas İşte değiştirilmiş kodunu ortadan kaldırır yığın swizzling kullanarak örtülü 'bu' parametresi ve kullandığı diğer kurucu öncesi derlemek, böylece bağlı Adres gerçek Adres yerine thunk:

using System;
using System.Linq.Expressions;
using System.Reflection.Emit;
using System.Diagnostics;

namespace Sandbox
{
    public class Program
    {
        public static void Main(String[] args)
        {
            DynamicMethod method = new DynamicMethod("TestMethod",
                typeof(Int32), new Type[] { typeof(object), typeof(Int32),
                typeof(Int32) }, true);
            var il = method.GetILGenerator();

            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Ldarg_2);
            il.Emit(OpCodes.Add);
            il.Emit(OpCodes.Ret);

            Func<Int32, Int32, Int32> f1 =
                (Func<Int32, Int32, Int32>)method.CreateDelegate(
                    typeof(Func<Int32, Int32, Int32>), null);
            Func<Int32, Int32, Int32> f2 = (Int32 a, Int32 b) => a   b;
            Func<Int32, Int32, Int32> f3 = Sum;
            Expression<Func<Int32, Int32, Int32>> f4x = (a, b) => a   b;
            Func<Int32, Int32, Int32> f4 = f4x.Compile();
            for (Int32 pass = 1; pass <= 2; pass  )
            {
                // Pass 1 just runs all the code without writing out anything
                // to avoid JIT overhead influencing the results
                Time(f1, "DynamicMethod", pass);
                Time(f2, "Lambda", pass);
                Time(f3, "Method", pass);
                Time(f4, "Expression", pass);
            }
        }

        private static void Time(Func<Int32, Int32, Int32> fn,
            String name, Int32 pass)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (Int32 index = 0; index <= 100000000; index  )
            {
                Int32 result = fn(index, 1);
            }
            sw.Stop();
            if (pass == 2)
                Console.WriteLine(name   ": "   sw.ElapsedMilliseconds   " ms");
        }

        private static Int32 Sum(Int32 a, Int32 b)
        {
            return a   b;
        }
    }
}

İşte benim sistem: çalışma zamanları

DynamicMethod: 312 ms
Lambda: 417 ms
Method: 417 ms
Expression: 312 ms

EKLEMEK İÇİN GÜNCELLENDİ:

Bu kod çalışan bir Core i7 920 ile Windows 7 x 64 çalıştığı yeni sistem, üzerinde çalıştım .NET 4 beta 2 yüklü (mscoree.dll ver. 4.0.30902) ve sonuçları, şey, değişken.

csc 3.5, /platform:x86, runtime v2.0.50727 (via .config)

Run #1
DynamicMethod: 214 ms
Lambda: 571 ms
Method: 570 ms
Expression: 249 ms

Run #2
DynamicMethod: 463 ms
Lambda: 392 ms
Method: 392 ms
Expression: 463 ms

Run #3
DynamicMethod: 463 ms
Lambda: 570 ms
Method: 570 ms
Expression: 463 ms

Belki de bu Intel etkileyen sonuçlar, ya da muhtemelen Turbo Boost. Her durumda, çok sinir bozucu.

csc 3.5, /platform:x64, runtime v2.0.50727 (via .config)
DynamicMethod: 428 ms
Lambda: 392 ms
Method: 392 ms
Expression: 428 ms

csc 3.5, /platform:x64, runtime v4
DynamicMethod: 428 ms
Lambda: 356 ms
Method: 356 ms
Expression: 428 ms

csc 4, /platform:x64, runtime v4
DynamicMethod: 428 ms
Lambda: 356 ms
Method: 356 ms
Expression: 428 ms

csc 4, /platform:x86, runtime v4
DynamicMethod: 463 ms
Lambda: 570 ms
Method: 570 ms
Expression: 463 ms

csc 3.5, /platform:x86, runtime v4
DynamicMethod: 214 ms
Lambda: 570 ms
Method: 571 ms
Expression: 249 ms

Bu sonuçlar, birçok C# 3.5 / çalışma zamanı v2 rastgele speedups neden olan her neyse zamanlama kazalar olacaktır.0 senaryo. Eğer Limit yoksa Turbo Boost bu etkilerden sorumlu olup olmadığını görmek için bilgisayarınızı yeniden başlatmanız gerekecek.

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • Adam Washington

    Adam Washing

    12 Mayıs 2006
  • Migs351

    Migs351

    3 HAZİRAN 2007
  • Rootjunky.com

    Rootjunky.co

    22 EKİM 2011