2012-06-27 11 views
9

Używam C# 4.0. "Optymalizacja kodu" w Visual Studio jest włączony.Jaki jest sens tej optymalizacji słownika C# <,>?

Rozważmy następujący kod w klasie:

Dictionary<int, int> dictionary = new Dictionary<int, int>(); 

public void IncrementDictionary(int key) { 
    if (!dictionary.ContainsKey(key)) { 
     dictionary[key] = 1; 
    } else { 
     dictionary[key]++; 
    } 
} 

Tutaj wezwanie do IncrementDictionary robi jedną z dwóch rzeczy:

  • Jeśli wartość nie istnieje dla key, to wartość jest tworzony i inicjowane 1.
  • Jeżeli wartość istnieje wartość jest zwiększany o 1.

teraz obserwować, co się dzieje, gdy używam ILSpy dekompilować Rezultat:

Dictionary<int, int> dictionary = new Dictionary<int, int>(); 

public void IncrementDictionary(int key) { 
    if (!dictionary.ContainsKey(key)) { 
     dictionary[key] = 1; 
     return; 
    } 
    Dictionary<int, int> dictionary2; 
    (dictionary2 = dictionary)[key] = dictionary2[key] + 1; 
} 

Uwaga: w rzeczywistej produkcji kodu przy użyciu tego, optymalizator/kompilator tworzy również: int key2 = key; i wykorzystuje key2 w ostatnim wierszu.

Ok, var został zastąpiony przez Dictionary<int, int>, co jest oczekiwane. I instrukcja if została uproszczona, aby dodać return zamiast używać else.

Ale dlaczego do cholery było nowe odniesienie do oryginalnego stworzonego słownika?

+3

Ważna uwaga: nie utworzono drugiego słownika. Zamiast tego jest brane pod uwagę drugie odniesienie do istniejącego słownika. – Jon

+0

@Jon: W porządku, mój błąd. –

+3

Opublikowany przez Ciebie plik ILSpy nie jest dokładnym odzwierciedleniem tego, co dzieje się bezpośrednio w kodzie CIL. Możliwe, że jest to błąd lub niedoskonałość dekompilatora CIL-to-C#. Czy możesz opublikować CIL, abyśmy byli pewni? – Dai

Odpowiedz

11

Zgaduję, że może być, aby uniknąć sytuacji wyścigu, gdzie jeśli masz:

dictionary[i] = dictionary[i] + 1

To nie atomowy. dictionary przypisany do może zmienić po masz wartość i inkrementowane.

Wyobraźmy sobie ten kod:

public Dictionary<int, int> dictionary = new Dictionary<int, int>(); 

public void Increment() 
{ 
    int newValue = dictionary[0] + 1; 
    //meanwhile, right now in another thread: dictionary = new Dictionary<int, int>(); 
    dictionary[0] = newValue; //at this point, "dictionary" is actually pointing to a whole new instance 
} 

z lokalnym zmiennej przypisania mają, to wygląda bardziej jak tego uniknąć warunek:

public void IncrementFix() 
{ 
    var dictionary2 = dictionary; 
    //in another thread: dictionary = new Dictionary<int, int>(); 
    //this is OK, dictionary2 is still pointing to the ORIGINAL dictionary 
    int newValue = dictionary2[0] + 1; 
    dictionary2[0] = newValue; 
} 

pamiętać, że nie w pełni spełniają wszystkie nici - bezpieczne wymagania. Na przykład w tym przypadku zaczniemy zwiększać wartość, ale odwołanie do klasy dictionary zmieniło się na zupełnie nową instancję. Ale jeśli potrzebujesz wyższego poziomu bezpieczeństwa wątku, musisz zaimplementować własną agresywną synchronizację/blokowanie, które zwykle wykracza poza zakres optymalizacji kompilatora. Jednak ten, z tego, co wiem, nie dodaje żadnego dużego trafienia (jeśli w ogóle) do przetwarzania i unika tego warunku. Może się tak zdarzyć, gdy dictionary jest własnością , a nie takim, jakim jest to pole w twoim przykładzie, w którym to przypadku z pewnością nie byłoby optymalizacją dwukrotne rozwiązanie właściwości getter. (Czy przypadkiem nie jest to rzeczywisty kod korzystający z właściwości słownika, a nie z pola, które opublikowałeś?)

EDIT: No, za pomocą prostego sposobu:

public void IncrementDictionary() 
{ 
    dictionary[0]++; 
} 

IL Zgłaszane od LINQPad jest:

IL_0000: nop   
IL_0001: ldarg.0  
IL_0002: ldfld  UserQuery.dictionary 
IL_0007: dup   
IL_0008: stloc.0  
IL_0009: ldc.i4.0  
IL_000A: ldloc.0  
IL_000B: ldc.i4.0  
IL_000C: callvirt System.Collections.Generic.Dictionary<System.Int32,System.Int32>.get_Item 
IL_0011: ldc.i4.1  
IL_0012: add   
IL_0013: callvirt System.Collections.Generic.Dictionary<System.Int32,System.Int32>.set_Item 
IL_0018: nop   
IL_0019: ret   

Nie jestem do końca pewien (nie jestem wiz IL) , ale myślę, że wywołanie dup powoduje podwojenie tego samego słownika na stosie, więc niezależnie od tego, zarówno wywołania get, jak i set będą wskazywać na ten sam słownik. Być może właśnie w ten sposób ILSpy przedstawia go jako kod C# (jest mniej więcej taki sam, co zachowanie). Myślę. Proszę, popraw mnie, jeśli się mylę, ponieważ, tak jak powiedziałem, nie znam jeszcze IL, jak na dłoni.

EDYCJA: Musisz uruchomić, ale ostateczny sens to: ++ i += nie są operacjami atomowymi i są w rzeczywistości dużo bardziej skomplikowane w wykonywanych instrukcjach niż te przedstawione w C#. W związku z tym, aby upewnić się, że każdy z kroków get/increment/set zostanie wykonany na tej samej instancji słownikowej (jak można się tego spodziewać po kodzie C#), lokalna referencja do słownika jest wykonywana w celu uniknięcia działania pola " "dwukrotnie wykonaj operację, która może spowodować wskazanie nowej instancji. W jaki sposób ILSpy pokazuje, że wszystko, co dotyczy pracy z indeksowaną operacją + =, zależy od niej.

+0

Nie, 'dictionary2' nadal wskazuje na oryginalny słownik. –

+2

Dobrze. 'Przyrost()' kontroluje 'słownik2': jest to lokalne odniesienie do oryginalnego słownika. –

+0

Ah, teraz widzę. Świetna odpowiedź. –

1

Twoja edycja pomieszane co staraliśmy się pokazać, ale dlatego, że dictionary[key]++ sprawia tymczasową kopię dictionary jest to, że nie ma możliwości dowiedzenia się, czy indeksowany getter dla Dictionary<int,int> zmieni pole dictionary. Specyfikacja wskazuje, że nawet jeśli zmieniono pole dictionary podczas indeksowanego get, indeksowany put nadal będzie wykonywany na tym samym obiekcie.

BTW, .net naprawdę powinien (i powinien) zapewnić środki, dzięki którym klasy mogą wystawiać właściwości jako ref. Byłoby możliwe, aby Dictionary zapewniał metody (zakładając, że są one podobne do Action<T>, ale parametr zadeklarował ref, a także ActionByRef<T1,T2>). Jeśli tak, można wykonać odczyt-modyfikowanie-zapisu w obiekcie kolekcji bez konieczności dwukrotnego indeksowania w kolekcji. Niestety, nie ma standardowej konwencji dotyczącej eksponowania nieruchomości w taki sposób, ani nie ma wsparcia językowego.

+0

Czy edycja jest teraz poprawiona? –

+0

@SimpleCoder: Wygląda lepiej. – supercat

+0

Ok dzięki. To, co mówisz, ma sens - nigdy o tym nie myślałem. –