C Promocja na liczbę całkowitą na 8-bitowych MCU


14

Korzystając z avr-gcc jako przykładu, typy int mają szerokość 16 bitów. Wykonywanie operacji na 8-bitowych operandach w C powoduje, że operandy te są konwertowane na 16-bitowe typy int z powodu promocji liczb całkowitych w C. Czy to oznacza, że ​​wszystkie 8-bitowe operacje arytmetyczne na AVR potrwają znacznie dłużej, jeśli są napisane w C niż jeśli napisane w asemblerze z powodu promocji liczby całkowitej C?


1
Nie sądzę, aby kompilator zdał sobie sprawę, że zmienna docelowa jest znakiem (niepodpisanym), dlatego nie będzie przeszkadzać w obliczaniu 8 pierwszych bitów. Mimo to odkryłem, że GCC czasami nie jest tak dobry w optymalizacji kodu, dlatego jeśli kodujesz w ASM, wynik MGIHT będzie szybszy. Jednakże, chyba że wykonujesz zadania / kontrole o znaczeniu krytycznym dla czasu, z bardzo silnym ograniczeniem budżetowym, to albo powinieneś wybrać mocniejszy procesor i zaprogramować go w C, albo po prostu nie martw się o niższą wydajność (zamiast tego rozważ czas - na rynek, lepsza czytelność / możliwość ponownego wykorzystania kodu, mniej błędów itp.).
następny hack

Przepraszam, że nie mam czasu na sprawdzenie. Myślę jednak, że dla gcc istniała flaga wiersza poleceń, która kontrolowałaby „promocję liczb całkowitych”. Może nawet istnieje potrzeba kontrolowania go dla określonych fragmentów kodu. Jak ważna jest wydajność? W wielu zastosowaniach AVR różnica prędkości dla niektórych działań arytmetycznych nie stanowi problemu. Foxus o poprawnym działaniu kodu w pierwszej kolejności. Następnie, jeśli występuje problem z wydajnością, dowiedz się, co to jest. Łatwo byłoby marnować czas na kodowanie w asemblerze, ale okazało się, że to nie miało znaczenia.
żarówka

1
wystarczy zdemontować i zobaczyć, co robi kompilator. Z czysto językowego punktu widzenia tak. wdrożenie tutaj jest nietypowe. normalnie int próbuje wyrównać się z wielkością rejestru, a jeśli masz 16-bitowe rejestry, to 8-bitowa matematyka jest w rzeczywistości tańsza przy 16 bitach niż 8. Ale jest na odwrót i przy 8-bitowym mcu sensowne jest zaimplementowanie int jako 16 bitów. więc prawdopodobnie powinieneś używać uchar tam, gdzie ci na tym zależy, ale nie rób tego wspólnego nawyku programowania, ponieważ boli cię najbardziej wszędzie.
old_timer

3
Pamiętaj: Unikaj odpowiadania na pytania w komentarzach.
rura

4
Tego rodzaju pytania lepiej zadawać ekspertom C w SO, ponieważ jest to pytanie czysto programowe. Promocja liczb całkowitych w C jest dość złożonym tematem - przeciętny programista C będzie miał wiele nieporozumień na ten temat.
Lundin,

Odpowiedzi:


16

Krótko mówiąc:

Zawsze następuje promocja liczb całkowitych do 16 bitów - wymusza to standard C. Ale kompilator może zoptymalizować obliczenia z powrotem do 8 bitów (kompilatory systemów osadzonych są zwykle całkiem dobre w takich optymalizacjach), jeśli można wywnioskować, że znak będzie taki sam, jak byłby, gdyby typ był promowany.

Nie zawsze tak jest! Niejawne zmiany sygnatur spowodowane promocją liczb całkowitych są częstym źródłem błędów w systemach wbudowanych.

Szczegółowe wyjaśnienie można znaleźć tutaj: Reguły promocji typu niejawnego .


8
unsigned int fun1 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned char fun2 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned int fun3 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

unsigned char fun4 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

zgodnie z oczekiwaniami fun1 to wszystko ints, podobnie jak 16-bitowa matematyka

00000000 <fun1>:
   0:   86 0f           add r24, r22
   2:   97 1f           adc r25, r23
   4:   08 95           ret

Chociaż technicznie niepoprawny, ponieważ jest 16-bitowym dodatkiem wywoływanym przez kod, nawet niezoptymalizowany ten kompilator usunął adc ze względu na rozmiar wyniku.

00000006 <fun2>:
   6:   86 0f           add r24, r22
   8:   08 95           ret

tak naprawdę nie dziwi mnie promocja, kompilatory nie robiły tego, nie jestem pewien, która wersja sprawiła, że ​​to się zaczęło, natrafiłem na to na początku mojej kariery i pomimo, że kompilatory promowały się nieczynnie (tak jak powyżej), robiłem promocję, mimo że ja kazał mi robić matematykę uchar, nie był zaskoczony.

0000000a <fun3>:
   a:   70 e0           ldi r23, 0x00   ; 0
   c:   26 2f           mov r18, r22
   e:   37 2f           mov r19, r23
  10:   28 0f           add r18, r24
  12:   31 1d           adc r19, r1
  14:   82 2f           mov r24, r18
  16:   93 2f           mov r25, r19
  18:   08 95           ret

i ideał, wiem, że to 8 bitów, chcę 8-bitowy wynik, więc po prostu powiedziałem, żeby wykonał 8 bitów przez całą drogę.

0000001a <fun4>:
  1a:   86 0f           add r24, r22
  1c:   08 95           ret

Ogólnie rzecz biorąc, lepiej jest dążyć do rozmiaru rejestru, który idealnie jest wielkością (u) int, dla takiego 8-bitowego mcu autorzy kompilatora musieli pójść na kompromis ... Nie należy przyzwyczajać się do używając uchar do matematyki, o której wiesz, że nie potrzebuje więcej niż 8 bitów, tak jak podczas przenoszenia tego kodu lub pisania takiego kodu na procesorze z większymi rejestrami, teraz kompilator musi zacząć maskować i rozszerzać znaki, co niektórzy robią natywnie w niektórych instrukcjach, i inni nie.

00000000 <fun1>:
   0:   e0800001    add r0, r0, r1
   4:   e12fff1e    bx  lr

00000008 <fun2>:
   8:   e0800001    add r0, r0, r1
   c:   e20000ff    and r0, r0, #255    ; 0xff
  10:   e12fff1e    bx  lr

zmuszanie 8 bitów kosztuje więcej. Oszukałem trochę / dużo, potrzebowałbym nieco bardziej skomplikowanych przykładów, aby zobaczyć więcej tego w uczciwy sposób.

EDYCJA na podstawie dyskusji na temat komentarzy

unsigned int fun ( unsigned char a, unsigned char b )
{
    unsigned int c;
    c = (a<<8)|b;
    return(c);
}

00000000 <fun>:
   0:   70 e0           ldi r23, 0x00   ; 0
   2:   26 2f           mov r18, r22
   4:   37 2f           mov r19, r23
   6:   38 2b           or  r19, r24
   8:   82 2f           mov r24, r18
   a:   93 2f           mov r25, r19
   c:   08 95           ret

00000000 <fun>:
   0:   e1810400    orr r0, r1, r0, lsl #8
   4:   e12fff1e    bx  lr

bez niespodzianki. Chociaż dlaczego optymalizator zostawił tę dodatkową instrukcję, czy nie możesz użyć ldi na r19? (Znałem odpowiedź, kiedy ją zadałem).

EDYCJA 2

dla avr

avr-gcc --version
avr-gcc (GCC) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

aby uniknąć złego nawyku lub nie 8-bitowego porównania

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

najwyraźniej optymalizacja była włączona zajmuje tylko sekundę, aby wypróbować własny kompilator, aby zobaczyć, jak wypada w porównaniu z moimi danymi wyjściowymi, ale w każdym razie:

whatever-gcc -O2 -c so.c -o so.o
whatever-objdump -D so.o

I tak, używanie bajtów do zmiennych wielkości bajtów, z pewnością na avr, pic itp., Pozwoli ci zaoszczędzić pamięć i naprawdę chcesz ją zachować ... jeśli tak naprawdę używasz, ale jak pokazano tutaj, jak najmniej będzie w pamięci, jak najwięcej w rejestrach, więc oszczędności flash wynikają z braku dodatkowych zmiennych, oszczędności pamięci RAM mogą, ale nie muszą być rzeczywiste.


2
„kompilatory nie robiły tego wcześniej, nie jestem pewien, która wersja sprawiła, że ​​to się zaczęło, natrafiłem na to na początku mojej kariery i pomimo, że kompilatory promowały się nieczynnie (tak jak powyżej), robiłem promocję, mimo że kazałem to zrobić matematyki uchar, nie zaskoczony." Jest tak, ponieważ kompilatory C systemów wbudowanych miały okropną zgodność ze standardami :) Kompilator zwykle może optymalizować, ale tutaj nie można wywnioskować, że wynik będzie pasował, unsigned charwięc musi przeprowadzić promocję do 16 bitów, zgodnie z wymaganiami według normy.
Lundin,

1
@ stary_timer (a<<8)|bjest zawsze niepoprawny dla każdego systemu, w którym intjest 16 bitów. azostanie niejawnie awansowany do intktórego jest podpisany. W przypadku aprzechowywania wartości w MSB, kończysz przesuwanie tych danych do bitu znakowego 16-bitowej liczby, co wywołuje niezdefiniowane zachowanie.
Lundin,

1
fun3 to fun..ny ... całkowicie niezoptymalizowany przez kompilator ... Biorąc pod uwagę, że r1 jest zawsze w GCC równe 0, i oznacza ra, rb, {rh, rl} rejestry dla zmiennych a, b i wynik, kompilator mógł zrobić: 1) mov rh, r1; 2) mov rl, ra; 2) dodaj rl, rb; 3) adc rh, rh; 4) ret. 4 instrukcje, vs 7 lub 8 ... Instrukcja 1 może być zmieniona w ldi rh, 0.
następny hack

1
To byłaby lepsza odpowiedź, gdyby określała kompilator i odpowiednie opcje w użyciu.
Russell Borogove,

1
Dobrym pomysłem jest unikanie używania int / char itp., A zamiast tego używanie znacznie bardziej wyraźnych i czytelnych int16_t i int8_t.
użytkownik

7

Niekoniecznie, ponieważ nowoczesne kompilatory wykonują dobrą pracę w zakresie optymalizacji generowanego kodu. Na przykład, jeśli napiszesz, z = x + y;gdzie są wszystkie zmienne unsigned char, kompilator musi je wypromować unsigned intprzed wykonaniem obliczeń. Ponieważ jednak wynik końcowy będzie dokładnie taki sam bez promocji, kompilator wygeneruje kod, który po prostu doda zmienne 8-bitowe.

Oczywiście nie zawsze tak jest, na przykład wynik z = (x + y)/2;zależy od górnego bajtu, więc nastąpi promocja. Nadal można tego uniknąć bez uciekania się do montażu poprzez rzutowanie wyniku pośredniego z powrotem na unsigned char.

Niektórych takich nieefektywności można uniknąć za pomocą opcji kompilatora. Na przykład wiele 8-bitowych kompilatorów ma opcję pragma lub przełącznik wiersza polecenia, aby zmieścić typy wyliczeń w 1 bajcie, zamiast intzgodnie z wymaganiami C.


4
„Kompilator jest wymagany do awansowania ich na unsigned int”. Nie, kompilator musi je promować int, ponieważ charnajprawdopodobniej nie będzie miał takiej samej rangi konwersji jak intna żadnej platformie.
Lundin,

3
„Na przykład wiele 8-bitowych kompilatorów ma opcję pragma lub przełącznik wiersza polecenia, aby zmieścić typy wyliczeń w 1 bajcie zamiast int, jak wymaga tego C.” Standard C pozwala na przypisywanie zmiennych wyliczeniowych do 1 bajtu. Wymaga to jedynie, aby stałe wyliczenia były int(tak, to niespójne). C11 6.7.2.2Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined...
Lundin,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.