Synchronizacja za pomocą SpinLock

Posted May 18th, 2012 by Piotr Zieliński
Categories: C#, Wielowątkowość

W .NET istnieje wiele sposobów synchronizacji pracy wątków. O dużej części z nich pisałem już na blogu (z ciekawszych np. klasa Barrier ). Najpopularniejszym i najłatwiejszym sposobem jest użycie słowa kluczowego lock. W wielu przypadkach jest to najlepszy i najbezpieczniejszy wybór. SpinLock to zupełnie inne podejście.

W przypadku lock, wątek jest usypiany i budzony gdy przyjdzie na niego kolej. Ma to kilka poważnych wad. Wiążą się one z szeregowaniem oraz zmianą kontekstu. Zmiana kontekstu jest dość czasochłonna ponieważ należy zapisać stan CPU (rejestry itp.) Usypianie więc wątku jest dość skomplikowane ponieważ należy dokonać pewnego rodzaju serializacji.

SpinLock działa zupełnie inaczej – wątek nigdy nie jest usypiany. Po prostu wątek działa i odpytuje czy może już uzyskać dostęp. Jeśli tak to od razu wykonuje operacje bez zbędnego szeregowania czy zmiany kontekstu. SpinLock to bardzo prosty algorytm, wykonujący pętle i sprawdzający jakąś flagę. W pseudokodzie można to zapisać następująco:


while (IsLockAlreadyTaken)
{
    // do nothing        
}

 

Rozważmy więc klasyczny problem zwiększania pewnej współdzielonej zmiennej:

internal class Program
{
private static int _counter;
private static SpinLock _spinLock=new SpinLock();

private static void Main(string[] args)
{
  for (int i = 0; i < 10000; i++)
  {
      var task = new Task(IncreaseValue);
      task.Start();
  }
  Thread.Sleep(7000);
  Console.WriteLine(_counter);
}
private static void IncreaseValue()
{
  bool lockTaken = false;

  try
  {
      _spinLock.Enter(ref lockTaken);
      _counter++;
  }
  finally
  {
      if (lockTaken)
          _spinLock.Exit();
  }
 }
}

Korzystanie z SpinLock jest łatwe, wystarczy wywołać metodę Enter a potem Exit.  Należy jednak zwrócić uwagę na kilka niesłychanie ważnych szczegółów:

  1. SpinLock to struktura a nie klasa! Z tego względu jeśli chcemy przekazać jako parametr musimy zrobić to przez referencję bo inaczej będą to całkowicie niezależne Spin’y.
  2. Przed wywołaniem metody SpinLock.Enter flaga musi być ustawiona na false.
  3. Spinlock nie jest “reentrant” czyli nie można wywołać metody Enter dwa razy. W przypadku gdy jest ustawiona właściwość  IsThreadOwnerTrackingEnabled zostanie wyrzucony wyjątek a w przeciwnym wypadku spowoduje to zakleszczenie.

Jakie są wady SpinLock? Największa to fakt, że wątek wciąż zużywa zasoby,wykonuje cykle. Dla operacji krótkotrwałych jest to lepsze niż zmiana kontekstu. W przypadku długich operacji zasoby systemowe są niepotrzebnie zużywane. Należy pamiętać, aby korzystać tylko z SpinLock dla prostych operacji, których wykonanie zajmie kilka cykli CPU – wtedy lepiej poczekać niż zmieniać kontekst. Jeszcze gorszy scenariusz, co w przypadku gdy czekamy na wątek, który został uśpiony ponieważ CPU miał coś ważniejszego do wykonania (interruption)? Im dłuższa operacja tym większe ryzyko. SpinLock jest odradzany dla procesorów jednordzeniowych gdzie trzymanie wątków nic nie robiących jest dużo bardziej kosztowne.

Klasa Tuple

Posted May 15th, 2012 by Piotr Zieliński
Categories: C#

W .NET 4.0 wprowadzoną klasę Tuple służącą do owijania kilku wartości w jeden obiekt. Tuple to nic innego jak obiekt zawierający w sobie jakieś dane w postaci właściwości. Do dyspozycji jest 8 statycznych  metod służących do stworzenia Tuple:

  • Create(T1)
  • Create(T1,T2)
  • Create(T1,T2,T3)
  • Create(T1,T2,T3,T4)
  • Create(T1,T2,T3,T4,T5)
  • Create(T1,T2,T3,T4,T5,T6)
  • Create(T1,T2,T3,T4,T5,T6,T7)
  • Create(T1,T2,T3,T4,T5,T6,T7,T8)

Ponadto istnieje możliwość stworzenia tuple za pomocą jednego z konstruktorów:

  • Tuple<T1>
  • Tuple<T1,T2>
  • Tuple<T1,T2,T3>
  • Tuple<T1,T2,T3,T4>
  • Tuple<T1,T2,T3,T4,T5>
  • Tuple<T1,T2,T3,T4,T5,T6>
  • Tuple<T1,T2,T3,T4,T5,T6,T7>
  • Tuple<T1,T2,T3,T4,T5,T6,T7,TRest>

Załóżmy, że chcemy stworzyć obiekt zawierający dwa pola: jedno string drugie int:

Tuple<string, int> tuple = Tuple.Create("Piotr", 26);
string firstName = tuple.Item1;
int age = tuple.Item2;

Należy zaznaczyć, że Tuple jest obiektem immutable i zmiana pól po utworzeniu jest już niemożliwa:

Tuple<string, int> tuple = Tuple.Create("Piotr", 26);
tuple.Item1 = "Piotr"; // niemozliwe - readonly
tuple.Item2 = 30; // niemozliwe - readonly

Oczywiście nic nie szkodzi na przeszkodzie aby stworzyć zagnieżdżone obiekty:

var tuple = Tuple.Create(Tuple.Create("Piotr", "Zielinski"), 26);
string firstName = tuple.Item1.Item1;
string lastName = tuple.Item1.Item2;
int age = tuple.Item2;

Korzystanie z metody statycznej Create myślę, że jest już jasne. Jak jednak wspomniałem, istnieje możliwość tworzenia tuple za pomocą konstruktorów:

var tuple = new Tuple<string, string>("Piotr", "Zielinski");
string firstName = tuple.Item1;
string lastName = tuple.Item2;

Najbardziej interesującym konstruktorem jest “Tuple<T1,T2,T3,T4,T5,T6,T7,TRest>”, który pozwala na zdefiniowanie więcej niż 8 wartości:

var tuple = new Tuple<int, int, int, int, int, int, int, Tuple<int, int>>(1, 2, 3, 4, 5, 6, 7,
                                                                                      new Tuple<int, int>(8, 9));
int item1 = tuple.Item1;
int item7 = tuple.Rest.Item1;
int item8 = tuple.Rest.Item2; 

Tuple to nie kolekcja – nie ma enumeratora. Kiedy warto tego używać? Moim zdaniem nigdy! Część programistów używa jednak tuple w wewnętrznych klasa np. przekazując parametry do wątku. Wszyscy natomiast odradzają eksponowania Tuple jako publiczna właściwość czy wartość zwracana przez funkcje. Osobiście nie używam tego tworu nawet w wewnętrznych klasach jako tymczasowe zmienne. Preferuje używać własne klasy, które zawierają właściwości z nazwami odzwierciedlającymi prawdziwe przeznaczenie. Wyjątkiem są aplikacje tymczasowe np. na szybko tworzone prototypy – uważam, że prototyp przede wszystkim musi zostać zaimplementowany szybko bo od niego zależy wiele ważnych decyzji.

Zasada Command-query separation (CQS)

Posted May 12th, 2012 by Piotr Zieliński
Categories: Patterns & Practices

Dziś znów powrót do podstaw inżynierii oprogramowania. Przedstawianie podstawowej zasady może wydawać się śmieszne ale mimo wszystko programista dobrze jak wie, że taka zasada ma swoją nazwę i naprawdę powinno się tego przestrzegać.  Za pewne wiele programistów nie zna nazw tych reguł ale i tak postępuje zgodnie z nimi. Post ma jednak uświadomić, że takie praktyki są dobrze udokumentowane i są na naprawdę dobrym zwyczajem a nie tylko intuicją doświadczonego programisty.

Wzorzec dotyczy konstrukcji metod – każda metoda powinna być “komendą (command)” lub “zapytaniem (query)” ale nigdy jednocześnie i komendą i zapytaniem. Pozostaje wyjaśnić czym się różnią te dwa twory? Komenda to polecenie zrobienia czegoś – np. zapisu danych do bazy danych. Zapytanie to zwrócenie danych. Innymi słowy, nie powinniśmy tworzyć metod, które zarówno wykonują jakaś logikę, zmieniając tym samym stan obiektu, bazy danych jak i które zwracają na końcu dane. Według CQS lepszym podejściem jest rozdzielenie tego na dwie metody.

Uważam, że zasada bardzo dobra ale czasami jest wręcz anty-wzorcem. Pierwszy przykład to programowanie współbieżne, gdzie należy unikać częstych lock’ow i z tego względu lepiej skorzystać z bardziej rozbudowanej ale jednej metody. Drugi przykład to warstwa usług, która eksponuje warstwę biznesową.  Fasada sama z definicji gromadzi szeroką funkcjonalność w jednej metodzie. Tworząc usługę sieciową lepiej aby jedno zapytanie zrobiło tyle co trzeba zamiast wysyłać kilka pojedynczych zapytań – interfejs warstwy usług nie powinien być “chatty”. Operacja na stosie POP (zdjęcie obiektu) jest klasycznym przykładem łamiącym CQS ale mimo wszystko jest to dobre podejście.

CQS przede wszystkim daje przejrzyste API – wiadomo, które metody zmieniają stan obiektu i prawdopodobnie kilkakrotne ich wywołanie może spowodować jakieś efekty uboczne.

Visual Studio 11 – kompatybilność wstecz oraz Solution Explorer

Posted May 10th, 2012 by Piotr Zieliński
Categories: Visual Studio

W dzisiejszym poście o dwóch nowościach. Pierwsza z nich to ulepszona kompatybilność. W VS 11 można otwierać solucje utworzone w Visual Studio 2010 bez znanego “Upgrade”. Oznacza to, że można jednocześnie na tym samym projekcie pracować zarówno w VS 11 jak i VS 2010. Projekt otworzony w VS 11 nie powoduje zmiany  formatu i później wciąż może być otwierany w VS 2010. Moim zdaniem znaczącą ułatwia to migrację. Niestety projekt utworzony od nowa w VS 11 nie może być już otwarty w 2010 ale wydaje się to naturalne.

Druga sprawa to ulepszony Solution Explorer. Screen:

image

 

Jak widać oprócz nazw plików mamy również listę klas oraz metod, właściwości itp. Klikając na którąś z pozycji otrzymujemy następujące menu:

image

Menu umożliwia sprawdzenie gdzie funkcja jest wywoływana, używana itp. Dla klas ponadto można sprawdzić hierarchię:

image

Visual Studio 11– QuickLaunch

Posted May 8th, 2012 by Piotr Zieliński
Categories: Visual Studio

W ostatnim poście o nowościach w VS 11 pisałem o szarych ikonach, co w większości osobom nie przypadło do gustu. Z tego co obserwuję, zdecydowana większość jest rozczarowana nowymi ikonami. Dzisiaj jednak chciałbym zaprezentować praktyczniejszą funkcję – nowy QuickLaunch. Na początek PrintScren:

image

Chodzi mi oczywiście o QuickLaunch znajdujący się w prawym górnym rogu. Wpisując jakiś tekst, zostanie przeszukanych kilka typów informacji jak:

  1. Otwarte dokumenty (przeszukuje po ich nazwie).
  2. Item’y w menu – bardzo przydatne gdy nie wiemy gdzie dokładnie jest coś w menu a pamiętamy tylko nazwę.
  3. Ostatnio używane operacje.
  4. Opcje w menu. Aby szybko przejść do opcji o nazwie VB Specific wystarczy:

image

Ponadto można ustawiać filtry. Jeśli chcemy przeszukać wyłącznie ostatnio wykonywane operacje wtedy zapytanie poprzedzamy @mru, otwarte dokumenty – @doc, item’y w menu – @menu, z kolei opcje – @opt.

image

Aby przełączyć się np. z edytora kodu do QuickLaunch wystarczy wcisnąć CTRL+Q a następnie możemy już wpisywać nasze zapytanie. Moim zdaniem przydatne gdy nie odrywając rąk od klawiatury chcemy wykonać jakaś operację a w końcu każdy programista VS powinien unikać używania myszki.

Service Locator jako anti-pattern

Posted May 5th, 2012 by Piotr Zieliński
Categories: C#, Patterns & Practices

W wielu publikacjach service locator podawany jest jako wzorzec projektowy, doskonale nadający się do implementacji inversion of control. W poście jednak chciałbym przedstawić drugą szkołę, która uważa, że ten wzorzec jest “brzydki” i powoduje ogromne zamieszanie.

Przede wszystkim odpowiedzmy sobie kiedy używamy podejścia IoC? W aplikacjach tymczasowych? Prototypach? Raczej nie… Początkowy czas na napisanie aplikacji IoC może okazać się dłuższy a korzyści nadchodzą dopiero po kilku miesiącach implementacji. IoC jest zatem doskonały albo nawet niezbędny dla aplikacji rozwijanych już trochę dłużej a szczególności dla projektów wykorzystujących testy jednostkowe.  Innym przykładem są różnego rodzaju biblioteki przeznaczone dla programistów. Wtedy gruntowne przetestowanie różnych przypadków użycia jest niezbędne.

Wady service locator’a widać bardzo w przypadku implementacji właśnie bibliotek zewnętrznych.  Zobaczmy przykładową implementację SL:

public static class SampleServiceLocator
{
    private readonly static IDictionary<Type, Func<object>> _services = new Dictionary<Type, Func<object>>();

    public static void Register<T>(Func<T> resolver)
    {
        _services[typeof(T)] = () => resolver();
    }
    public static T Resolve<T>()
    {
        return (T)_services[typeof(T)]();
    }
}

Następnie napiszmy jakiś kod oparty o Service Lcoator:

class DocumentManager
{
    public void Print(IDocument document)
    {
        IPritnManager manager = SampleServiceLocator.Resolve<IPrintManager>();
        manager.Print(document);
    }
}

Aby wykorzystać kod należy PAMIĘTAĆ o wcześniejszej rejestracji IPrintManager – to jest największa wada. Wyobraźmy sobie, że opublikowaliśmy kod i użytkownik próbuje wywołać metodę Print:

var documentManager = new DocumentManager();
documentManager.Print();

Powyższy kod wyrzuci wyjątek ponieważ nie zarejestrowano danego obiektu. Sygnatura nie wymusiła prawidłowego wykorzystania metody. Bez przeczytania dokumentacji nie ma możliwości prawidłowego zarejestrowania obiektów – użytkownik nie ma o tym zielonego pojęcia. Zobaczmy jakby wyglądał DocumentManager z wykorzystaniem wstrzyknięcia implementacji w konstruktorze:

class DocumentManager
{
    private IPrintManager _printManager;

    public DocumentManager(IPrintManager printManager)
    {
        _printManager = printManager;
    }
    public void Print(IDocument document)
    {
        _printManager.Print(document);
    }
}

W tej chwili nie ma możliwości złego wywołania kodu. Użytkownik tworząc DocumentManager wie, że musi przekazać implementację PrintManager.

Inna sprawa to testy jednostkowe. W takiej architekturze łatwo zobaczyć jakie obiekty należy przekazać. Wiadomo, że DocumentManager zależy od PrintManager i pierwszym etapem powinna być implementacja stub’a dla PrintManager.

Kolejną wadą jest modyfikacja istniejących metod. Przypuśćmy, że pewnego dnia Print został zmodyfikowany i teraz wykorzystuje kolejną zależność. Wszystkie aplikacje klienckie od tego momentu zostały złamane a co gorsze w czasie run-time a nie compile-time. Wszystko podczas kompilacji będzie działać a potem okaże się, że brakuje tej nowej zależności. Naszym celem jest sprawdzenie jak największej liczby błędów na etapie kompilacji a nie podczas działania już aplikacji. W przypadku wstrzyknięcia zależności za pomocą konstruktora byłoby to możliwe.

Wiem, że czasami SL jest jedynym rozwiązaniem ale warto zastanowić się na architekturą oprogramowania, które piszemy i minimalizować użycie tego wzorca\antywzorca.

Wyrażenia lambda i niespodziewany rezultat

Posted May 2nd, 2012 by Piotr Zieliński
Categories: C#

Co poniższy kod zwróci na ekranie?

var lambdas = new List<Func<int>>();
for (int index = 0; index < 5;index++ )
{
    lambdas.Add(() =>index);
}
Console.WriteLine(lambdas[0]());
Console.WriteLine(lambdas[1]());
Console.WriteLine(lambdas[2]());

Spodziewać się można 0,1,2. Jednak na ekranie ujrzymy 5,5,5. Dlaczego?  Aby odpowiedzieć na te pytanie zajrzymy do Reflector’a:

private static void Main(string[] args)
{
    List<Func<int>> lambdas;
    Func<int> CS$<>9__CachedAnonymousMethodDelegate1;
    <>c__DisplayClass2 CS$<>8__locals3;
    lambdas = new List<Func<int>>();
    CS$<>9__CachedAnonymousMethodDelegate1 = null;
    CS$<>8__locals3 = new <>c__DisplayClass2();
    CS$<>8__locals3.index = 0;
    goto Label_003C;
Label_0017:
    if (CS$<>9__CachedAnonymousMethodDelegate1 != null)
    {
        goto Label_0028;
    }
    CS$<>9__CachedAnonymousMethodDelegate1 = new Func<int>(CS$<>8__locals3.<Main>b__0);
Label_0028:
    lambdas.Add(CS$<>9__CachedAnonymousMethodDelegate1);
    CS$<>8__locals3.index += 1;
Label_003C:
    if (CS$<>8__locals3.index < 5)
    {
        goto Label_0017;
    }
    Console.WriteLine(lambdas[0]());
    Console.WriteLine(lambdas[1]());
    Console.WriteLine(lambdas[2]());
    return;
}
));

Jak widać, tworzona jest w miejsce lambdy delegata. Aby przekazać parametry do niej, została stworzona klasa <>c__DisplayClass2() , która jest po prostu wrapperem dla parametru index. Innymi słowy, została wygenerowana delegata, która jako parametr wejściowy bierze klasę DisplayClass2, która z kolei zawiera parametr index. Wszystko byłoby w porządku gdyby instancja DisplayClass2 była tworzona wewnątrz pętli. Z kodu jasno jednak wynika, że instancja jest tworzona przed Label_0017 (jest to początek pętli). Używamy zatem tej samej instancji, a w każdej iteracji wywołujemy CS$<>8__locals3.index += 1 (locals3 to instancja obiektu DisplayClass2), co zwiększa indeks. W ostatniej iteracji osiągnie się zatem wartość 5.

Przykład pokazuje, że wyrażenia lambda są łatwe w użyciu ale jeśli nie zna się w pełni zasady działania, mogą stworzyć niespodziewane efekty. Rozwiązaniem może być przekopiowanie indeksu do pomocniczej zmiennej. C#:

var lambdas = new List<Func<int>>();
for (int index = 0; index < 5;index++ )
{
 int tmp = index;
 lambdas.Add(() => tmp);
}
Console.WriteLine(lambdas[0]());
Console.WriteLine(lambdas[1]());
Console.WriteLine(lambdas[2]());
}

Reflector:

List<Func<int>> lambdas;
int index;
<>c__DisplayClass1 CS$<>8__locals2;
lambdas = new List<Func<int>>();
index = 0;
goto Label_002D;
Label_000A:
CS$<>8__locals2 = new <>c__DisplayClass1();
CS$<>8__locals2.tmp = index;
lambdas.Add(new Func<int>(CS$<>8__locals2.<Main>b__0));
index += 1;
Label_002D:
if (index < 5)
{
   goto Label_000A;
}
Console.WriteLine(lambdas[0]());
Console.WriteLine(lambdas[1]());
Console.WriteLine(lambdas[2]());
return;

Na zakończenie dodam, że w c# 5.0 usprawniono trochę wyrażenia lambda ale o tym kiedyś indziej…

Visual Studio 11 – pierwsze spojrzenie

Posted May 1st, 2012 by Piotr Zieliński
Categories: Visual Studio

Zamierzam napisać kilka krótkich postów o nowych funkcjonalnościach w Visual Studio 11. Na dobry początek zacznijmy po prostu od screenu:

image

Co od razu rzuca się w oczy? Czarno białe ikony. Dla porównania zobaczymy jak wygląda “stary” Visual Studio:

image

Na pierwszy rzut oka to poprzednia wersja wygląda dużo nowocześniej ze względu na kolorowe, bardziej 3d ikonki. Dlaczego Microsoft zdecydował się na taki krok? Z tego co wiem, doszli do wniosku, że środowisko programistyczne nie powinno być zabawką i nie może rozpraszać od pracy nad kodem. W pełni się z tym zgadzam. Wolę szybkie IDE bez zbędnych wodotrysków. Tak samo mam z Windows – zawsze ustawiam tryb klasyczny. Dlatego lubię nowe ikonki chodź osobiście wolałbym dużo bardziej  ikony  z Visual C++ 6.0 niż te co dodali teraz…

C# 5.0: Atrybuty CallerMemberName, CallerFilePath oraz implementacja interfejsu INotifyPropertyChanged

Posted April 30th, 2012 by Piotr Zieliński
Categories: C#

W c# 5.0 dodano dwa nowe atrybuty: CallerMemberName oraz CallerFilePath. Można je stosować do domyślnych parametrów metod:

static private void Print([CallerMemberName]string methodName = null, [CallerFilePath]string fileName = null)
{
  Console.WriteLine(methodName);
  Console.WriteLine(fileName);
}

Wywołanie powyższej metody bez podania argumentów spowoduje przekazaniem nazwy metody, która wywołała Print oraz ścieżki pliku w którym znajduje się ta metoda:

Print();

Jeśli Print został wywołany w metodzie Main wtedy methodName zawiera tekst “Main”.

Jak to jednak może się przydać w praktyce? Pierwsza sprawa to loggery – w łatwy sposób można zapisać nazwę pliku oraz stacktrace. Dla programistów WPF oraz ViewModel jednak warto pokazać pewny przykład, który ułatwia implementacje INotifyPropertyChanged. Jedną z możliwych implementacji w “dawnych” czasach była:

public class ViewModel : INotifyPropertyChanged
{
   private string _text;
   public string Text
   {
       get { return _text; }
       set
       {
           _text = value;
           OnPropertyChanged("Text");

       }
   }
   virtual protected void OnPropertyChanged(string propName)
   {
       if (PropertyChanged != null)
       {
           PropertyChanged(this, new PropertyChangedEventArgs(propName));
       }
   }
   public event PropertyChangedEventHandler PropertyChanged;
}

Czasami programiści zamiast przekazywać po prostu string wykorzystywali lambda. W C# 5.0 można jednak wykorzystać wspominany atrybut:

public class ViewModel : INotifyPropertyChanged
{
   private string _text;
   public string Text
   {
       get { return _text; }
       set
       {
           _text = value;
           RaisePropertyChanged();

       }
   }
   virtual protected void RaisePropertyChanged([CallerMemberName]string propName=null)
   {
       if (PropertyChanged != null)
       {
           PropertyChanged(this, new PropertyChangedEventArgs(propName));
       }
   }
   public event PropertyChangedEventHandler PropertyChanged;
}

Atrybut spowoduje przekazane do propName tekstu “Text”. Moim zdaniem łatwe i eleganckie – nie trzeba dłużej na sztywno przekazywać stringów albo korzystać z nieco wolniejszej lambdy.

Code review: bezpieczny dostęp do słownika

Posted April 28th, 2012 by Piotr Zieliński
Categories: C#, Patterns & Practices

Często widzę następujący kod:

private  Dictionary<string,string> _dictionary=new Dictionary<string, string>();
//......
string value = _dictionary["Key"];

Oczywiście jeśli mamy pewność, że zawsze jest klucz nie ma z tym problemu. Czasami jednak takiej pewności nie ma i należy zwrócić NULL albo stworzyć dany element w słowniku. Wtedy zaczynają się problemy bo najczęściej jest to dokonywane za pomocą:

if (_dictionary.ContainsKey("Key"))
    return _dictionary["Key"];
//else
return null;

Powyższy kod będzie musiał dwa razy dokonać przeszukiwania słownika. Raz aby wykonać ContainsKey a potem drugi raz aby zwrócić element. Łączna złożoność to O(2). Jest to czas optymistyczny ponieważ gdy występują kolizje wtedy proces się wydłuża.  Szybszym i lepszym rozwiązaniem jest użyte metody TryGetValue:

string value;
_dictionary.TryGetValue("Key", out value);

Złożoność wynosi O(1). TryGetValue sprawdza czy klucz istnieje, jeśli tak to zwraca jego wartość w przeciwnym wypadku value wynosi NULL. W przeciwieństwie do zwykłego dostępu nie jest wyrzucany wyjątek. Jeśli zatem w pętli (np. w pętli renderującej) musimy dokonywać częstych operacji na słowniku to różnica między O(1) a O(2) ma już znaczenie bo w końcu może to być O(5000) a O(10000) – dwukrotnie szybsze rozwiązanie.