2009-06-12

DebuggerTypeProxyAttribute

Atrybut ten może zostać nałożony na klasę, strukturę i zestaw. Reguły definiowane przez ten atrybut dotyczą zarówno okienek wyskakujących po najechaniu na zmienną, jak i tego co pojawia się w Watch, Autos, Locals. Atrybut ten mówi debugerowi by podczas debugowania danej klasy pokazał nam zawartość innej klasy. Jedynym wymaganiem względem tej klasy jest by istniał jej konstruktor przyjmujący jako parametr obiekt klasy, którą chcemy substytuować. Przykład zastosowania:
public class List
{
  public class Node
  {
      [System.Diagnostics.DebuggerBrowsable(
          System.Diagnostics.DebuggerBrowsableState.Never)]
      private Node m_next;

      public readonly int Value;

      public Node(int a_value)
      {
          Value = a_value;
      }

      public Node Next
      {
          get
          {
              return m_next;
          }
      }

      protected internal void Add(Node a_node)
      {
          if (m_next == null)
              m_next = a_node;
          else
              m_next.Add(a_node);
      }
  }

  [System.Diagnostics.DebuggerBrowsable(
      System.Diagnostics.DebuggerBrowsableState.Never)]
  private Node m_head;

  public void Add(Node a_node)
  {
      if (m_head == null)
          m_head = a_node;
      else
          m_head.Add(a_node);
  }

  public Node Head
  {
      get
      {
          return m_head;
      }
  }
}

public class DebuggerTypeProxyTest
{
  public void Test()
  {
      List list = new List();

      list.Add(new List.Node(5));
      list.Add(new List.Node(6));
      list.Add(new List.Node(7));

      list.ToString();
  }
}
Podgląd list wygląda tak: Po uzupełnieniu kodu odpowiednio:
public class ListProxy
{
  private List m_list;

  public ListProxy(List a_list)
  {
      m_list = a_list;
  }

  private IEnumerable<int> getEnum()
  {
      List.Node node = m_list.Head;

      while (node != null)
      {
          List.Node n = node;
          node = node.Next;
          yield return n.Value;
      }
  }

  [System.Diagnostics.DebuggerBrowsable(
      System.Diagnostics.DebuggerBrowsableState.RootHidden)]
  public int[] List
  {
      get
      {
          return getEnum().ToArray();
      }
  }
}

[System.Diagnostics.DebuggerTypeProxy(typeof(ListProxy))]
public class List
{
  public class Node
  {
      [System.Diagnostics.DebuggerBrowsable(
          System.Diagnostics.DebuggerBrowsableState.Never)]
      private Node m_next;

      public readonly int Value;

      public Node(int a_value)
      {
          Value = a_value;
      }

      public Node Next
      {
          get
          {
              return m_next;
          }
      }

      protected internal void Add(Node a_node)
      {
          if (m_next == null)
              m_next = a_node;
          else
              m_next.Add(a_node);
      }
  }

  [System.Diagnostics.DebuggerBrowsable(
      System.Diagnostics.DebuggerBrowsableState.Never)]
  private Node m_head;

  public void Add(Node a_node)
  {
      if (m_head == null)
          m_head = a_node;
      else
          m_head.Add(a_node);
  }

  public Node Head
  {
      get
      {
          return m_head;
      }
  }
}

public class DebuggerTypeProxyTest
{
  public void Test()
  {
      List list = new List();

      list.Add(new List.Node(5));
      list.Add(new List.Node(6));
      list.Add(new List.Node(7));

      list.ToString();
  }
}
W debugerze zobaczymy: Dzięki użyciu tego atrybutu możemy kompleksowo zmienić sposób pokazywania zawartości klasy podczas debugowania. Co ważniejsze dane możemy wizualizować zupełnie inaczej niż wyglądają w strukturze obiektu. Jeśli obiekt implementuje strukturę listę (jak w powyższym przykładzie), możemy je pokazać jako tablicę, nie zmuszając użytkownika do klikania poprzez wszystkie elementy listy. Możemy zmienić wizualizację naszych klas, a także klas z biblioteki .NET (jak i dowolnie innej biblioteki). Możemy też powiedzieć, że atrybut ten pozwala nam rozdzielić kod dla debugera od kodu klasy. Jeśli klasa proxy musi się dostać do składników prywatnych klasy debugowanej najlepiej zdefiniować ją jako klasę wewnętrzną klasy debugowanej. Reszta informacji dla tego atrybutu podaje w postaci pytań i odpowiedzi. Jak można się przekonać niżej czasami coś nie działa, czasami działa inaczej niż powinno. Chyba wszystkie te sytuacje podane niżej kiedy coś nie działa, a powinno, można zakwalifikować jako błędy. Jak nałożyć atrybut na zestaw ? Podobnie jak dla DebugerDisplayAttribute. Więcej szczegółów można znaleźć w tamtym wpisie. Przykład:
[assembly: System.Diagnostics.DebuggerTypeProxy(typeof(TestAppNET.Priv.TestClassProxy), Target = typeof(TestAppNET.TestClass))]
Czy klasa proxy może być klasą wewnętrzną ? Może. Czy klasa proxy może zostać zastosowana dla klasy debugowanej generycznej? Tak. Przykład:
public class TestClassProxy<K,V>
{
  public K x;
  public V y;
  public string str;

  public TestClassProxy(TestClass<K,V> a_tc)
  {
      x = a_tc.x;
      y = a_tc.y;
      str = "test " + a_tc.str;
  }
}

[System.Diagnostics.DebuggerTypeProxy(typeof(TestAppNET.TestClassProxy<,>))]
public class TestClass<K,V>
{
  public K x;
  public V y;
  public string str;
}
Dobrze jest zwrócić uwagę jak definiuje się typ generyczny w atrybucie DebuggerTypeProxy. Teraz jeśli zamiast typu w atrybucie podamy stringa przykład przestanie działać:
[System.Diagnostics.DebuggerTypeProxy("TestAppNET.TestClassProxy<,>")]
Proxy przestanie działać też dla poniższego przykładu kiedy zamkniemy typ generyczny dla proxy:
public class TestClassProxy
{
  public int x;
  public string y;
  public string str;

  public TestClassProxy(TestClass a_tc)
  {
      x = a_tc.x;
      y = a_tc.y;
      str = "test " + a_tc.str;
  }
}

public class BaseClass<K, V>
{
  public K x;
  public V y;
}

[System.Diagnostics.DebuggerTypeProxy("TestAppNET.TestClassProxy")]
public class TestClass : BaseClass<int, string>
{
  public string str;
}
Jak musi być widoczność klasy proxy ? Nie ma znaczenia. Oczywiście podając klasy debugowaną jako type musi ona być widoczna. Podając ją jako string należy zwrócić uwagę, że jeśli klasa proxy jest klasą wewnętrzną w jej pełnej nazwie dla składników klasy wewnętrznej zamiast kropki używamy plusa:
[System.Diagnostics.DebuggerTypeProxy("TestAppNET.SomeClass+InnerTestClassProxy")]
Dla atrybutu dla zestawu kiedy zamiast typu użyłem stringa proxy przestało działać (niezależnie dla którego parametru konstruktora atrybutu). Jaka musi być widoczność klasy debugowanej przy definiowaniu atrybutu dla zestawu ? Zgodnie z tym co powiedziałem w powyżej musi być widoczna, by móc ją podać jako typ. Czy klasa proxy może dziedziczyć po innej klasie, implementować interfejsy ? Nie ma problemu. Czy jeśli klasa proxy dziedziczy po innej klasie to czy jej pola i właściwości też są pokazywane podczas debugowania ? Tak, w rozwinięciu klasy. Co jeśli proxy nałożymy na klasę bazową klasy, której podgląd będziemy chcieli zobaczyć ? Proxy dla tej klasy zostanie uruchomione. Co jeśli konstruktor proxy jako parametru wymaga klasy bazowej naszej debugowanej klasy lub interfejsu, który nasza klasa debugowana implementuje ? Jeśli takie proxy jest nałożone na naszą klasę, podczas debugowania klasy proxy zostanie użyta. Czy możemy nasza klasę proxy wykorzystać dla dwóch różnych klas debugowanych ? Czyli nasza klasa proxy posiada dwa konstruktory dla dwóch różnych klas debugowanych. Możemy tak zrobić. Czy składniki prywatne klasy proxy są wyświetlane ? Nie, pokazywane są tylko składniki publiczne i protected (pola i właściwości). Co się stanie kiedy wpis w atrybucie DebuggerTypeProxyAttribute jest błędny ? Nic się nie dzieje, nie ma żadnego komunikatu o błędzie. Czy po rekompilacji kodu w którym znajduje się klasa proxy, by efekt był widoczny, a atrybut został nałożony na zestaw, trzeba zrestartować VS ? Nie. Dla ścisłości atrybut na zestaw został już nałożony, a VS zrestartowane, teraz tylko rekompilujemy klasę proxy. Czy klasa proxy i klasa debugowana mogą się znajdować w innych zestawach ? Mogą, ale aby uniknąć circular references, w innym zestawie może się znajdować tylko debugowana klasa. Po umieszczeniu tam proxy kodu nie uda nam się tego skompilować. Co jeśli dla klasy debugowanej są ustanowione jednocześnie proxy w zestawie i klasie debugowanej ? W przeciwieństwie do tego jak działa atrybut DebugerDisplayAttribute tutaj proxy zdefiniowane w klasie debugowanej zostanie zignorowane. Co jeśli zestaw z klasa proxy załadujemy dynamicznie ? Załadować się załadował, ale debug nie uwzględnił klasy proxy. Co jeśli klasa debugowana jest w innym zestawie ? Nie ma żadnego problemu o ile nie jest ładowana dynamicznie. Przy ładowaniu dynamicznym klasa proxy się nie skompiluje z powodu braku referencji do klasy debugowanej. Patrz pytanie wyżej (klasa proxy nie może być ładowana dynamicznie). Jak debugować klasę proxy ? Można wyświetlać okienka, wydawać dźwięki, logować do pliku. Nie udało mi się skorzystać z konsoli VS dostępnej w trybie debug. Kiedy tworzony jest obiekt klasy proxy ? Dla Watch, Autos, Locals dla każdego wymaganego odświeżenia wartości (krok debugera albo zatrzymanie wykonywania programu), o ile jest rozwinięty. Dla podglądy stworzenie obiektu proxy następuje w momencie rozwinięcia obiektu. Ponieważ obiekt klasy proxy tworzony jest za każdym razem należy zwrócić uwagę na szybkość działania klasy proxy. Co się dzieje kiedy na klasę proxy nałożymy jakieś atrybuty modyfikujące wyświetlanie zawartości klasy w debugerze ? Dla klasy proxy DebugerDisplayAttribute nie jest uwzględniany. To samo tyczy się zastosowania metody ToString() do pokazywania informacji o obiekcie podczas debugowania. Więcej informacji można znaleźć przy opisie atrybutu DebugerDisplayAttribute. Atrybut DebuggerBrowsable działa.

2009-06-08

.NET 3.5, Java, Visual Studio 2008 C++ - prędkość działania na tablicach

Testy były robione na 64 bitowej Viście. Testy C++ na Visual Studio 2008 SP1. Testy C# na Visual Studio 2008 SP1 (.NET 3.5). Testy Javy na JRE 1.6.0 (IDE to Netbeans). Wszystkie programy kompilowałem w Release (o ile była taka możliwość). Wszystkie programy były uruchamiane samodzielnie, nie z IDE. Java i C++ nie wykazywały spowolnienia podczas uruchamiania z IDE. C# w pierwszym teście dwie pierwsze metody potrafił wykonać 4 razy wolniej. Mając na myśli C++ mam na myśli język niezarządzalny, bez Garbage Collectora. Dla precyzyjnego pomiaru czasu w C++ wykorzystałem klasę CPreciseTimer. C#, .NET 3.5, kompilator do kodu pośredniego, kompilator kodu pośredniego do maszynowego - mam nadzieję, że wiadomo jakie są różnicę, bo dalej nie będę taki precyzyjny (chyba już nawet w tytule postu nie jestem). Podobnie zresztą można powiedzieć o Javie. W przypadku C++ testy były robione podczas kompilacji na 32 i 64 bity. To samo tyczy się C#. Tutaj kod pośredni wyglądał identycznie (przynajmniej dla pierwszego testu), różnica brała się pewnie podczas kompilacji kodu pośredniego. W Javie kompilacja jest chyba zawsze na 32 bity. Podczas testów wyłączałem oszczędzanie energii polegające na spowolnieniu częstotliwości procesora kiedy nic się nie dzieje. Dostęp do dużej tablicy Test polega na stworzeniu wielkiej tablicy, zapełnieniu ją losowymi wartościami i zsumowaniu ich w prostej pętli. Tablica składa się z 100 milionów intów. Tablica jest na tyle duża by pominąć efekt pamięci cache procesora na jej przetwarzanie. Java:
public class ArraySpeedTest1
{
    private int[] big_array;

    public ArraySpeedTest1()
    {
        big_array = new int[1000*1000*100];
        java.util.Random r = new java.util.Random();
        for (int i = 0; i < big_array.length; i++)
            big_array[i] = r.nextInt(5);
    }

    private int SpeedTest1()
    {
        int r = 0;

        for (int i = 0; i < big_array.length; i++)
            r += big_array[i];

        return r;
    }

    private int SpeedTest2()
    {
        int r = 0;

        for (int n : big_array)
            r += n;

        return r;
    }

    public void Test()
    {
        long t = System.nanoTime();
        int r = SpeedTest1();
        t = System.nanoTime() - t;
        System.out.println(String.format("SpeedTest1, sum: %d, time: %d [ms]", r, t / 1000000));

        t = System.nanoTime();
        r = SpeedTest2();
        t = System.nanoTime() - t;
        System.out.println(String.format("SpeedTest2, sum: %d, time: %d [ms]", r, t / 1000000));
    }
}
Wynik: SpeedTest1, sum: 199998357, time: 244 [ms] SpeedTest2, sum: 199998357, time: 168 [ms] C++:
class ArraySpeedTest1
{
    private: int BIG_ARRAY_LENGTH;
    private: int* big_array;

    public: ArraySpeedTest1()
    {
        BIG_ARRAY_LENGTH = 1000*1000*100;

        big_array = new int[BIG_ARRAY_LENGTH];

        srand((unsigned)time(NULL));
        for (int i=0; i<BIG_ARRAY_LENGTH; i++)
            big_array[i] = rand() % 4;
    }

    public: ~ArraySpeedTest1()
    {
        delete big_array;
    }

    private: int SpeedTest1()
    {
        int r = 0;
        int* p = big_array;
        for (int i=0; i<BIG_ARRAY_LENGTH; i++)
            r += *p++;
        return r;
    }

    private: int SpeedTest2()
    {
        int r = 0;
        for (int i=0; i<BIG_ARRAY_LENGTH; i++)
            r = r + big_array[i];
        return r;
    }

    public: void Test()
    {
        CPreciseTimer* timer = new CPreciseTimer();
        timer->StartTimer();
        int r = SpeedTest1();
        timer->StopTimer();
        std::cout << "SpeedTest1, sum: " << r << ", time: " << timer->GetTime() / 1000 << "[ms]\r\n";
        delete timer;

        timer = new CPreciseTimer();
        timer->StartTimer();
        r = SpeedTest2();
        timer->StopTimer();
        std::cout << "SpeedTest2, sum: " << r << ", time: " << timer->GetTime() / 1000 << "[ms]\r\n";
        delete timer;
    }
};
Wyniki: 32 bity: SpeedTest1, sum: 449948271, time: 82[ms] SpeedTest2, sum: 449948271, time: 82[ms] 64 bity: SpeedTest1, sum: 449948271, time: 82[ms] SpeedTest2, sum: 449948271, time: 82[ms] C#:
public class ArraySpeedTest1
{
    private int[] big_array = new int[1000 * 1000 * 100];

    public ArraySpeedTest1()
    {
        Random r = new Random();
        for (int i = 0; i < big_array.Length; i++)
            big_array[i] = r.Next(5);
    }

    private int SpeedTest1()
    {
        int r = 0;

        for (int i = 0; i < big_array.Length; i++)
            r += big_array[i];

        return r;
    }

    private int SpeedTest2()
    {
        int r = 0;

        foreach (int n in big_array)
            r += n;

        return r;
    }

    private int SpeedTest3()
    {
        return big_array.Sum();
    }

    public void Test()
    {
        Action<Func<int>, string> d = (f, s) =>
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            int r = f();
            sw.Stop();
            System.Console.WriteLine("{0}, sum: {1}, time: {2} [ms]", s, r, sw.ElapsedMilliseconds);
        };

        d(SpeedTest1, "SpeedTest1");
        d(SpeedTest2, "SpeedTest2");
        d(SpeedTest3, "SpeedTest3");
    }
}
Wyniki 32 bity: SpeedTest1, sum: 199995106, time: 111 [ms] SpeedTest2, sum: 199995106, time: 105 [ms] SpeedTest3, sum: 199995106, time: 1064 [ms] 64 bity: SpeedTest1, sum: 199981552, time: 130 [ms] SpeedTest2, sum: 199981552, time: 134 [ms] SpeedTest3, sum: 199981552, time: 1028 [ms] Komentarz: W C# kod 32 bitowy jest wykonywany szybciej niż kod 64 bitowy. W C++ nie widać różnicy w prędkości pomiędzy kodem 64 bitowym, a 32 bitowym. Wygląda na to, że optymalizacja dla 64 bitowego kodu nie jest najlepsza dla C#. Co do różnic w prędkości widzimy, że kod najszybciej wykonuje się w C++ (obie wersje tak samo szybkie). Dalej jest C#. A na końcu Java (co ciekawe wersja z wykorzystaniem enumeracji jest wyraźnie szybsza). Dla C# wersja z wykorzystaniem LINQ choć ma najelegantszy kod to jest najwolniejsza. Dla C++ widzimy, że ręczna zabawa na wskaźnikach nie zawsze daje efekty. Możemy uśrednić wyniki: C++: 85 [ms], C#: 115[ms], Java: 200[ms] Wielokrotny dostęp do małej tablicy Ilość sumowanych wartości jest taka sama, tylko, że zamiast sumować tablice 100 milionów elementów, sumujemy 100 tysięcy razy tablice 1000 elementów. Dzięki temu korzystamy w pełni z pamięci cache procesora. Java:
public class ArraySpeedTest2
{
    private int OUTER_LOOP;
    private int[] big_array;

    public ArraySpeedTest2()
    {
        OUTER_LOOP = 1000*100;
        big_array = new int[1000];
        java.util.Random r = new java.util.Random();
        for (int i = 0; i < big_array.length; i++)
            big_array[i] = r.nextInt(5);
    }

    private int SpeedTest1()
    {
        int r = 0;

        for (int j=0; j<OUTER_LOOP; j++)
            for (int i = 0; i < big_array.length; i++)
                r += big_array[i];

        return r;
    }

    private int SpeedTest2()
    {
        int r = 0;

        for (int j=0; j<OUTER_LOOP; j++)
            for (int n : big_array)
                r += n;

        return r;
    }

    public void Test()
    {
        long t = System.nanoTime();
        int r = SpeedTest1();
        t = System.nanoTime() - t;
        System.out.println(String.format("SpeedTest1, sum: %d, time: %d [ms]", r, t / 1000000));

        t = System.nanoTime();
        r = SpeedTest2();
        t = System.nanoTime() - t;
        System.out.println(String.format("SpeedTest2, sum: %d, time: %d [ms]", r, t / 1000000));
    }
}
Wyniki: SpeedTest1, sum: 201900000, time: 224 [ms] SpeedTest2, sum: 201900000, time: 136 [ms] C++:
class ArraySpeedTest2
{
    private: int OUTER_LOOP;
    private: int BIG_ARRAY_LENGTH;
    private: int* big_array;

    public: ArraySpeedTest2()
    {
        OUTER_LOOP = 1000*100;
        BIG_ARRAY_LENGTH = 1000;

        big_array = new int[BIG_ARRAY_LENGTH];

        srand((unsigned)time(NULL));
        for (int i=0; i<BIG_ARRAY_LENGTH; i++)
            big_array[i] = rand() % 4;
    }

    public: ~ArraySpeedTest2()
    {
        delete big_array;
    }

    private: int SpeedTest1()
    {
        int r = 0;
        
        int* p;
        for (int j=0; j<OUTER_LOOP; j++)
        {
            p = big_array;
            for (int i=0; i<BIG_ARRAY_LENGTH; i++)
                r += *p++;
        }
        return r;
    }

    private: int SpeedTest2()
    {
        int r = 0;
        for (int j=0; j<OUTER_LOOP; j++)
            for (int i=0; i<BIG_ARRAY_LENGTH; i++)
                r = r + big_array[i];
        return r;
    }

    public: void Test()
    {
        CPreciseTimer* timer = new CPreciseTimer();
        timer->StartTimer();
        int r = SpeedTest1();
        timer->StopTimer();
        std::cout << "SpeedTest1, sum: " << r << ", time: " << timer->GetTime() / 1000 << "[ms]\r\n";
        delete timer;

        timer = new CPreciseTimer();
        timer->StartTimer();
        r = SpeedTest2();
        timer->StopTimer();
        std::cout << "SpeedTest2, sum: " << r << ", time: " << timer->GetTime() / 1000 << "[ms]\r\n";
        delete timer;
    }
};
Wyniki: 32 bity: SpeedTest1, sum: 155800000, time: 45[ms] SpeedTest2, sum: 155800000, time: 45[ms] 64 bity: SpeedTest1, sum: 155800000, time: 45[ms] SpeedTest2, sum: 155800000, time: 45[ms] C#:
public class ArraySpeedTest2
{
    private int OUTER_LOOP = 1000 * 100;
    private int[] big_array = new int[1000];

    public ArraySpeedTest2()
    {
        Random r = new Random();
        for (int i = 0; i < big_array.Length; i++)
            big_array[i] = r.Next(5);
    }

    private int SpeedTest1()
    {
        int r = 0;

        for (int j = 0; j < OUTER_LOOP; j++)
            for (int i = 0; i < big_array.Length; i++)
                r += big_array[i];

        return r;
    }

    private int SpeedTest2()
    {
        int r = 0;

        for (int j = 0; j < OUTER_LOOP; j++)
            foreach (int n in big_array)
                r += n;

        return r;
    }

    private int SpeedTest3()
    {
        int r = 0;

        for (int j = 0; j < OUTER_LOOP; j++)
            r += big_array.Sum();

        return r;
    }

    public void Test()
    {
        Action<Func<int>, string> d = (f, s) =>
        {
            System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            int r = f();
            sw.Stop();
            System.Console.WriteLine("{0}, sum: {1}, time: {2} [ms]", s, r, sw.ElapsedMilliseconds);
        };

        d(SpeedTest1, "SpeedTest1");
        d(SpeedTest2, "SpeedTest2");
        d(SpeedTest3, "SpeedTest3");
    }
}
Wyniki: 32 bity: SpeedTest1, sum: 195800000, time: 93 [ms] SpeedTest2, sum: 195800000, time: 90 [ms] SpeedTest3, sum: 195800000, time: 1051 [ms] 64 bity: SpeedTest1, sum: 200200000, time: 117 [ms] SpeedTest2, sum: 200200000, time: 116 [ms] SpeedTest3, sum: 200200000, time: 1013 [ms] Widzimy, że kod w C++ przyspieszył dwukrotnie, zaś kod w C# i w Javie przyspieszył nieznacznie. Wynika to prawdopodobnie z ilości instrukcji procesora na pojedyńczą pętle. Kiedy cała pętla zaczyna korzystać z pamięci cache, do której dostęp jest szybszy niż do pamięci zewnętrznej, to kod w C++, który ma najmniej instrukcji na pętle zaczyna działać najszybciej. Prawdopodobnie jest tak, że w C++ dla pierwszego testu to procesor czeka na dane z pamięci, a wykonywanie kodu stoi. W C# i Javie nie widzimy tak wielkiego przyspieszenia, gdyż pomimo dostępu do pamięci cache procesor i tak ma dużo rozkazów do przetworzenia. Możemy uśrednić wyniki: C++: 45 () [ms], C#: 90[ms], Java: 170[ms]. Te same testy uruchomione na domowym komputerze dla C++ i C# przyspieszyły do 30 [ms] i 60 [ms]. Stosunek przyspieszenia jest idealnie proporcjonalny do prędkości zegara procesora. Dla porównania wersja pierwsza działająca na dużej tablicy w ogóle nie przyspieszyła. Widzimy, że kiedy dane są w pamięci cache liczy się tylko prędkość procesora. W pierwszym przypadku prawdopodobnie znaczenie ma prędkość pamięci (u mnie w obu przypadkach taka sama patrząc na indeks szybkości liczony przez Viste). Kod w C++ wykonał się w drugim przypadku w 45 [ms]. Prędkość procesora to 2,26 [GHz]. W tym czasie przetworzył 100M elementów tablicy. Ile elementów tablicy przetworzył by w sekundę? - 2222M. Czyli możemy powiedzieć, że efektywna częstotliwość przetwarzania tablicy to 2,22 [GHz]. A taka pętla to na pewno jest kilka instrukcji procesora. Tutaj widać jak działają współczesne procesory, jak potrafią optymalizować wykonanie kodu. I widać (może trochę mniej), jak skomplikowanym zagadnieniem jest optymalizacja kodu pod współczesne procesory - czym na niższy poziom schodzimy tym efekty naszego niby-przyspieszania mogą być coraz bardziej zaskakujące. Potrzebna jest tutaj dokładna wiedza jak działa procesor niestety...