różny czas operacji na strukturze i zmiennej

0

Witam.
Piszę program na mikrokontroler AVR. PRogram ma się zajmować pomiarem temperatury i yświetleniem jej na wyświetlaczy segmentowym. Porblem jaki napotkałem jest taki: gdy stworzyłem sobie stukturę e wszystkimi zmiennymi dotyczącymi temperatury (temperatura, pozycja przecinka, znak (+-), jednostka) to zauważłem, że czas wykonania prostej operacji modulo, czy dzielenia przez 10 jest 100 razy dłuższy niż przy operowaniu na zwykłej zmiennej. Nie mam pojęcia z czego to może wynikać. Programuję w Atmel Studio 6.2.

struct dane
{
	int32_t temperature;
	int8_t pointer;
	int8_t sign;
	int8_t unit;
//************************************
//Wewnątrz funkcji obsługi przerwania timera:
static struct dane present;

//*****************************
//testowane operacje:
present.temperature % 10;	//czas wykonania: ~380 cykli procesora, na zwykłej zmiennej lokalnej ~4 cykle.
present.temperature /= 10;	//czas wykonania: ~611 cykli procesora
};
0

Sprawdź jaki kod assemblera to coś generuje, opcja przeważnie -S

0

A jak to zrobić żeby podejrzeć część kodu w asemblerze?

0

Podziel kod na pliki i kompiluj tylko jeden plik?

0

Pokazał mi coś takiego:

	present.temperature /= 10;
	
00000215  LDI R28,0x7D		Load immediate 
00000216  LDI R29,0x00		Load immediate 
00000217  LDD R22,Y+0		Load indirect with displacement 
00000218  LDD R23,Y+1		Load indirect with displacement 
00000219  LDD R24,Y+2		Load indirect with displacement 
0000021A  LDD R25,Y+3		Load indirect with displacement 
0000021B  LDI R18,0x0A		Load immediate 
0000021C  LDI R19,0x00		Load immediate 
0000021D  LDI R20,0x00		Load immediate 
0000021E  LDI R21,0x00		Load immediate 
0000021F  RCALL PC+0x01AC		Relative call subroutine 
00000220  STD Y+0,R18		Store indirect with displacement 
00000221  STD Y+1,R19		Store indirect with displacement 
00000222  STD Y+2,R20		Store indirect with displacement 
00000223  STD Y+3,R21		Store indirect with displacement 
0

a przy braku struktury?

0

Przy braku struktury, ale dla zmiennej statycznej:

	present_temperature /= 10;
0000023F  LDS R22,0x007D		Load direct from data space 
00000241  LDS R23,0x007E		Load direct from data space 
00000243  LDS R24,0x007F		Load direct from data space 
00000245  LDS R25,0x0080		Load direct from data space 
00000247  LDI R18,0x0A		Load immediate 
00000248  LDI R19,0x00		Load immediate 
00000249  LDI R20,0x00		Load immediate 
0000024A  LDI R21,0x00		Load immediate 
0000024B  RCALL PC+0x019D		Relative call subroutine 
0000024C  STS 0x007D,R18		Store direct to data space 
0000024E  STS 0x007E,R19		Store direct to data space 
00000250  STS 0x007F,R20		Store direct to data space 
00000252  STS 0x0080,R21		Store direct to data space 
0

Z tego co widzę masz 8 bitowy procesor, wiec czyta do rejestrów bajt po bajcie, a potem wywołuje funkcję do dzielenia dużych liczb (dla 8bitowego procesora int32_t to duża liczba) i dlatego to tak długo trwa.
Pokaż więcej, bo dobry kompilator powinien połączyć obie linijki w jedną (wynik z dzielenia i resztę).

Jaki widać obliczanie adresu względem Y stanowi spory narzut.
Swoją drogą dziwne, że kompilator traktuje te przypadki odmiennie, gdybyś posługiwał się dynamicznym wskaźnikiem, to wtedy powinna być różnica, a ty się odwołujesz do zmiennej globalnej ergo stałego adresu pamięci.

0

Ale to jest całość dla tej instrukcji.

EDIT:
tylko, że w tym miejscu gdzie pokazałem kod assemblera to ta zmienna była w sumie już kilka linijek wcześniej używana.

0

Zmniejsz rozmiar zmiennej z 32 do 16 albo nawet 8 bitów to będzie lepiej.
Po prostu jest to bardzo mała maszynka a dużo wymagasz.

0

Mam takie wyniki:

typedef struct
{
    int32_t temperature;
    int8_t pointer;
    int8_t sign;
    int8_t unit;
} Data;

void fun_struct() {
  Data present = { .temperature = dummy_get() };

  present.temperature /= 10;

  dummy_set(present.temperature);
}

void fun_local()
{
  int32_t temperature = dummy_get();

  temperature /= 10;

  dummy_set(temperature);
}
fun_struct:
	call dummy_get
	ldi r18,lo8(10)
	ldi r19,0
	ldi r20,0
	ldi r21,0
	call __divmodsi4
	sts present.1386,r18
	sts present.1386+1,r19
	sts present.1386+2,r20
	sts present.1386+3,r21
	movw r24,r20
	movw r22,r18
	call dummy_set
	ret

fun_local:
	call dummy_get
	ldi r18,lo8(10)
	ldi r19,0
	ldi r20,0
	ldi r21,0
	call __divmodsi4
	movw r24,r20
	movw r22,r18
	call dummy_set
	ret

Kompiluje to GCC 4.9.0 ze zwyczajowymi flagami do kompilacji na AVR:
-Os -mmcu=avr5 -fpack-struct -fshort-enums -ffunction-sections -fdata-sections -mcall-prologues

Przy static jest wymagany zapis do pamięci i to jedyna różnica. Te funkcje są zapewne większe i to z całą pewnościa komplkuje sprawę. Ale to, czy jest to struktura, czy zmienna lokalna nie powinno mieć znaczenia - to static ma znaczenie.

0

Dzięki wielkie. Próbowałem trochę pooptymalizować i ostatecznie temperaturę zapisuję w 2 zmiennych int8_t, minimalnie więcej kodu, ale co najważniejsze - szybsze kilka razy. Pozostaje mi jeszcze jedno pytanie. W jednej z funkcji mam pętle while (8 iteracji), która wykonuje się 4 razy dłużej niż ręcznie przepisane instrukcje 8 razy. Czy ktoś mógłby mi powiedzieć dlaczego tak się dzieje? Wolałbym mieć to upakowane ładnie w pętli, niż mieć wszystko rozwalone na zewnątrz.

 void send_buf(uint8_t byte)
{
	int8_t i = 0;

	while(i < 8)
	{
		if(byte & (1 << i))
			DS_1;			//to są zdefiniowane po prostu zmiany odpowiednich wartości w rejestrach pinów mikrokontrolera
		else
			DS_0;
		SHCP_1;
		SHCP_0;
		i++;
	}
}
if(byte & (1 << i))
00000162  LDI R25,0x00		Load immediate 
00000163  MOVW R20,R24		Copy register pair 
00000164  MOV R0,R18		Copy register 
00000165  RJMP PC+0x0003		Relative jump 
00000166  ASR R21		Arithmetic shift right 
00000167  ROR R20		Rotate right through carry 
00000168  DEC R0		Decrement 
00000169  BRPL PC-0x03		Branch if plus 
0000016A  SBRS R20,0		Skip if bit in register set 
0000016B  RJMP PC+0x0003		Relative jump 
			DS_1;
0000016C  SBI 0x15,4		Set bit in I/O register 
0000016D  RJMP PC+0x0002		Relative jump 
			DS_0;
0000016E  CBI 0x15,4		Clear bit in I/O register 
		SHCP_1;
0000016F  SBI 0x15,5		Set bit in I/O register 
		SHCP_0;
00000170  CBI 0x15,5		Clear bit in I/O register 
00000171  SUBI R18,0xFF		Subtract immediate 
00000172  SBCI R19,0xFF		Subtract immediate with carry 
	while(i < 8)
00000173  CPI R18,0x08		Compare with immediate 
00000174  CPC R19,R1		Compare with carry 
00000175  BRNE PC-0x12		Branch if not equal 
}
00000176  RET 		Subroutine return 
{
00000177  PUSH R28		Push register on stack 
00000178  MOV R28,R22		Copy register 
0

@legier, powiedz mi jakie tam mierzysz temperatury że nie mieści się w zakresie 0..255 dla uint8_t ?

void send_buf(uint8_t byte)
  {
   for(uint8_t i=1;i;i<<=1)
     {
      if(byte&i) DS_1; else DS_0;
      SHCP_1;
      SHCP_0;
     }
  }

Wydaje mi się że istnieje DS(x) jeżeli tak to można jeszcze bardziej uprościć.

0

temperatury uzyskuję z zakresu -55 do 125, przy czym rozdzielczość pomiaru wynosi 0,0625, na wyświetlaczy wyświetlam 4 cyfry znaczące, więc żeby nie bawić się w float'y zapisuję wszystko pod odpowiednio dużym intem odpowiednio przemnożone przez potęge 10.

2
-Os -mmcu=avr5 -fpack-struct -fshort-enums -ffunction-sections -fdata-sections -mcall-prologues

-Os. a próbowałeś innych opcji, jak -O3?

temperatury uzyskuję z zakresu -55 do 125, przy czym rozdzielczość pomiaru wynosi 0,0625
Czyli masz około 3760 różnych wartości. To 16 bitów.
Może dałoby się jednak „pobawić we floaty” - dopiero przy samym wyświetlaniu?

0

próbowałem na -O3 i jest dużo dużo lepiej, czas wykonania całego przerwania wyniósł 200 cykli procesora. Co do floatów to one chyba też utrudniają, bo liczbę wyświetlam na poczwórnym wyświetlaczu 7-segmentowym z użyciem rejestru przesuwnego dla oszczędzenia na pinach, a na intcie mogę łatwo poznać ostatnią cyfrę za pomocą modulo 10.

0

Może powinieneś napisać jaką dokładnie łysą wartość dostajesz z sensora? Całkiem możliwe, że jest jakiś cwany sposób szybkiej konwersji tej wartości do BCD.
Czy to wygląda tak (bardziej chodzi o binarną postać jaką dostajesz z sensora i jej znaczenie niż obliczania zmiennoprzecinkowe)?

int16_t value = getTemperature();
float valuToDisplay = value*0.0625-55;

Bo jeśli tak to powinno to powinieneś do tego podejść w ten sposób:

int16_t value = getTemperature();
int8_t integerPart = value>>4;
int8_t fractionalPart = value&0xf;

a dalsze przetwarzanie tego powinno być tanie, bo już operujesz na liczbach 8 bitowych.
Przykładowo, by otrzymać w takim wypadku dziesiętną część stopni, to powinieneś: zrobić tak

int8_t fracDec = ((value&0xf)*10+8)>>4; // +8 by zaokrąglać

1 użytkowników online, w tym zalogowanych: 0, gości: 1