Czy inline Assembly language jest wolniejszy niż natywny kod C++?

Próbowałem porównać wydajność języka inline assembly i kodu C++, więc napisałem funkcję, która dodaje dwie tablice o rozmiarze 2000 dla 100000 razy. Oto kod:

#define TIMES 100000
void calcuC(int *x,int *y,int length)
{
    for(int i = 0; i < TIMES; i++)
    {
        for(int j = 0; j < length; j++)
            x[j] += y[j];
    }
}


void calcuAsm(int *x,int *y,int lengthOfArray)
{
    __asm
    {
        mov edi,TIMES
        start:
        mov esi,0
        mov ecx,lengthOfArray
        label:
        mov edx,x
        push edx
        mov eax,DWORD PTR [edx + esi*4]
        mov edx,y
        mov ebx,DWORD PTR [edx + esi*4]
        add eax,ebx
        pop edx
        mov [edx + esi*4],eax
        inc esi
        loop label
        dec edi
        cmp edi,0
        jnz start
    };
}

Oto main():

int main() {
    bool errorOccured = false;
    setbuf(stdout,NULL);
    int *xC,*xAsm,*yC,*yAsm;
    xC = new int[2000];
    xAsm = new int[2000];
    yC = new int[2000];
    yAsm = new int[2000];
    for(int i = 0; i < 2000; i++)
    {
        xC[i] = 0;
        xAsm[i] = 0;
        yC[i] = i;
        yAsm[i] = i;
    }
    time_t start = clock();
    calcuC(xC,yC,2000);

    //    calcuAsm(xAsm,yAsm,2000);
    //    for(int i = 0; i < 2000; i++)
    //    {
    //        if(xC[i] != xAsm[i])
    //        {
    //            cout<<"xC["<<i<<"]="<<xC[i]<<" "<<"xAsm["<<i<<"]="<<xAsm[i]<<endl;
    //            errorOccured = true;
    //            break;
    //        }
    //    }
    //    if(errorOccured)
    //        cout<<"Error occurs!"<<endl;
    //    else
    //        cout<<"Works fine!"<<endl;

    time_t end = clock();

    //    cout<<"time = "<<(float)(end - start) / CLOCKS_PER_SEC<<"\n";

    cout<<"time = "<<end - start<<endl;
    return 0;
}

Następnie uruchamiam program pięć razy, aby uzyskać cykle procesora, które mogą być postrzegane jako czas. Za każdym razem wywołuję jedną z funkcji wymienionych powyżej.

I oto wynik.

Funkcja wersji montażowej:

Debug   Release
---------------
732        668
733        680
659        672
667        675
684        694
Average:   677

Funkcja C++ Wersja:

Debug     Release
-----------------
1068      168
 999      166
1072      231
1002      166
1114      183
Average:  182

Kod C++ w trybie release jest prawie 3,7 razy szybszy niż kod assembly. Dlaczego?

Domyślam się, że kod asemblera, który napisałem, nie jest tak skuteczny jak kod generowany przez GCC. Trudno jest zwykłemu programiście jak ja napisać kod szybciej niż jego przeciwnik generowany przez kompilator.Czy to znaczy, że nie powinienem ufać wydajności języka assembly pisanego moimi rękami, skupić się na C++ i zapomnieć o języku assembly?

Author: Adriano Repetti, 2012-03-07

22 answers

Tak, najczęściej.

Przede wszystkim zaczynasz od błędnego założenia, że język niskopoziomowy (w tym przypadku assembly) zawsze wytworzy szybszy Kod niż język wysokiego poziomu (C++ i C w tym przypadku). To nieprawda. Czy kod C jest zawsze szybszy od kodu Javy? Nie, ponieważ istnieje inna zmienna: programista. Sposób pisania kodu i znajomość szczegółów architektury mają duży wpływ na wydajność (tak jak w tym przypadku).

Możesz Zawsze stworzyć przykład, gdzie kod handmade assembly jest lepszy od kodu skompilowanego, ale zazwyczaj jest to fikcyjny przykład lub pojedyncza rutyna, a nie prawdziwy program składający się z ponad 500.000 linii kodu C++). Myślę, że Kompilatory będą produkować lepszy kod assembly 95% razy i czasami, tylko kilka rzadkich razy, być może będziesz musiał napisać kod assembly dla kilku, krótkich, wysoce używanych, performance critical procedury lub gdy masz dostęp do funkcji Twój ulubiony język wysokiego poziomu nie ujawnia się. Do chcesz odrobinę tej złożoności? Przeczytaj tę niesamowitą odpowiedź tutaj NA SO.

Dlaczego to?

Przede wszystkim dlatego, że Kompilatory mogą wykonywać optymalizacje, których nawet nie możemy sobie wyobrazić (patrz ta krótka lista) i zrobią to w sekund (kiedy możemy potrzebować dni ).

Podczas kodowania w assembly musisz tworzyć dobrze zdefiniowane funkcje z dobrze zdefiniowanym interfejsem wywołania. Mogą jednak wziąć pod uwagę optymalizację całego programu i optymalizacja międzyprocesowa takie as przydział rejestru, stała propagacja, wspólna eliminacja podwyrażeń, planowanie instrukcji i inne złożone, nieoczywiste optymalizacje (np.). Na RISC architekci przestali się o to martwić wiele lat temu (np. schedulowanie instrukcji jest bardzo trudne do dostrojenia ręcznie ), a nowoczesne CISC procesory mają bardzo długie rurociągi też.

Dla niektórych skomplikowanych mikrokontrolerów nawet system biblioteki są napisane w języku C zamiast assembly, ponieważ ich Kompilatory wytwarzają lepszy (i łatwy w utrzymaniu) kod końcowy.

Kompilatory czasami mogą automatycznie używać instrukcji MMX / SIMDx samodzielnie, a jeśli ich nie używasz, po prostu nie możesz porównać (inne odpowiedzi bardzo dobrze sprawdzały Twój kod montażu). Tylko dla pętli jest to krótka lista pętli optymalizacje co jest Często sprawdzane przez kompilator (czy uważasz, że możesz to zrobić sam, gdy twój harmonogram został wybrany dla programu C#?) Jeśli piszesz coś w assembly, myślę, że musisz wziąć pod uwagę przynajmniej niektóre proste optymalizacje. Przykład z podręcznika szkolnego dla tablic to rozwinąć cykl (jego rozmiar jest znany w czasie kompilacji). Zrób to i powtórz test.

W dzisiejszych czasach bardzo rzadko trzeba używać język asemblowania z innego powodu: mnóstwo różnych procesorów . Chcesz wesprzeć ich wszystkich? Każdy z nich ma specyficzny mikroarchitekturę i niektóre specyficzne zestawy instrukcji. Mają różną liczbę jednostek funkcjonalnych i instrukcje montażu powinny być ułożone tak, aby wszystkie były zajęte . Jeśli piszesz w C możesz użyć PGO ale w assembly będziesz potrzebował dużej wiedzy na temat tej specyficznej architektury (i Przemyśl i powtórz wszystko dla innej architektury ). Dla małych zadań kompilator zwykle robi to lepiej, a dla złożonych zadań zwykle praca nie jest zwracana (i kompilator może robić lepiej w każdym razie).

Jeśli usiądziesz i spojrzysz na swój kod prawdopodobnie zobaczysz, że zyskasz więcej, aby przeprojektować algorytm niż przetłumaczyć na assembly( przeczytaj ten świetny post tutaj NA so ), są optymalizacje wysokiego poziomu (i wskazówki do kompilatora), które możesz może skutecznie zastosować, zanim będziesz musiał uciekać się do języka montażu. Prawdopodobnie warto wspomnieć, że często korzystając z intrinsics zyskasz wydajność, której szukasz, a kompilator nadal będzie w stanie wykonać większość swoich optymalizacji.

To wszystko powiedziane, nawet jeśli możesz wyprodukować 5~10 razy szybszy kod montażowy, powinieneś zapytać swoich klientów, czy wolą zapłacić tydzień twojego czasu lub kupić szybszy o 50$ procesor . Optymalizacja ekstremalna więcej często (a zwłaszcza w aplikacjach LOB) po prostu nie jest wymagane od większości z nas.

 223
Author: Adriano Repetti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:43

Twój kod montażowy jest wyjątkowo ubogi nieco nieoptymalne i może być poprawione:

  • wciskasz i wyskakujesz rejestr ( EDX ) w swojej wewnętrznej pętli. To powinno zostać usunięte z pętli.
  • przeładowujesz wskaźniki tablicy w każdej iteracji pętli. To powinno wyjść z obiegu.
  • używasz instrukcji loop, która jest znana jako martwa powolność na większości nowoczesnych procesorów (prawdopodobnie wynik użycia starożytnego zestawu książka*)
  • nie korzystasz z ręcznego rozwijania pętli.
  • nie używasz dostępnych instrukcji SIMD.

Więc chyba, że znacznie poprawisz swoje umiejętności dotyczące asemblera, nie ma sensu pisać kodu asemblera dla wydajności.

*Oczywiście, że nie wiem, czy naprawdę dostałeś loop instrukcję ze starożytnej księgi zgromadzeń. Ale prawie nigdy nie widzisz tego w kodzie realnego świata, ponieważ każdy kompilator jest wystarczająco inteligentny, aby nie emitować loop, widać to tylko w IMHO Złych i przestarzałych książkach.

 186
Author: Gunther Piez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:33:26

Jeszcze przed przejściem do assembly, istnieją transformacje kodu, które istnieją na wyższym poziomie.

static int const TIMES = 100000;

void calcuC(int *x, int *y, int length) {
  for (int i = 0; i < TIMES; i++) {
    for (int j = 0; j < length; j++) {
      x[j] += y[j];
    }
  }
}

Można przekształcić za pomocą obrót pętli :

static int const TIMES = 100000;

void calcuC(int *x, int *y, int length) {
    for (int j = 0; j < length; ++j) {
      for (int i = 0; i < TIMES; ++i) {
        x[j] += y[j];
      }
    }
}
Co jest o wiele lepsze, jeśli chodzi o pamięć.

Można by to dalej optymalizować, Robienie a += b x razy jest równoważne robieniu a += X * b więc otrzymujemy:

static int const TIMES = 100000;

void calcuC(int *x, int *y, int length) {
    for (int j = 0; j < length; ++j) {
      x[j] += TIMES * y[j];
    }
}

Wydaje się jednak, że mój ulubiony optymalizator (LLVM) nie wykonuje tej transformacji.

[edit] I okazało się, że transformacja jest wykonywana, jeśli mamy restrict kwalifikator do x i y. W rzeczywistości bez tego ograniczenia, x[j] i y[j] mogą być przypisane do tego samego miejsca, co czyni tę transformację błędną. [end edit]

W każdym razie, ta to chyba zoptymalizowana wersja C. Już jest o wiele prostsze. Na tej podstawie, oto mój crack do ASM (pozwalam Clang generować go, Jestem bezużyteczny w nim): {]}

calcuAsm:                               # @calcuAsm
.Ltmp0:
    .cfi_startproc
# BB#0:
    testl   %edx, %edx
    jle .LBB0_2
    .align  16, 0x90
.LBB0_1:                                # %.lr.ph
                                        # =>This Inner Loop Header: Depth=1
    imull   $100000, (%rsi), %eax   # imm = 0x186A0
    addl    %eax, (%rdi)
    addq    $4, %rsi
    addq    $4, %rdi
    decl    %edx
    jne .LBB0_1
.LBB0_2:                                # %._crit_edge
    ret
.Ltmp1:
    .size   calcuAsm, .Ltmp1-calcuAsm
.Ltmp2:
    .cfi_endproc

Obawiam się, że nie rozumiem, gdzie te wszystkie instrukcje pochodzą z, jednak zawsze można się dobrze bawić i spróbować i zobaczyć, jak to porównuje... ale nadal używałbym zoptymalizowanej wersji C, a nie assembly, w kodzie, znacznie bardziej przenośnej.

 56
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-07 20:08:23

Krótka odpowiedź: tak.

Długa odpowiedź: tak, chyba że naprawdę wiesz, co robisz i masz ku temu powód.

 40
Author: Oliver Charlesworth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-07 12:24:05

Naprawiłem mój kod asm:

  __asm
{   
    mov ebx,TIMES
 start:
    mov ecx,lengthOfArray
    mov esi,x
    shr ecx,1
    mov edi,y
label:
    movq mm0,QWORD PTR[esi]
    paddd mm0,QWORD PTR[edi]
    add edi,8
    movq QWORD PTR[esi],mm0
    add esi,8
    dec ecx 
    jnz label
    dec ebx
    jnz start
};

Wyniki dla wersji Release:

 Function of assembly version: 81
 Function of C++ version: 161

Kod asemblera w trybie release jest prawie 2 razy szybszy niż w C++.

 30
Author: sasha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-09 12:44:08

Czy to oznacza, że nie powinienem ufać wydajności języka asemblacji napisanego moimi rękami

Tak, dokładnie to oznacza, i to jest prawdą dla KAŻDEGO języka. Jeśli nie wiesz, jak pisać wydajny kod w języku X, nie powinieneś ufać swojej zdolności do pisania wydajnego kodu w języku X. i tak, jeśli chcesz wydajnego kodu, powinieneś użyć innego języka.

Montaż jest na to szczególnie wrażliwy, ponieważ, cóż, to, co widzisz, jest tym, co Ty get. Piszesz konkretne instrukcje, które procesor ma wykonać. W przypadku języków wysokiego poziomu istnieje kompilator, który może przekształcić Twój kod i usunąć wiele nieefektywności. Z montażem jesteś zdany na siebie.

 23
Author: jalf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-07 16:33:14

Jedynym powodem używania języka asemblera jest użycie niektórych funkcji niedostępnych dla tego języka.

Dotyczy to:

  • Programowanie jądra, które wymaga dostępu do pewnych funkcji sprzętowych, takich jak MMU
  • wysokowydajne programowanie, które wykorzystuje bardzo specyficzne instrukcje wektorowe lub multimedialne, które nie są obsługiwane przez kompilator.

Ale obecne Kompilatory są dość inteligentne, mogą nawet zastąpić dwa oddzielne instrukcje, takie jak d = a / b; r = a % b; z pojedynczym instrukcja, która oblicza dzielenie i resztę za jednym razem, jeśli jest dostępna, nawet jeśli C nie ma takiego operatora.

 20
Author: fortran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-07 12:34:47

To prawda, że nowoczesny kompilator wykonuje niesamowitą pracę w optymalizacji kodu, jednak nadal zachęcałbym Cię do dalszej nauki assembly.

Po pierwsze jesteś wyraźnie nie zastraszany przez to, to wielki, wielki plus, po drugie - jesteś na dobrej drodze przez profilowanie w celu potwierdzenia lub odrzucenia założeń prędkości, prosisz o wkład doświadczonych ludzi, a masz największe narzędzie optymalizacyjne znane ludzkości: mózg {4]}.

Wraz ze wzrostem doświadczenia dowiesz się, kiedy i gdzie go używać (Zwykle najciaśniejsze, najskrytsze pętle w kodzie, po dogłębnej optymalizacji na poziomie algorytmicznym).

Dla inspiracji polecam przejrzeć Artykuły Michaela Abrasha (Jeśli nie słyszałeś od niego, jest on Guru optymalizacji; współpracował nawet z Johnem Carmackiem w optymalizacji renderera oprogramowania Quake!)

" nie ma czegoś takiego jak najszybszy code " - Michael Abrash

 16
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-09 15:51:25

Zmieniłem kod asm:

 __asm
{ 
    mov ebx,TIMES
 start:
    mov ecx,lengthOfArray
    mov esi,x
    shr ecx,2
    mov edi,y
label:
    mov eax,DWORD PTR [esi]
    add eax,DWORD PTR [edi]
    add edi,4   
    dec ecx 
    mov DWORD PTR [esi],eax
    add esi,4
    test ecx,ecx
    jnz label
    dec ebx
    test ebx,ebx
    jnz start
};

Wyniki dla wersji Release:

 Function of assembly version: 41
 Function of C++ version: 161

Kod asemblera w trybie release jest prawie 4 razy szybszy niż w C++. IMHo, szybkość kodu montażowego zależy od programisty

 14
Author: sasha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-09 08:03:00

Większość kompilatorów języków wysokiego poziomu jest bardzo zoptymalizowana i wie, co robi. Możesz spróbować zrzucić kod demontażu i porównać go z natywnym zespołem. Wierzę, że zobaczysz kilka fajnych sztuczek, których używa Twój kompilator.

Tylko na przykład, nawet że nie jestem pewien czy to już prawda:):

Robi:

mov eax,0

Kosztują więcej cykli niż

xor eax,eax
Co robi to samo.

Kompilator zna wszystkie te sztuczki i używa ich.

 12
Author: Nuno_147,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-07 21:32:13

Kompilator cię pokonał. Spróbuję, ale nie udzielę żadnych gwarancji. Zakładam, że "mnożenie" przez czasy ma na celu uczynienie go bardziej odpowiednim testem wydajności, że y i {[2] } są wyrównane 16, A length jest niezerową wielokrotnością 4. To pewnie i tak prawda.

  mov ecx,length
  lea esi,[y+4*ecx]
  lea edi,[x+4*ecx]
  neg ecx
loop:
  movdqa xmm0,[esi+4*ecx]
  paddd xmm0,[edi+4*ecx]
  movdqa [edi+4*ecx],xmm0
  add ecx,4
  jnz loop
Jak mówiłem, nie daję żadnych gwarancji. Ale będę zaskoczony, jeśli można to zrobić znacznie szybciej - wąskim gardłem tutaj jest przepustowość pamięci, nawet jeśli wszystko jest hitem L1.
 10
Author: harold,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-07 14:11:56

To bardzo ciekawy temat!
Zmieniłem MMX przez SSE w kodzie Sashy
Oto moje wyniki:

Function of C++ version:      315
Function of assembly(simply): 312
Function of assembly  (MMX):  136
Function of assembly  (SSE):  62

Kod asemblera z SSE jest 5 razy szybszy niż w C++

 10
Author: salaoshi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-11 03:56:26

po prostu ślepo zaimplementowanie dokładnie tego samego algorytmu, instrukcja po instrukcji, w assembly jest gwarantowane, że będzie wolniejsze niż to, co kompilator może zrobić.

To dlatego, że nawet najmniejsza optymalizacja, którą wykonuje kompilator, jest lepsza niż sztywny kod bez żadnej optymalizacji.

Oczywiście można pokonać kompilator, zwłaszcza jeśli jest to mała, zlokalizowana część kodu, musiałem nawet zrobić to sam, aby uzyskać ok. 4X przyspieszyć, ale w tym case musimy w dużej mierze polegać na dobrej znajomości sprzętu i licznych, pozornie przeciwstawnych sztuczkach.

 6
Author: vsz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-22 05:21:13

To dokładnie to, co to znaczy. Zostaw mikro-optymalizacje kompilatorowi.

 4
Author: Luchian Grigore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-07 12:24:01

Uwielbiam ten przykład, ponieważ pokazuje ważną lekcję o kodzie niskiego poziomu. Tak, ty Możesz napisać assembly, który jest tak szybki jak Twój kod C. Jest to tautologicznie prawda, ale niekoniecznie oznacza cokolwiek. Najwyraźniej ktoś może, inaczej asembler nie zna odpowiednich optymalizacji.

Podobnie, ta sama zasada ma zastosowanie, gdy idzie się w górę hierarchii abstrakcji języka. Tak, możesz napisać parser w C, który jest jak szybki jak szybki i brudny skrypt Perla, a wiele osób to robi. Ale to nie znaczy, że ponieważ użyłeś C, Twój kod będzie szybki. W wielu przypadkach języki wyższego poziomu dokonują optymalizacji, których być może nigdy nie brałeś pod uwagę.

 4
Author: tylerl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-07 20:34:24

Jako kompilator zamieniłbym pętlę o stałym rozmiarze na wiele zadań wykonawczych.

int a = 10;
for (int i = 0; i < 3; i += 1) {
    a = a + i;
}

Wyprodukuje

int a = 10;
a = a + 0;
a = a + 1;
a = a + 2;

I w końcu będzie wiedział, że "A = A + 0;" jest bezużyteczne, więc usunie tę linię. Mam nadzieję, że coś w twojej głowie jest teraz gotowe dołączyć kilka opcji optymalizacji jako komentarz. Wszystkie te bardzo skuteczne optymalizacje sprawią, że skompilowany język będzie szybszy.

 4
Author: Miah,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-11 21:27:41

W wielu przypadkach optymalny sposób wykonania jakiegoś zadania może zależeć od kontekstu, w którym zadanie jest wykonywane. Jeśli rutyna jest napisana w języku asemblera, to na ogół nie będzie możliwe zróżnicowanie sekwencji instrukcji w zależności od kontekstu. Jako prosty przykład rozważ następującą prostą metodę:

inline void set_port_high(void)
{
  (*((volatile unsigned char*)0x40001204) = 0xFF);
}
Kompilator 32-bitowego kodu ARM, biorąc pod uwagę powyższe, prawdopodobnie renderowałby go jako coś w stylu:]}
ldr  r0,=0x40001204
mov  r1,#0
strb r1,[r0]
[a fourth word somewhere holding the constant 0x40001204]

A może

ldr  r0,=0x40001000  ; Some assemblers like to round pointer loads to multiples of 4096
mov  r1,#0
strb r1,[r0+0x204]
[a fourth word somewhere holding the constant 0x40001000]

Że może być nieco zoptymalizowany w ręcznie zmontowanym kodzie, ponieważ:

ldr  r0,=0x400011FF
strb r0,[r0+5]
[a third word somewhere holding the constant 0x400011FF]

Lub

mvn  r0,#0xC0       ; Load with 0x3FFFFFFF
add  r0,r0,#0x1200  ; Add 0x1200, yielding 0x400011FF
strb r0,[r0+5]

Oba ręcznie montowane podejścia wymagałyby 12 bajtów przestrzeni kodowej zamiast 16; to drugie zastąpiłoby "load" z "add", co na ARM7-TDMI wykonywałoby dwa cykle szybciej. Jeśli kod miał być wykonywany w kontekście, w którym r0 był " don 't-know/don 't-care", wersje językowe asemblera byłyby więc nieco lepsze niż wersja skompilowana. Z drugiej strony, Załóżmy, że kompilator wiedział, że jakiś rejestr [np. r5] będzie posiadał wartość mieszczącą się w granicach 2047 bajtów żądanego adresu 0x40001204 [np. 0x40001000], a ponadto wiedział, że jakiś inny rejestr [np. r7] będzie posiadał wartość o małych bitach 0xFF. W tym przypadku kompilator może zoptymalizować wersję C kodu po prostu:

strb r7,[r5+0x204]

Znacznie krótszy i szybszy niż nawet ręcznie zoptymalizowany kod złożenia. Ponadto Załóżmy, że set_port_high wystąpił w kontekst:

int temp = function1();
set_port_high();
function2(temp); // Assume temp is not used after this

Nie jest to wcale nieprawdopodobne podczas kodowania dla systemu wbudowanego. Jeśli {[8] } jest zapisany w kodzie asemblera, kompilator musiałby przenieść r0 (który przechowuje wartość zwracaną z function1) gdzieś indziej przed wywołaniem kodu asemblera ,a następnie przenieść tę wartość z powrotem do r0 (ponieważ {[10] } będzie oczekiwał pierwszego parametru w r0), więc "zoptymalizowany" kod asemblera będzie potrzebował pięciu instrukcji. Nawet jeśli kompilator nie wiedział o żadnych rejestrach zawierających adres lub wartość do przechowywania, jej wersja z czterema instrukcjami (którą mógłby dostosować do użycia wszelkich dostępnych rejestrów-niekoniecznie r0 i r1) przebiłaby "zoptymalizowaną" wersję językową asemblera. Jeśli kompilator miał adres i dane w r5 i r7, jak opisano wcześniej, function1 nie zmieniłby tych rejestrów, a zatem mógłby zastąpić set_port_high pojedynczą instrukcją strb--cztery instrukcje mniejsze i szybsze niż "ręcznie zoptymalizowany" kod asemblera.

Zauważ, że ręcznie zoptymalizowany kod złożenia może często przewyższać kompilator w przypadkach, gdy programista zna dokładny przebieg programu, ale Kompilatory świecą w przypadkach, gdy fragment kodu jest napisany przed poznaniem jego kontekstu lub gdy jeden fragment kodu źródłowego może być wywołany z wielu kontekstów [Jeśli set_port_high jest używany w pięćdziesięciu różnych miejscach kodu, kompilator może niezależnie zdecydować dla każdego z nich, jak najlepiej go rozwinąć].

Ogólnie rzecz biorąc, sugerowałbym, że język assembly jest odpowiedni do zapewnia największą poprawę wydajności w przypadkach, gdy do każdego fragmentu kodu można podchodzić z bardzo ograniczonej liczby kontekstów i może być szkodliwy dla wydajności w miejscach, w których fragment kodu można podchodzić z wielu różnych kontekstów. Co ciekawe (i wygodnie) przypadki, w których montaż jest najbardziej korzystny dla wydajności, to często te, w których kod jest najprostszy i łatwy do odczytania. Miejsca, w których kod języka asemblera zmieniłby się w lepki bałagan to często te, w których pisanie w montażu przyniosłoby najmniejszą korzyść z wydajności.

[Minor note: są miejsca, w których kod asemblacji może być użyty do uzyskania hiperoptymalizowanego lepkiego bałaganu; na przykład, jeden kawałek kodu, który zrobiłem dla ARM, potrzebował pobrać słowo z pamięci RAM i wykonać jedną z około dwunastu procedur opartych na sześciu górnych bitach wartości (wiele wartości mapowanych do tej samej procedury). Myślę, że zoptymalizowałem ten kod do czegoś takiego:

ldrh  r0,[r1],#2! ; Fetch with post-increment
ldrb  r1,[r8,r0 asr #10]
sub   pc,r8,r1,asl #2

The rejestr r8 zawsze posiadał adres głównej tabeli wysyłkowej (w pętli, w której kod spędzał 98% swojego czasu, nigdy nie używał go do innych celów); wszystkie 64 wpisy odnosiły się do adresów w 256 bajtach przed nim. Ponieważ pierwotna pętla miała w większości przypadków trudny termin wykonania około 60 cykli, pobieranie i wysyłanie dziewięciu cykli było bardzo pomocne w osiągnięciu tego celu. Korzystanie z tabeli 256 32-bitowych adresów byłoby o jeden cykl szybsze, ale pochłonęłoby do 1KB bardzo cennej pamięci RAM [flash dodałby więcej niż jeden stan oczekiwania]. Użycie 64 32-bitowych adresów wymagałoby dodania instrukcji do zamaskowania niektórych bitów z pobranego słowa i nadal pożrełoby o 192 więcej bajtów niż tabela, której faktycznie użyłem. Korzystanie z tabeli 8-bitowych offsetów dało bardzo zwarty i szybki kod, ale nie spodziewałbym się, że kompilator kiedykolwiek wymyśli; nie spodziewałbym się również, że kompilator poświęci rejestr "cały czas" na trzymanie adres tabeli.

[15]}powyższy kod został zaprojektowany do działania jako samodzielny system; mógł okresowo wywoływać kod C, ale tylko w pewnych momentach, gdy sprzęt, z którym się komunikował, mógł bezpiecznie zostać wprowadzony w stan" bezczynności " przez dwie mniej więcej milisekundowe interwały co 16ms.]}
 3
Author: supercat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-24 20:26:46

W ostatnim czasie wszystkie optymalizacje prędkości, które zrobiłem, zastępowały uszkodzony mózg powolny kod rozsądnym kodem. Ale dla rzeczy były szybkość była naprawdę krytyczna i włożyłem poważny wysiłek, aby zrobić coś szybko, wynik był zawsze iteracyjny proces, gdzie każda iteracja dał więcej wgląd w problem, znalezienie sposobów, jak rozwiązać problem z mniejszą ilością operacji. Ostateczna prędkość zawsze zależała od tego, jak dużo wglądu mam w problem. Jeśli na którymś etapie użyłem kod assembly, lub kod C, który został nadmiernie zoptymalizowany, proces znalezienia lepszego rozwiązania ucierpiałby, a efekt końcowy byłby wolniejszy.

 2
Author: gnasher729,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-10 17:02:19

C++ jest szybszy, chyba że używasz języka asemblera z głębszym wiedza w prawidłowy sposób.

Kiedy koduję w ASM, reorganizuję instrukcje ręcznie, aby procesor mógł wykonać więcej z nich równolegle, jeśli jest to logicznie możliwe. Ledwo używam pamięci RAM, gdy koduję w ASM na przykład: może być 20000 + linii kodu w ASM i nigdy nie używałem push / pop.

Możesz przeskoczyć środek kodu opcode, aby samodzielnie zmodyfikować kod i zachowanie bez możliwości samodzielnej modyfikacji kodu. Dostęp do rejestrów zajmuje 1 tick(czasami zajmuje .25 tyknięć) procesora.Dostęp do pamięci RAM może zająć setki.

Podczas mojej ostatniej przygody z ASM, ani razu nie użyłem pamięci RAM do przechowywania zmiennej (dla tysięcy linii ASM). ASM może być potencjalnie niewyobrażalnie szybszy niż C++. Ale to zależy od wielu zmiennych czynników, takich jak:

1. I was writing my apps to run on the bare metal.
2. I was writing my own boot loader that was starting my programs in ASM so there was no OS management in the middle.

Uczę się teraz C# i C++ , ponieważ zdałem sobie sprawę, że produktywność ma znaczenie!! Możesz spróbować zrobić najszybsze możliwe do wyobrażenia programy wykorzystujące tylko czysty ASM w wolnym czasie. Ale aby coś wyprodukować, użyj jakiegoś języka wysokiego poziomu.

Na przykład, ostatni program, który zakodowałem, używał JS i GLSL i nigdy nie zauważyłem żadnych problemów z wydajnością, nawet mówiąc o JS, który jest powolny. Dzieje się tak, ponieważ sama koncepcja programowania GPU dla 3D sprawia, że szybkość języka, który wysyła polecenia do GPU, jest prawie nieistotna.

Prędkość samego asemblera na gołym metalu jest niepodważalne. Czy w C++może być jeszcze wolniej? - Może być tak dlatego, że piszesz kod asemblera za pomocą kompilatora, który nie używa asemblera.

Moją osobistą radą jest nigdy nie pisać kodu asemblera, jeśli możesz go uniknąć, mimo że kocham assemblera.

 1
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-08 07:02:27

Wszystkie odpowiedzi tutaj wydają się wykluczać jeden aspekt: czasami nie piszemy kodu, aby osiągnąć konkretny cel, ale dla czystej zabawy. Może nie być opłacalne zainwestowanie czasu, aby to zrobić, ale prawdopodobnie nie ma większej satysfakcji niż pokonanie najszybszego zoptymalizowanego przez kompilator fragmentu kodu z ręcznie zwijaną alternatywą asm.

 0
Author: madoki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-21 15:47:10

Kompilator c++, po optymalizacji na poziomie organizacyjnym, produkowałby kod, który wykorzystywałby wbudowane funkcje docelowego procesora. HLL nigdy nie wykona asemblera z kilku powodów; 1. ) HLL zostanie skompilowany i wyprowadzony z kodem dostępowym, sprawdzaniem granic i ewentualnie wbudowanym w garbage collection (dawniej adresowanie zakresu w manieryzmie OOP) wszystkie wymagające cykli (flipy i flopy). HLL robi w dzisiejszych czasach świetną robotę (w tym nowsze C++ i inne jak GO), ale jeśli przewyższają assemblera (a mianowicie Twój kod) musisz skonsultować dokumentację procesora-porównania z niechlujnym kodem są z pewnością niejednoznaczne i skompilowane langy, takie jak assembler, wszystkie rozwiązują się do op-code HLL abstrakuje szczegóły i nie eliminuje ich, inaczej aplikacja nie będzie uruchamiana, jeśli jest rozpoznawana przez system operacyjny hosta.

Większość kodu asemblera (głównie obiektów) jest wyprowadzana jako "bezgłowy" w celu włączenia do innych formatów wykonywalnych przy znacznie mniejszym przetwarzaniu wymagane w związku z tym będzie znacznie szybsze, ale o wiele bardziej niezabezpieczone; jeśli plik wykonywalny jest wyprowadzany przez asembler (NAsm, YAsm itp.) nadal będzie działać szybciej, dopóki nie całkowicie pasuje do kodu HLL w funkcjonalności, a następnie wyniki mogą być dokładnie ważone.

Wywołanie obiektu kodu asemblera z HLL w dowolnym formacie spowoduje z natury dodanie narzutu przetwarzania, a także wywołań przestrzeni pamięci przy użyciu globalnie przydzielonej pamięci dla zmiennych / stałych typów danych (dotyczy to zarówno LLL, jak i HLL). Pamiętaj, że końcowe wyjście wykorzystuje procesor jako jego api i abi w stosunku do sprzętu (kod OPC) i oba asemblery i "kompilatory HLL" są zasadniczo / zasadniczo identyczne z jedynym prawdziwym wyjątkiem jest czytelność (gramatyczna).

Hello world aplikacja konsolowa w asemblerze używającym FAsm ma 1,5 KB (a to w Windows jeszcze mniejsze we FreeBSD i Linuksie) i przewyższa wszystko, co GCC może wyrzucić w swoim najlepszym dniu; powody są niejawne padding z nops, dostęp do walidacji i sprawdzania granic, aby wymienić tylko kilka. Prawdziwym celem jest czyste biblioteki HLL i optymalny kompilator, który celuje w procesor w sposób "hardcore" i większość robi to w dzisiejszych czasach (w końcu). GCC nie jest lepszy niż YAsm-chodzi o praktyki kodowania i zrozumienie programisty, a "optymalizacja" przychodzi po początkującym eksploracji i tymczasowym szkoleniu i doświadczeniu.

Kompilatory muszą łączyć się i montować dla wyjścia w tym samym opcode co asembler, ponieważ te kody są wszystkim, co CPU będzie wyjątkiem (CISC lub RISC [PIC too]). YAsm zoptymalizował i wyczyścił wiele na wczesnym NAsm ostatecznie przyspieszając wszystkie wyjścia z tego asemblera, ale nawet wtedy YAsm nadal, podobnie jak NAsm, produkuje pliki wykonywalne z zewnętrznymi zależnościami kierującymi biblioteki systemu operacyjnego w imieniu dewelopera, więc przebieg może się różnić. Zamykając C++ jest w punkcie, który jest niesamowity i znacznie bardziej bezpieczny niż assembler dla 80 + procent, zwłaszcza w sektorze komercyjnym...

 -2
Author: TheRaven,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-11-19 19:52:05

Assembly może być szybsze, jeśli kompilator generuje dużo oo kodu wsparcia.

Edit:

Do downvoters: OP napisał (a):.. skupić się na C++ i zapomnieć o języku assembly?"i podtrzymuję swoją odpowiedź. Zawsze musisz mieć oko na kod generowany przez OO, szczególnie podczas korzystania z metod. Nie zapominając o języku assembly oznacza to, że będziesz okresowo przeglądać assembly generowany przez Twój kod OO, co moim zdaniem jest koniecznością do pisania dobrze działające oprogramowanie.

W zasadzie dotyczy to całego kodu kompilowalnego, nie tylko OO.

 -3
Author: Olof Forshell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-22 16:59:58