Skocz do zawartości

Szybkość wykonywania porównań na STM32L4


Gieneq

Pomocna odpowiedź

Zainspirowany tym filmem chciałem sprawdzić jak to działa na STM32L4:

 

Napisałem 4 funkcje:

int smaller_v1(int a, int b){
	if(a<b)
		return a;
	if(b<a)
		return b;
}

int smaller_v2(int a, int b){
	if(a<b)
		return a;
	return b;
}

int smaller_v3(int a, int b){
	return (a<b)?a:b;
}

int smaller_v4(int a, int b){
	return a * (a<b) + b*(a>=b);
}

Przetestowałem każdą wykonując na danych z tablic 1000 losowych zmiennych 1000 razy:

  startTime = HAL_GetTick();
  for(int j = 0; j < laps; ++j) {
	  for(int i = 0; i < dsize; ++i){
		  smaller_v4(data_a[i], data_b[i]);
	  }
  }
  deltaTime = HAL_GetTick() - startTime;

Program wykonywałem z wyborem optymalizacji kompilacji ale nic to nie zmieniło.

  • _v1 - 3064 ms,
  • _v2 - 2854 ms,
  • _v3 - 2635 ms,
  • _v4 - 3764 ms.

Jestem w stanie zrozumieć dlaczego _v4 jednak działa wolno - pewnie znaczenie ma tu sprzęt, ale nie rozumiem dlaczego _v3 jest szybsze?

Też sprawdziłem _v3 dla różnych typów danych:

  • int/uint32_t/uint_fast8_t - 2635 ms,
  • char/uint8_t - 3075 ms,
  • float - 3635 ms.

size.PNG.e60dc5221a3f8334baa3653332ace62a.PNG

Rozumiem dlaczego pierwsze 3 mają ten sam czas, ale ciekawi mnie dlaczego 1 bajtowe zmienne są gorsze, czy wynika to z potrzeby dopisania 3 bajtów żeby dało się wykonać na nich operacje? Pewnie dlatego zmienne fast są fast, bo mają 4 B.

  • Lubię! 1
Link do komentarza
Share on other sites

@Elvis Na początek  -O0 później -O3. Czasy które spisały są z -O3.

Sprawdzałem też jednen temat: -O3 podobno wyrzuca takie kwiatki jak puste pętle. Wrzuciłem w kod pustą pętlę i w debuggerze dalej była, zmiana liczby iteracji zmieniała czas wykonania i można było prześledzić ją w poleceniach asm. Może to zostało przez odpalanie w debuggerze?

Link do komentarza
Share on other sites

@Gieneq Możesz udostępnić pliki .elf? Fajnie byłoby zobaczyć co ten kompilator faktycznie wygenerował. Debugger nie najlepiej sobie radzi przy -O3, kodu w C nie potrafi zmieniać i czasem pokazuje co innego niż faktycznie działa.

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

A nie możesz zerknąć na wygenerowany kod asemblera? Co ciekawsze: v2 i v4 z -O3 generują praktycznie ten sam kod (pozostałych nie sprawdziłem bo mi się nie chciało).

A tak poza tym:

int smaller_v1(int a, int b){
	if(a<b)
		return a;
	if(b<a)
		return b;
}

Nie miałeś tu warninga przypadkiem?

Link do komentarza
Share on other sites

Zrobiłem drobny błąd przy wpisywaniu optymalizacji... już działa.

1 godzinę temu, ethanak napisał:

Nie miałeś tu warninga przypadkiem?

@ethanak Warning jest dopiero od O1, przez to że nie wybrałem optymalizacji to tkwiłem w O0.

Poprawiłem kod do testów bo mi już na O1 wszystko wycinało. Teraz wynik jest do czegoś używany i nie wylatuje:

/* USER CODE BEGIN PV */

#define LAPS 200
#define DSIZE 1000

int data_a[DSIZE];
int data_b[DSIZE];
int data_c[DSIZE];

uint32_t tempvalue = 0;
uint32_t startTime;
uint32_t deltaTime;

/* USER CODE END PV */



//....

for(int i = 0; i < DSIZE; ++i){
	  HAL_RNG_GenerateRandomNumber(&hrng, &tempvalue);
	  data_a[i] = (int)(tempvalue % 50);
	  HAL_RNG_GenerateRandomNumber(&hrng, &tempvalue);
	  data_b[i] = (int)(tempvalue % 50);
  }


  startTime = HAL_GetTick();
  for(int j = 0; j < LAPS; ++j) {
	  for(int i = 0; i < DSIZE; ++i){
		  data_c[i] = smaller_v1(data_a[i], data_b[i]);
	  }
  }
  deltaTime = HAL_GetTick() - startTime;

  while (1)
  {
	  HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
	  HAL_Delay(deltaTime);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

Tu spisałem czas wykonania i zajętość pamięci:

image.thumb.png.5ea3c47df60be866d940fea2db4b1cbd.png

Pomiędzy v1 i v2 jest przy każdym stopniu różnica, czyli optymalizacja nie wyrzuci elsa - stąd pewnie ostrzeżenie.

1 godzinę temu, Elvis napisał:

Możesz udostępnić pliki .elf?

@Elvis w załączniku, każdy katalog do osobnego poziomu i w środku 4 pliki do każdej z funkcji.

smaller.zip

Link do komentarza
Share on other sites

(edytowany)

@Gieneq  Chyba musisz zacząć o poprawienia swojego programu testowego.

W pętli wywołujesz smaller_v4(), ale wyników nigdzie nie używasz, nawet do zapisania w zmiennej volatile.

Więc optymalizator wycina całe odwołanie do smaller_v4.

Edit: chociaż w sumie nie jestem tego na 100% pewien, analiza kodu po opytmalizacji wymaga jednak trochę więcej czasu 😉

Edytowano przez Elvis
  • Pomogłeś! 1
Link do komentarza
Share on other sites

@Gieneq Wrzuciłem funkcje smaller* do CompilerExplorer - link do mojego testu - i w wynikowym asemblerze możemy zobaczyć że już przy O1 kod funkcji smaller_v1, smaller_v2 i smaller_v3 generują ten sam kod maszynowy, z kolei smaller_v4 generuje kod maszynowy który ma dwie instrukcje więcej niż pozostała trójka. 

Do testu wziąłem output z STM32CubeIDE:

arm-none-eabi-gcc "../Core/Src/main.c" -mcpu=cortex-m4 -std=gnu11 -g3 -DDEBUG -DUSE_HAL_DRIVER -DSTM32L476xx -c -I../Core/Inc -I../Drivers/STM32L4xx_HAL_Driver/Inc -I../Drivers/STM32L4xx_HAL_Driver/Inc/Legacy -I../Drivers/CMSIS/Device/ST/STM32L4xx/Include -I../Drivers/CMSIS/Include -O0 -ffunction-sections -fdata-sections -Wall -fstack-usage -MMD -MP -MF"Core/Src/main.d" -MT"Core/Src/main.o" --specs=nano.specs -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb -o "Core/Src/main.o"

i do CE wrzuciłem te ważniejsze flagi:

-O1 -mcpu=cortex-m4 -std=gnu11 -mfpu=fpv4-sp-d16 -mfloat-abi=hard -mthumb 

Wrzuciłem też kod do benchmarka (link do mojego testu), co prawda benchmark dla C++ i wykonuje się na innej architekturze ale... generalnie wyszło bardzo podobnie:

1211025849_Zrzutekranu2021-05-06223226.thumb.png.a51979e2021bcb946642ced9c46af6ea.png

Natomiast gdy odpalimy test dla innego przykładu benchmarka (testowy dla tego benchmarka):

1836628766_Zrzutekranu2021-05-06223621.thumb.png.30eb9b5c50162046f1c809e99314761c.png

To tutaj już nie ma dyskusji nt. tego która opcja jest szybsza.

Podsumowując asemblerowo (dla Cortex-M4) wszystkie opcje wyglądają bardzo podobnie, gdzie jedynie smaller_v4 generuje więcej instrukcji. Z kolei benchmark potwierdza (przynajmniej dla smaller_v2 i smaller_v3 na x86), że obie te funkcje generują taki sam kod maszynowy. 

Tyle z mojej strony, natomiast bardzo chętnie przeczytałbym jakąś bardziej dokładną analizę 😛 

Link do komentarza
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »
×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.