fredericopissarra Posted September 25, 2019 at 02:58 PM Share Posted September 25, 2019 at 02:58 PM https://is.gd/WfRLHy Link to comment Share on other sites More sharing options...
Administrators Fernando Mercês Posted September 26, 2019 at 12:58 AM Administrators Share Posted September 26, 2019 at 12:58 AM Oi Frederico. Você pode por o texto no corpo do técnico também? Mantém o link da fonte, claro. Acho que fica mais legal que só o link. ? Link to comment Share on other sites More sharing options...
fredericopissarra Posted September 26, 2019 at 10:25 AM Author Share Posted September 26, 2019 at 10:25 AM Ei-lo: --------%<-----------%<----------------------------- Usei as siglas “oficiais” aqui. AArch32 é o modo de 32 bits do ARMv8 e IA-32 é o modo de 32 bits dos processadores da família Intel (costumo chamar esse modo de i386). Para a comparação, compilei com o GCC, para as duas plataformas, o seguinte código: #include <stddef.h> #include <stdint.h> int64_t sum( int *ptr, size_t size ) { int64_t s; s = 0; while ( size-- ) s += *ptr++; return s; } Para poupar tempo, fiz uma cross compilation, na minha estação de trabalho (Intel), usando a versão 7.4 do GCC para as duas plataformas: $ arm-linux-gnueabi-gcc --version | head -1 arm-linux-gnueabi-gcc (Ubuntu/Linaro 7.4.0-1ubuntu1~18.04.1) 7.4.0 $ gcc --version | head -1 gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0 Abaixo, temos os códigos gerados com otimizações ligadas: #-- Intel i386 @-- ARM Cortex-A53 AArch32 .text .cpu cortex-a53 .globl sum .text .global sum .arm sum: sum: push ebp cmp r1, #0 push edi mov r2, #0 push esi mov r3, #0 push ebx sub r1, r1, #1 mov edi, [esp+24] beq .exit mov ebp, [esp+20] .loop: test edi, edi ldr ip, [r0], #4 je .exit sub r1, r1, #1 xor esi, esi adds r2, r2, ip xor eax, eax adc r3, r3, ip, asr #31 xor edx, edx cmn r1, #1 .loop: bne .loop mov ecx, [ebp+esi*4] .exit: mov ebx, ecx mov r0, r2 sar ebx, 31 mov r1, r3 add eax, ecx bx lr adc edx, ebx add esi, 1 cmp esi, edi jne .loop pop ebx pop esi pop edi pop ebp ret .exit: pop ebx xor eax, eax xor edx, edx pop esi pop edi pop ebp ret Note que: Usei a calling convention default em ambos os casos; Não fiz quaisquer otimizações adicionais, em ambos os casos. O código para i386 é sempre pior porque a convenção de chamada usa, necessariamente, a pilha e temos menos registradores de uso geral para manipular que no ARM. Com o i386 temos apenas EAX, EBX, ECX, EDX, ESI, EDI e EBP, ou seja, 7 registradores, onde 4 deles precisam ser preservados entre chamadas (EBX, ESI, EDI e EBP), daí os pushes/pops. No caso do ARM temos 13 registradores de uso geral disponíveis para brincar, de R0 até R12 (R13 [SP] é o Stack Pointer, R14 [LR] é Link Register e R15 [PC] é o Program Counter — eles não devem ser futucados levianamente!) e o código gerado pelo GCC não precisou usar nenhum dos registradores que precisassem ser preservados (R4 até R11). O registrador IP é um apelido para R12 usado pelo assembly, que funciona como um Intra Procedure scratch register. Dentre outras regras, a convenção de chamada padrão para o AArch32 é usar de R0 até R4 como argumentos da função, de R4 até R11 como “variáveis locais” (daí a preservação necessária). Note que, no caso da família Intel, o processador não tem alternativa senão afetar os flags depois de uma instrução lógica ou aritmética. Não é o caso com o ARM. A instrução ADDS tem esse ‘S’ ai adicionado para dizer que essa adição afeta os flags (no caso, precisamos do carry), mas ADC não afeta! Outro detalhe é que os modos de endereçamento permitem pré-incremento (ou decremento) e pós-incremento (ou decremento). No caso da instrução ldr ip, [r0], #4 o que ela instrui é a carga de IP com o conteúdo da memória apontada por R0, mas depois R0 é incrementado em 4 (o tamanho de um int). Ou seja, a sub-expressão *ptr++ é executada numa única instrução. Já no modo i386 precisamos de duas: mov ecx,[ebp+esi*4]/add esi,1. Outro detalhe é que todas as instruções do ARM têm sempre exatamente 32 bits de tamanho. Em teoria não existe penalidades para decodificação de instruções complexas. Isso não é verdade na família Intel… Prefixos, extensões, endereçamentos complexos, tudo isso adiciona ciclos de clock ao processamento, bem como podem gerar instruções grandes (do ponto de vista da decodificação). Para compensar isso os processadores da família Intel mais recentes usam um monte de “macetes” para que o código execute o mais rápido possível, como, por exemplo, manter um buffer de instruções (a família Haswell mantém um buffer de 192 instruções) e a reordenação das instruções para melhorar o paralelismo… É nesse ponto que minha comparaçẽo dos códigos acima é “injusta” porque, embora possa parecer que para a família Intel o código seja mais extenso do que deveria, em comparação com o código ARM, na verdade muitas das “redundâncias” são compensadas pelo processador. Isso não quer dizer que o código Intel é mais eficiente, neste caso. Note que existem vários acessos à memória (os PUSH/POP e a cópia dos argumentos à partir da pilha) que, é claro, sáo feitos apenas uma vez na entrada e outra na saída. Só que nada disso é necessário no ARM. Assim, um código para 32 bits para a plataforma Intel é, quase que certamente, menos eficiente (se não fossem os “macetes” e o clock mais elevado) do que o mesmo código gerado para o ARM AArch32. Agora, comparemos o mesmo código entre o x86-64 e AArch64 (para o mesmo processador ARM: O Cortex-A53): .text .arch armv8-a+crc .globl sum .text .global sum sum: sum: test rsi, rsi mov x4, x0 je .exit cbz x1, .exit xor edx, edx mov x2, 0 xor eax, eax mov x0, 0 .loop: .loop: movsx rcx, dword [rdi+rdx*4] ldrsw x3, [x4, x2, lsl 2] add rdx, 1 add x2, x2, 1 add rax, rcx cmp x2, x1 cmp rdx, rsi add x0, x0, x3 jne .loop bne .loop ret ret .exit: .exit: xor eax, eax mov x0, 0 ret ret Reparou que o código é, essencialemnte, o mesmo? Isso porque x86-64 tem 15 registradores de uso geral disponíveis e a convenção de chamada (SysV ABI para x86-64, neste caso) os usa, assim como no ARM. Mesmo assim, se os clocks fosse identicos, provavelmente o código ARM seria alguns poucos ciclos mais rápido: No código x86-64 uma instrução TEST RSI,RSI é necessária para afetar os flags e fazer o salto se ZF=1 em seguida (JE .exit), mas isso sofrerá uma fusão no reordenador de instruções e se comportará exatamente como o CBZ X1, .exit do ARM. O problema é no loop: As instruções MOVSX RCX,DWORD [RDI+RDX*4], ADD RDX,1, ADD RAX,RCX e CMP RDX,RSI usam o prefixo REX, o que adicionará 1 ciclo extra a cada uma delas. Isso não acontece com o ARM porque a instrução tem sempre 32 bits de tamanho (exceto no modo Thumb, onde têm 16 bits de tamanho), então, não há penalidade na decodificação. PS: Modo Thumb (dedão) é claramente uma piadinha com o modo ARM (braço). As documentações do ARM estão cheias dessas piadinhas: O Manual de Referencia da Architetura ARM, por exemplo, é chamado de ARM ARM (ARM Architecture Reference Manual). Existem 3 tipos de “processadores”, os da série A (Application), os da série R (Realtime) e os da série M (MicroController), ou seja, A,R e M…. e por ai vai… -------------%<-------------%<--------------- Link to comment Share on other sites More sharing options...
Recommended Posts
Archived
This topic is now archived and is closed to further replies.