Despre codul x86_64 compilat cu gcc și optimizarea codului C

Am compilat următorul cod C:

typedef struct {
    long x, y, z;
} Foo;

long Bar(Foo *f, long i)
{
    return f[i].x + f[i].y + f[i].z;
}

cu comanda gcc -S -O3 test.c . Aici este funcția Bar în ieșire:

    .section    __TEXT,__text,regular,pure_instructions
    .globl  _Bar
    .align  4, 0x90
_Bar:
Leh_func_begin1:
    pushq   %rbp
Ltmp0:
    movq    %rsp, %rbp
Ltmp1:
    leaq    (%rsi,%rsi,2), %rcx
    movq    8(%rdi,%rcx,8), %rax
    addq    (%rdi,%rcx,8), %rax
    addq    16(%rdi,%rcx,8), %rax
    popq    %rbp
    ret
Leh_func_end1:

Am câteva întrebări despre acest cod de asamblare:

  1. What is the purpose of "pushq %rbp", "movq %rsp, %rbp", and "popq %rbp", if neither rbp nor rsp is used in the body of the function?
  2. Why do rsi and rdi automatically contain the arguments to the C function (i and f, respectively) without reading them from the stack?
  3. I tried increasing the size of Foo to 88 bytes (11 longs) and the leaq instruction became an imulq. Would it make sense to design my structs to have "rounder" sizes to avoid the multiply instructions (in order to optimize array access)? The leaq instruction was replaced with:

    imulq   $88, %rsi, %rcx
    
0

3 răspunsuri

  1. The function is simply building its own stack frame with these instructions. There's nothing really unusual about them. You should note, though, that due to this function's small size, it will probably be inlined when used in the code. The compiler is always required to produce a "normal" version of the function, though. Also, what @ouah said in his answer.

  2. This is because that's how the AMD64 ABI specifies the arguments should be passed to functions.

    If the class is INTEGER, the next available register of the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9 is used.

    Page 20, AMD64 ABI Draft 0.99.5 – September 3, 2010

  3. This is not directly related to the structure size, rather - the absolute address that the function has to access. If the size of the structure is 24 bytes, f is the address of the array containing the structures, and i is the index at which the array has to be accessed, then the byte offset to each structure is i*24. Multiplying by 24 in this case is achieved by a combination of lea and SIB addressing. The first lea instruction simply calculates i*3, then every subsequent instruction uses that i*3 and multiplies it further by 8, therefore accessing the array at the needed absolute byte offset, and then using immediate displacements to access the individual structure members ((%rdi,%rcx,8). 8(%rdi,%rcx,8), and 16(%rdi,%rcx,8)). If you make the size of the structure 88 bytes, there is simply no way of doing such a thing swiftly with a combination of lea and any kind of addressing. The compiler simply assumes that a simple imull will be more efficient in calculating i*88 than a series of shifts, adds, leas or anything else.

0
adăugat
Am postat codul pe care l-am primit.
adăugat autor Matt, sursa
Da, știu toate astea. Întrebarea mea a fost că merită așezat structura cu spațiu suplimentar doar pentru a face un număr "rounder" (cum ar fi 12 lungi în loc de 11 longs), care ar evita utilizarea unui multiplicator în calculul indexului matricei?
adăugat autor Matt, sursa
@Matt: nimeni nu poate răspunde în general - căptușeala nu vine gratuit (dimensiuni cache); nu ghici, măsură!
adăugat autor Christoph, sursa
3. I tried increasing the size of Foo to 88 bytes (11 longs) and the leaq instruction became an imulq. Would it make sense to design my structs to have "rounder" sizes to avoid the multiply instructions (in order to optimize array access)?

Apelul leaq este (în esență și în acest caz) calculul k * a + b unde "k" este 1, 2, 4 sau 8 și "a" și "b" sunt registre. Dacă "a" și "b" sunt aceleași, poate fi folosit pentru structuri de 1, 2, 3, 4, 5, 8 și 9 lungi.

Structurile mai mari, cum ar fi 16 lungi pot pot fi optimizabile prin calcularea offsetului pentru "k" și dublare, dar nu știu dacă acesta este ceea ce compilatorul va face efectiv; va trebui să testați.

0
adăugat
Am încercat-o cu doisprezece și o optimizează. ("% rsi,% rsi, 2),% rcx " si apoi " shlq $ 5,% rcx ") vă permite să spuneți de la 88 la 96 doar pentru a evita o multiplicare în timpul accesului la matrice (presupunând că voi face o mulțime de acces la matrice).
adăugat autor Matt, sursa
Ah, scuze. Dacă memoria este mai puțin importantă decât performanța și puteți avea încredere că imulul va fi evitat, atunci da, aș face-o. (Introduceți aici avertismentul standard privind pre-optimizarea și testarea pentru verificare.)
adăugat autor DocMax, sursa
      
  1. Care este scopul pushq% rbp, movq% rsp,% rbp și popq% rbp, dacă nici rbp, nici rsp nu este folosit în corpul funcției?
  2.   

Pentru a urmări cadrele atunci când utilizați un program de depanare. Adăugați codul -fomit-frame-pointer pentru a optimiza (rețineți că ar trebui activat la -O3 , dar într-o mulțime de versiuni gcc nu este).

0
adăugat