2017-11-14 22 views
19

Więc jestem emitujące pewne dynamiczne proxy poprzez DefineDynamicAssembly, a podczas testów stwierdziliśmy, że:Wiele typów w jednym dynamicznym zespole jest zdecydowanie wolniej niż wielu dynamicznych zespołów z jednego typu każdy

  • Jeden typ za dynamicznym zespole: szybko, ale zużywa dużo pamięci
  • Wszystkie rodzaje w jednym dynamicznym zespole: bardzo powolny, ale zużywa znacznie mniej pamięci

W moim teście wygenerować 10.000 rodzajów i montaż jednego typu per- kod działa około 8-10 razy szybciej. Wykorzystanie pamięci jest całkowicie zgodne z tym, czego się spodziewałem, ale dlaczego nadszedł czas na generowanie typów?

Edycja: Dodano przykładowy kod.

Jeden zespół:

var an = new AssemblyName("Foo"); 
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run); 
var mb = ab.DefineDynamicModule("Bar"); 

for(int i = 0; i < 10000; i++) 
{     
    var tb = mb.DefineType("Baz" + i.ToString("000")); 
    var met = tb.DefineMethod("Qux", MethodAttributes.Public); 
    met.SetReturnType(typeof(int)); 

    var ilg = met.GetILGenerator(); 
    ilg.Emit(OpCodes.Ldc_I4, 4711); 
    ilg.Emit(OpCodes.Ret); 

    tb.CreateType(); 
} 

Jeden zespół od rodzaju:

for(int i = 0; i < 10000; i++) 
{ 
    var an = new AssemblyName("Foo"); 
    var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an, 
                  AssemblyBuilderAccess.Run); 
    var mb = ab.DefineDynamicModule("Bar"); 

    var tb = mb.DefineType("Baz" + i.ToString("000")); 
    var met = tb.DefineMethod("Qux", MethodAttributes.Public); 
    met.SetReturnType(typeof(int)); 

    var ilg = met.GetILGenerator(); 
    ilg.Emit(OpCodes.Ldc_I4, 4711); 
    ilg.Emit(OpCodes.Ret); 

    tb.CreateType(); 
} 
+0

Czy możesz udostępnić pewne szczegóły dotyczące środowiska budowy i środowiska .Net? Próbowałem tego na starej wersji (.NET 3.5 release build) i otrzymałem następujące czasy: Czas na jeden zestaw: 03.6230775; Czas na wiele zjazdów: 02.7712719. Znacznie mniejsza różnica niż to, co widzisz. Jednak ** przy pierwszym uruchomieniu ** zauważyłem znacznie większą różnicę - bliżej Ciebie - sugerując coś, co może mieć profilowanie JIT. – dbc

Odpowiedz

8

na moim komputerze w LINQPad przy użyciu C# 7.0 Mam jeden zespół około 8,8 sekundy, jeden zespół od rodzaju około 2,6 sekundy. Zazwyczaj w jednym zestawie są DefineType i CreateType, podczas gdy w czasie jest głównie w DefineDynamicAssembly + DefineDynamicModule.

sprawdza, czy nie ma konfliktów nazw, co jest odnośnikiem do zapytania Dictionary. Jeśli numer Dictionary jest pusty, chodzi o sprawdzenie dla null.

Większość czasu spędza się w CreateType, ale nie widzę gdzie, ale wydaje się, że wymaga dodatkowego czasu dodawania typów do jednego Modułu.

Utworzenie wielu modułów spowalnia cały proces, ale większość czasu spędza się na tworzeniu modułów, a następnie w DefineType, który musi skanować każdy moduł pod kątem duplikatu, a teraz zwiększył się do 10 000 sprawdzeń: null. Z unikalnym modułem na typ, CreateType jest bardzo szybki.

+0

Hmm, wypróbowałem jeden zespół + wiele modułów po jednym typie, jest on nieco szybszy, ale wykorzystuje tę samą ilość pamięci co jeden typ na sposób montażu. Słownik wydaje się mało prawdopodobne, aby to wszystko spowodowało, ale próbowałem wyczyścić jego wewnętrzny słownik po każdym typie, bez powodzenia. – Chris

+0

Co słownika wewnętrznego masz na myśli? Jeśli wykonujesz wiele typów w jednym zespole, słownik jest potrzebny do śledzenia utworzonych typów. – NetMage

+0

Po prostu pomysł, który dostałem z https://stackoverflow.com/questions/2503645/reflect-emit-dynamic-type-memory-blowup, choć całkiem bez sensu, ponieważ to, co i tak było, jest teraz słownikiem ... – Chris

7

W moim kontroli dlaczego definiowania wielu modułów w jednym zespole jest wolniejszy niż stworzyć nowy zespół z jednego modułu, za pomocą tych fragmentów kodu:

Single-montażowe Scenariusz:

 var an = new AssemblyName("Foo"); 
     var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run); 
     for (int i = 0; i < 10000; i++) 
     { 
      ab.DefineDynamicModule("Bar" + i.ToString("000")); 
     } 
Scenariusz

Wielu Montaż:

 var an = new AssemblyName("Foo"); 
     for (int i = 0; i < 10000; i++) 
     { 
      var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run); 
      ab.DefineDynamicModule("Bar"); 
     } 
  1. Znalazłem, że około 20% (50% w wielu przykładach złożeń) razy, kod źródłowy przechodzi przez wszystkie nazwy modułów, aby sprawdzić, czy nie ma konfliktu. Ta część jest zrozumiała i oczekiwana.
  2. Podczas używania jednego zestawu, kolejne 60% -80% czasu, CLI's DefineDynamicModule() jest pod ciśnieniem. Jednak przy użyciu wielu złożeń ta metoda nigdy nie jest wywoływana; zamiast tego inne metody są odpowiedzialne za pozostałe 50%.

Chodźmy głębiej dokumentacji ECMA-335 dla CLI.

II.6 Zespół to zestaw jednego lub więcej plików wdrożonych jako jednostka.

Page 140

Więc teraz zrozumieć, że zespół jest w zasadzie pakiet i moduły są głównymi składnikami. Przy czym powiedział:

II.6 Moduł jest pojedynczy plik zawierający treść wykonywalny w formacie określonym tutaj. Jeśli moduł zawiera manifest, to określa również moduły (w tym siebie), które tworzą zespół. Zgromadzenie zawiera tylko jeden plik manifestu spośród wszystkich jego plików akt.

Page 140

Na podstawie tych informacji, wiemy, że gdy tworzymy zespół, automatycznie dodać jeden moduł do montażu, jak również. Dlatego nigdy nie otrzymujemy trafienia w funkcję CLI o numerze DefineDynamicModule(), jeśli nadal będziemy tworzyć nowe złożenia. Zamiast tego otrzymujemy hit w metodzie CLI o nazwie GetInMemoryAssemblyModule() w celu pobrania informacji o Module Manifestów (moduł, który jest tworzony automatycznie).

Tak więc mamy niewielki wzrost wydajności; z jednym zestawem otrzymujemy moduły 10001, ale z wieloma zespołami otrzymujemy w sumie 10000 modułów. Nie za wiele, więc ten dodatkowy moduł nie powinien być głównym powodem tego.

II.6.5 Gdy element jest w obecnym zespole, ale jest częścią modułu innego niż jednego zawierającego manifest, moduł definiowania zostanie ogłoszony w manifeście zespołu pomocą .module dyrektywa zewnętrzna.

Page 146

i

II.6.7 Manifest moduł, od której nie może być tylko jedna za montaż, zawiera .assembly dyrektywy. Aby wyeksportować typ zdefiniowany w dowolnym innym module złożenia, należy wpisać w manifeście złożenia.

Page 146

Dlatego za każdym razem tworzyć nowy moduł, są faktycznie dodanie nowego pliku do archiwum, a następnie modyfikując pierwszy plik z archiwum odwoływać się do nowego modułu. Zasadniczo w kodzie jednoskładnikowym dodajemy 10000 modułów, a następnie edytujemy pierwszy moduł 10000 razy. Nie dotyczy to kodu wielozespołu, w którym edytujemy pierwszy automatycznie wygenerowany moduł, 10000 razy.

To jest napowiedź, którą widzimy. I wzrasta wykładniczo w moim systemie.

(5000 = 1.5s, 10000 = 6s, 20000 = 25s)

z kodem jednak, wąskim gardłem jest SetMethodIL funkcja CLR niezarządzanego zadzwonił z metody CreateTypeNoLock.CreateTypeNoLock() i nie mogłem znaleźć nic w jeszcze dokumentacja na ten temat.

Niestety, trudno jest dokonać dekompilacji i zrozumieć CLR.dll, aby zobaczyć, co faktycznie się tam dzieje, a w rezultacie, właśnie zgadujemy na podstawie publicznych informacji opublikowanych przez Microsoft na tym etapie.

+0

Ładnie zbadane ! Jednak w moim teście z pojedynczym montażem, po prostu tworzę jeden moduł, a nie jeden dla każdego typu ... Próbowałem też zrobić jeden zespół z wieloma modułami, i jest nieco szybszy, ale nadal jest wolniejszy niż jeden typ na montaż. – Chris

+0

@ Chris, tak, wspomniałem, że wąskim gardłem jest 'SetMethodIL' rzekomo wywołany dla konstruktora typu. Nie wiem, dlaczego jeszcze to spowalnia. –

+0

Czy czas 'SetMethodIL' zmienia się w zależności od scenariusza? Wydaje się, że to nie powinno mieć związku, chyba że dzieje się coś ciekawego ... – Chris