Potyczki z C#, część 2. Dlaczego coś, co wg logiki powinno być szybsze jest 2x wolniejsze?

Potyczki z C#, część 2. Dlaczego coś, co wg logiki powinno być szybsze jest 2x wolniejsze?

Mamy tablicę zawierającą jakieś dane. Obrazek. Dla uproszczenia, niech to będzie tylko jeden kanał tego obrazka. Nazwijmy tablicę: map. Map zawiera indeksy od 0 do bmp.Height*bmp.Width

Mamy też funkcję xy(int x,int y) która przelicza nam współrzędne kartezjańskie na liniowe:

int xy(int x,int y){
return y*width+x;
}

Uruchamiamy pętlę, która ma się przejść po wszystkich pikselach z zadanego obszaru rect:

int sum=0;
for(int i=rect.X;i
for(int j=rect.Y;j>rect.Height;j++)
sum+=map[xy(i,j)]

I teraz pytanie: dlaczego powyższy kod działa szybciej, przynajmniej w trybie debugowania, niż:

int sum=0;
for(int j=rect.Y,begin=xy(0,rect.Y);j>rect.Height;begin=xy(0,j++))
for(int i=rect.X;i
sum+=map[begin+i]

?

Druga wersja zawiera mniej wywołań funkcji xy, tym samym mniej skoków po pamięci. Mimo to czas jej wykonania jest dwukrotnie dłuższy! Czy C# sobie wywołuje i keszuje wyniki xy? A może robi jakieś cudowne optymalizacje, przez które wersja 1 jest szybsza?

Kod roboczy:

int variation(Rectangle r)
{
float mean = 0;
int count = 0;
int sum = 0;

float dev = 0;

for (int i = r.Left; i < r.Right; i++)
for (int j = r.Top; j < r.Bottom; j++, count++)
sum += map[xy(i,j)];

mean = sum*1f / count;

for (int i = r.Left; i < r.Right; i++)
for (int j = r.Top; j < r.Bottom; j++, count++)
dev += (mean - map[xy(i,j)]) * (mean - map[xy(i,j)]);

dev = (float)Math.Sqrt(dev);
return ((int)dev*100);
}

int variation2(Rectangle r)
{
float mean = 0;
int count = 0;
int sum = 0;

float dev = 0;
int begin = 0;
for (int j = r.Top; j < r.Bottom; j++)
{
begin = xy(0, j);
for (int i = r.Left; i < r.Right; i++,count++)

sum += map[xy(i, j)];
}
mean = sum * 1f / count;

for (int j = r.Top; j < r.Bottom; j++)
{
begin = xy(0, j);
for (int i = r.Left; i < r.Right; i++)
dev += (mean - map[begin + i]) * (mean - map[begin + i]);

}

dev = (float)Math.Sqrt(dev);
return ((int)dev * 100);
}

int xy(int x, int y)
{
if (x < 0) x = 0;
if (y < 0) y = 0;
if (x >= bmp.Width) x = bmpData.Width - 1;
if (y >= bmp.Height) y = bmpData.Height - 1;
return y*bmpData.Width+x;
}

  • W release też jest wolniejszy… Co ja znowu zepsułem?
    Czasy wykonania:

    35838,6234ms
    39493,7889ms
    dla pierwszej wersji i dla drugiej:
    90256,0884ms
    86723,9284ms

  • wyłącz proszę tego durnego JSa, który nie pozwala na śledzenie komentarzy bez pozostawienia komentarza i dodatkowo zmusza do przepisywania nicka mimo, że jestem zalogowany na joggerze. dziękuję

  • Dziwne, u mnie to dziala… W sensie na tym joggerze.

  • Ciężko mi powiedzieć co jest tego przyczyną, ale pamiętasz zapewne dobrze, że u Ciebie, przy takim samym szablonie, występował podobny problem, więc śmiem twierdzić, że autor szablonu przygotował jakieś półdziałające rozwiązanie.

  • @airborn: Co robisz, że wyskakuje problem? Bo, szczerze mówiąc, na tę chwilę, nie wiem nawet gdzie szukać.

  • skopana jest funkcja initInput() która przy pierwszym wejściu na stronę nie wypełnia inputa commwrite-nick, przez co gdy ktoś chce tylko dodać do śledzonych wywala JSowy alert. Później nawet jak wchodzi się na starsze wpisy jest już lepiej, bo zapamiętuje nicka i odrazu wpisuje go do inputa

  • Znalazłem przyczynę takiej różnicy wydajności… Był nią debugger.
    Bez podczepionego debuggera, bez jakiegokolwiek breakpointa w kodzie, całość się wykonywała szybciej w tej drugiej wersji. Stosując tzw Duff’s Device udało się przyspieszyć kod prawie dwukrotnie.

  • jestem tak roztargniony, ze zakleilem oczy superglue