kategorie: ANSI C, Asembler, C++
[#1] [C, C++] Czy będzie róznica w wydajności w tych dwóch przykładach?
cześć,
oto dwa proste przykłady, róznice są w strukturze.
Drugie rozwiązanie na pewno zajmie mniej pamieci, ale to akurat bez znaczenia.
Chciałbym się dowiedzieć czy pod względem wydajności będą się one znacząco różnić?
Wydaje mi sie że drugie powinno być "wolniejsze"..
Chodzi tu o częsty dostep do wartości cx i cy.

Wariant 1.

struct sCell
{
  int x,y,px,py;
  int type;

  float cx, cy;  // przechowują konkretną wartość
}

sCell cells[4096];

main()
{
....
   loop
  {
   ...

   float cx = cells[index].cx;
   float cy = cells[index].cy;
   ...   
  }
...
}



Wariant 2.

float numbers[40][2] = { {1.0f, 0.0f}, {1.0f, 0.1f}, {1.0f, 0.2f}, .... {0.4f, 1.0} }

struct sCell
{
  int x,y,px,py;
  int type;

  int cx, cy;  // przechowują index do tablicy gdzie znajduje się wartosc
}

sCell cells[4096];

main()
{
....
   loop
  {
   ...

   float cx = numbers[cells[index].cx][0];
   float cy = numbers[cells[index].cy][1];
   ...   
  }
...
}
[wyróżniony] [#2] Re: [C, C++] Czy będzie róznica w wydajności w tych dwóch przykładach?

@mateusz_s, post #1

Jeśli twój kompilator ma możliwość generowania plików assemblera (*.s), to tam sobie możesz podejrzeć ile instrukcji pójdzie na daną funkcję. To jest najbardziej wiarygodne źródło, bo co kompilator, to inna bajka.

Wydaje mi się, że drugi przykład będzie działał tak samo szybko, póki te dodatkowe indeksy są wartościami stałymi, do obliczenia adresu zostanie podstawiony inny adres (tak jakbyś czytał z innej tabeli), co innego, gdyby te dwie wartości [0] i [1] były zmiennymi, wtedy doszłyby instrukcje ładujące wartość tej zmiennej i dodające do adresu.

Ostatnia aktualizacja: 10.01.2021 19:25:43 przez WyciorX
[wyróżniony] [#3] Re: [C, C++] Czy będzie róznica w wydajności w tych dwóch przykładach?

@mateusz_s, post #1

Zbuduj oba warianty, zrób dużą pętlę na różnych liczbach i pomierz wydajność. Albo właśnie output asemblerowy i policz cykle dla danego cpu. Nie ma sensu się takich rzeczy uczyć na pamięć, bo tego typu optymalizacje zmieniają zasadność w zależności od platformy.
[wyróżniony] [#4] Re: [C, C++] Czy będzie róznica w wydajności w tych dwóch przykładach?

@mateusz_s, post #1

Drugi wariant bedzie ze dwa razy wolniejszy, bo zawiera odwolanie do dwoch roznych tablic i dwa dostepy do pamieci na jedna zmienna cx/cy, natomiast pierwszy wariant tylko do jednej tablicy (i jeden dostep do pamieci). Kompilator w tym przypadku wygeneruje krotszy i szybszy kod.
Najlepiej jednak samemu zrobic test czasowy obu wariantow.
[wyróżniony] [#5] Re: [C, C++] Czy będzie róznica w wydajności w tych dwóch przykładach?

@mateusz_s, post #1

Wchodzisz sobie na https://godbolt.org/ i tam wklejasz swój kod, wybierasz kompilator i masz od razu po prawej podgląd wygenerowanego assemblera, najeżdżając na kod w C/C+ będzie się podświetlał też kod po prawej.

Wklej tam swój przykład w tej formie:
#include <stdio.h>

float numbers[4][2] = { {1.0f, 0.0f}, {1.0f, 0.1f}, {1.0f, 0.2f}, {0.4f, 1.0} };
float numbers1[4] = { 1.0f, 1.0f, 1.0f, 0.4f};

struct sCell
{
  int x,y,px,py;
  int type;

  int cx, cy, cz, c1;  // przechowują index do tablicy gdzie znajduje się wartosc
};

struct sCell cells[4096];

int main()
{
    int index;
    float cx, cy, cz, c1;


   while(1)
  {

   cz = numbers1[cells[index].cz];
   c1 = numbers1[cells[index].c1];

    printf("%d", (int) (cz + c1));

   cx = numbers[cells[index].cx][0];
   cy = numbers[cells[index].cy][1];

   printf("%d", (int) (cx + cy));

   
  };
}


Wg tej strony, w obydwu przypadkach będzie tylko jedna instrukcja jeśli wpiszesz w opcjach kompilatora -O3, bez optymalizacji będzie po 11 instrukcji. Kompilator jaki miałem wybrany to GCC x86-64 10.2, kiedyś tam był chyba jeszcze VBCC albo widziałem go na innej, podobnej stronie. Sugerując się powyższym, obydwa warianty będą działały tak samo szybko.

Sytuacja zmieni się, kiedy użyjesz zmiennej jako indeksu zamiast stałej:
#include <stdio.h>

float numbers[4][2] = { {1.0f, 0.0f}, {1.0f, 0.1f}, {1.0f, 0.2f}, {0.4f, 1.0} };
float numbers1[4] = { 1.0f, 1.0f, 1.0f, 0.4f};

struct sCell
{
  int x,y,px,py;
  int type;

  int cx, cy, cz, c1;  // przechowują index do tablicy gdzie znajduje się wartosc
};

struct sCell cells[4096];

int main()
{
    int index;
    int i = 0;
    float cx, cy, cz, c1;


   while(1)
  {
      if(i) i = 0; else i = 1;

   cz = numbers1[cells[index].cz];
   c1 = numbers1[cells[index].c1];

    printf("%d", (int) (cz + c1));

   cx = numbers[cells[index].cx][i];
   cy = numbers[cells[index].cy][i];

   printf("%d", (int) (cx + cy));

   
  };
}


Wtedy generowane są trzy instrukcje przy -O3.

Ostatnia aktualizacja: 11.01.2021 15:34:08 przez WyciorX
[#6] Re: [C, C++] Czy będzie róznica w wydajności w tych dwóch przykładach?

@WyciorX, post #5

Dzięki :) ciekawa analiza
[#7] Re: [C, C++] Czy będzie róznica w wydajności w tych dwóch przykładach?

@WyciorX, post #5

Twoja analiza jest troche bez sensu bo:
1. uzywasz kodu, generowanego przez kompilator dla 64 bitowego procesora (x64) do odpowiedzi na pytanie, ktore dotycza kodu, generowanego dla innego, 32-bitowego procesora (mc68k). To sa procesory o kompletnie innym ABI - to jak porownywanie dyni z pomarancza.

2. Nie porownujesz tego, o co autor pytal. Autor pytania szuka opdowiedzi, ktora z tych dwoch operacji bedzie wolniejsza:
float cx = cells[index].cx;
float cy = cells[index].cy;
czy
float cx = numbers[cells[index].cx][0];
float cy = numbers[cells[index].cy][1];

Twoja odpowiedz porownuje
cx = numbers[cells[index].cx][0];
cy = numbers[cells[index].cy][1];
z
cx = numbers[cells[index].cx][i];
cy = numbers[cells[index].cy][i]; 
gdzie i=1 lub 0

3. W przykladzie, ktory testujesz index nie jest w ogole modyfikowany w petli, co przy ustawieniu kompilatora na optymalizacje na poziomie O3 powoduje, ze operacja zostaje zoptymalizowana do 1 instrukcji pobrania wartosci z pamieci. Wynika to z faktu, ze index jest znany i nie ulega zmianie wiec jest traktowany jako stala. Prowadzi to do blednej konkluzji, ze taka operacja zajmuje 1 instrukcje. Tego rodzaju optymalizacja moze tez wystepowac np w trywialnych petlach gdzie index rosnie o stala wartosc, ale w realnych aplikacjach to nie jest czesty przypadek.
[#8] Re: [C, C++] Czy będzie róznica w wydajności w tych dwóch przykładach?

@docent, post #7

A ja tylko na marginesie wtrącę że dzięki Bebbo jest wersja compiler explorera z amigowymi kompilatorami - i to nie tylko dla jego gcc 6.5, są tam inne skamieliny do wyboru.
[#9] Re: [C, C++] Czy będzie róznica w wydajności w tych dwóch przykładach?

@docent, post #7

1. Kolega nie podał na co kompiluje. AIB nie ma znaczenia do przykładu poglądowego, jeśli jest jakieś miejsce w którym będzie potrzeba doładowania dodatkowej zmiennej, to będzie ona widoczna w wygenerowanym pliku assemblera, bez znaczenia czy to jest x86, czy procesor z C64.

2. Tutaj chyba powinienem powiedzieć, że czytasz, ale nie rozumiesz celu, bo celem tego drugiego przykładu było uzmysłowienie autorowi wątku, że zamiana stałych na zmienne może zmienić sporo w prędkości kodu, co może dać czasami do myślenia, że np. nieraz warto jest rozwinąć pętle z indeksami reprezentowanymi przez stałe, zamiast zmiennych.

3. Ja to zauważyłem, ale kolega podał tak a nie inaczej, i tak rozwinąłem jego przykład by się w ogóle kompilował i nie wywalał operacji, które uznaje za zbędne.

Żeby dalej nie dyskutować na ślepo, skorzystałem z strony podanej przez kolegę z kompilatorami dla Amigi, dodałem inkrementację index, aby nie była już traktowana jak stała wartość:

#include <stdio.h>

float numbers[4][2] = { {1.0f, 0.0f}, {1.0f, 0.1f}, {1.0f, 0.2f}, {0.4f, 1.0} };
float numbers1[4] = { 1.0f, 1.0f, 1.0f, 0.4f};

struct sCell
{
int x,y,px,py;
int type;

int cx, cy, cz, c1; // przechowują index do tablicy gdzie znajduje się wartosc
};

struct sCell cells[4096];

int main()
{
int index;
float cx, cy, cz, c1;


while(1)
{

cz = numbers1[cells[index].cz];
c1 = numbers1[cells[index].c1];

printf("%d", (int) (cz + c1));
index++;

cx = numbers[cells[index].cx][0];
cy = numbers[cells[index].cy][1];

printf("%d", (int) (cx + cy));
index++;


};
}


Sprawdzane na g++ 10b i g++ 6.5.0b z parametrem -O3, w obydwu przypadkach generowane były 4 instrukcje. Kto chce, może sobie sprawdzić sam pod linkiem podanym przez kolegę powyżej. Trzeba pamiętać oczywiście, że nie tylko ilość instrukcji się liczy, ale też, jakie to instrukcje. W tym przypadku jednak są użyte te same move.l i lsl.l i z tą samą ilością parametrów.


Ostatnia aktualizacja: 12.01.2021 15:31:18 przez WyciorX
Na stronie www.PPA.pl, podobnie jak na wielu innych stronach internetowych, wykorzystywane są tzw. cookies (ciasteczka). Służą ona m.in. do tego, aby zalogować się na swoje konto, czy brać udział w ankietach. Ze względu na nowe regulacje prawne jesteśmy zobowiązani do poinformowania Cię o tym w wyraźniejszy niż dotychczas sposób. Dalsze korzystanie z naszej strony bez zmiany ustawień przeglądarki internetowej będzie oznaczać, że zgadzasz się na ich wykorzystywanie.
OK, rozumiem