Odpowiedzi:
Gęstość kodu luźno odnosi się do liczby instrukcji mikroprocesora potrzebnych do wykonania żądanego działania i ilości miejsca zajmowanego przez każdą instrukcję. Mówiąc ogólnie, im mniej miejsca zajmuje instrukcja i im więcej pracy na instrukcję może wykonać mikroprocesor, tym bardziej gęsty jest jego kod.
Zauważam, że otagowałeś swoje pytanie tagiem „uzbrojenie”; Mogę zilustrować gęstość kodu za pomocą instrukcji ARM.
Powiedzmy, że chcesz skopiować blok danych z jednego miejsca w pamięci do drugiego. Pod względem koncepcyjnym kod wysokiego poziomu wyglądałby mniej więcej tak:
void memcpy(void *dest, void *source, int count_bytes)
{
char *s, *d;
s = source; d = dest;
while(count_bytes--) { *d++ = *s++; }
}
Teraz prosty kompilator dla prostego mikroprocesora może przekonwertować to na coś takiego:
movl r0, count_bytes
movl r1, s
movl r2, d
loop: ldrb r3, [r1]
strb [r2], r3
movl r3, 1
add r1, r3
add r2, r3
sub r0, r3
cmp r0, 0
bne loop
(mój ARM jest trochę zardzewiały, ale masz pomysł)
Byłby to bardzo prosty kompilator i bardzo prosty mikroprocesor, ale z przykładu widać, że analizujemy 8 instrukcji na iterację pętli (7, jeśli przeniesiemy „1” do innego rejestru i przeniesiemy obciążenie poza pętlą). To wcale nie jest gęste. Gęstość kodu wpływa również na wydajność; jeśli twoje pętle są dłuższe, ponieważ kod nie jest gęsty, możesz potrzebować więcej pamięci podręcznej instrukcji, aby zatrzymać pętlę. Więcej pamięci podręcznej oznacza droższy procesor, ale z drugiej strony złożone dekodowanie instrukcji oznacza więcej tranzystorów do odszyfrowania żądanej instrukcji, więc jest to klasyczny problem inżynieryjny.
ARM jest pod tym względem całkiem niezły. Każda instrukcja może być warunkowa, większość instrukcji może zwiększać lub zmniejszać wartość rejestrów, a większość instrukcji może opcjonalnie aktualizować flagi procesora. Na ARM i przy średnio przydatnym kompilatorze ta sama pętla może wyglądać mniej więcej tak:
movl r0, count_bytes
movl r1, s
movl r2, d
loop: ldrb r3, [r1++]
strb [r2++], r3
subs r0, r0, 1
bne loop
Jak widać, główna pętla zawiera teraz 4 instrukcje. Kod jest bardziej gęsty, ponieważ każda instrukcja w głównej pętli robi więcej. Zasadniczo oznacza to, że można zrobić więcej przy danej ilości pamięci, ponieważ jej mniej służy do opisania sposobu wykonywania pracy.
Teraz natywny kod ARM często skarżył się, że nie był zbyt gęsty; wynika to z dwóch głównych powodów: po pierwsze 32 bity to strasznie „długa” instrukcja, więc wydaje się, że wiele bitów jest marnowanych na prostsze instrukcje, a po drugie, kod został rozdęty ze względu na naturę ARM: każda instrukcja ma 32 bity długie, bez wyjątku. Oznacza to, że istnieje duża liczba 32-bitowych wartości literalnych, których nie można po prostu załadować do rejestru. Jeśli chciałbym załadować „0x12345678” do r0, jak kodować instrukcję, która nie tylko zawiera 0x12345678, ale także opisuje „ładowanie literału do r0”? Nie ma już bitów do zakodowania rzeczywistej operacji. Dosłowna instrukcja ARM ładowania jest interesującą małą bestią, a asembler ARM musi być również trochę mądrzejszy niż normalne asemblery, ponieważ musi „złapać”
W każdym razie, aby odpowiedzieć na te skargi, ARM wymyślił tryb Kciuka. Zamiast 32 bitów na instrukcję, długość instrukcji wynosi teraz 16 bitów dla prawie wszystkich instrukcji i 32 bity dla gałęzi. W trybie Kciuka było kilka poświęceń, ale ogólnie rzecz biorąc, te poświęcenia były łatwe do wykonania, ponieważ Thumb dał ci coś w rodzaju 40% poprawy gęstości kodu tylko poprzez zmniejszenie długości instrukcji.
„Gęstość kodu” zestawu instrukcji jest miarą tego, ile rzeczy można dostać do określonej ilości pamięci programu lub ile bajtów pamięci programu potrzebujesz do przechowywania określonej ilości funkcjonalności.
Jak zauważył Andrew Kohlsmith, nawet na tym samym MCU różne kompilatory mogą uzyskać różną gęstość kodu.
Możesz przeczytać „Owady świata komputerowego” autorstwa Miro Samka, który porównuje różne MCU.