Po naszej sesji pojawiły się dwa pytania, na które udzieliliśmy odpowiedzi, której nie byliśmy do końca pewni. Oczywiście zaznaczyliśmy, że najlepiej jest to przetestować i zobaczyć jak zachowa się telefon. Wyniki potwierdziły nasze odpowiedzi.

Pytanie 1

Pierwsze pytanie dotyczyło tego, czy nie wystarczy jedynie zamienić kolejności pętli, aby uzyskać podobne przyspieszenie działania programu, bez wyciągania operacji wyliczenia indeksu. Wtedy nasz kod zmieni się z poniższego:

public void Convert1(WriteableBitmap bitmap)
{
  int[] grayScale = new int[bitmap.PixelHeight * bitmap.PixelWidth];
  for (int i = 0; i < bitmap.PixelWidth; i++)
  {
    for (int j = 0; j < bitmap.PixelHeight; j++)
    {
      Color currentColor = this.ConvertToColor(
          bitmap.Pixels[i + (bitmap.PixelHeight * j)]);
      Color grayColor = this.ToGrayScale(currentColor);
      grayScale[i + (bitmap.PixelHeight * j)] = this.ToIntFromColor(grayColor);
    }
  }
}

Na następujący:

public void Convert2(WriteableBitmap bitmap)
{
  int[] grayScale = new int[bitmap.PixelHeight * bitmap.PixelWidth];
  for (int j = 0; j < bitmap.PixelHeight; j++) 
  {
    for (int i = 0; i < bitmap.PixelWidth; i++)
    {
      Color currentColor = this.ConvertToColor(
          bitmap.Pixels[i + (bitmap.PixelHeight * j)]);
      Color grayColor = this.ToGrayScale(currentColor);
      grayScale[i + (bitmap.PixelHeight * j)] = this.ToIntFromColor(grayColor);
    }
  }
}

Dla przypomnienia podam jeszcze zaproponowaną przez nas wersję pierwszego kroku optymalizacji:

public void Convert3(WriteableBitmap bitmap)
{
  int[] grayScale = new int[bitmap.PixelHeight * bitmap.PixelWidth];
  for (int j = 0; j < bitmap.PixelHeight; j++) 
  {
    int heightIndex = bitmap.PixelHeight * j;
    for (int i = 0; i < bitmap.PixelWidth; i++)
    {
      int pixelIndex = i + heightIndex;
      Color currentColor = this.ConvertToColor(bitmap.Pixels[pixelIndex]);
      Color grayColor = this.ToGrayScale(currentColor);
      grayScale[pixelIndex] = this.ToIntFromColor(grayColor);
    }
  }
}

Jak pokazują testy na urządzeniach – na każdym z nich zostało wykonane 1000 pomiarów – czas uległ skróceniu, ale nieznacznie. Poniżej zamieszczam tabelę z wynikami:

 

Metoda Nokia Lumia 800 HTC Titan HTC 7 Pro
Convert1 236,57 ms 263,42 ms 393,08 ms
Convert2 223,17 ms 234,88 ms 350,30 ms
Convert3 179,32 ms 170,63 ms 253,50 ms

 

Na podstawie otrzymanych wyników możemy zaobserwować, że pierwsza modyfikacja skróciła czas wykonywania metody średnio o 9%. Natomiast jednokrotne obliczenie indeksu wraz z redukcją ilości wykonywanych operacji mnożenia oraz zamiana kolejności wykonywania pętli skraca czas wykonywania metody średnio o 32%.

Pytanie 2

Drugie pytanie dotyczyło finalnej implementacji:

public void Convert4(WriteableBitmap bitmap)
{
  int[] pixels = bitmap.Pixels;
  int length = pixels.Length;
  int[] grayScale = new int[length];
  for (int i = 0; i < length; i++)
  {
    int currentPixel = pixels[i];
    int a = (byte)(currentPixel >> 24);
    int r = (byte)(currentPixel >> 16);
    int g = (byte)(currentPixel >> 8);
    int b = (byte)currentPixel;
    int gray = (6966 * r + 23436 * g + 2366 * b) >> 15;
    int grayColor = (a << 24) | (gray << 16) | (gray << 8) | gray;
    grayScale[i] = grayColor;
  }
}

Pojawił się pomysł, aby wyciągnąć deklarację zmiennych [mark]int[/mark] przez pętlę i zobaczyć co się stanie:

public void Convert5(WriteableBitmap bitmap)
{
  int[] pixels = bitmap.Pixels;
  int length = pixels.Length;
  int[] grayScale = new int[length];
  int currentPixel, a, r, g, b, gray, grayColor;
  for (int i = 0; i < length; i++)
  {
    currentPixel = pixels[i];
    a = (byte)(currentPixel >> 24);
    r = (byte)(currentPixel >> 16);
    g = (byte)(currentPixel >> 8);
    b = (byte)currentPixel;
    gray = (6966 * r + 23436 * g + 2366 * b) >> 15;
    grayColor = (a << 24) | (gray << 16) | (gray << 8) | gray;
    grayScale[i] = grayColor;
  }
}

W tym przypadku, nie udało się zaobserwować żadnej zmiany. Zarówno czas wykonania funkcji, jak również zużycie pamięci pozostało to samo.