2014-11-02 1 views
-1

Я практиковал Assembler давно, и мне хотелось бы понять простую программу (я генерирую код ассемблера из кода C), который добавляет 2 вектора (фактически 2 массива) и сохраняет результат в другом векторе (выходной массив). Моя цель - изучить вектологию. Для этого я использую gcc-4.9 под Debian Wheezy на i7-ядерном процессоре.Понимание простой программы осанки

Здесь код С фрагмент (не векторизованы версия):

#include <stdio.h> 

#define SIZE 10000 

void test(double *a, double *b, double *c) 
{ 
    int i; 

    for (i = 0; i < SIZE; i++) 
    { 
    c[i] = a[i] + b[i]; 
    } 
} 

int main() 
{ 
int i; 
double tab1[SIZE]; 
double tab2[SIZE]; 
double tab3[SIZE]; 

for (i = 0; i < SIZE; i++) 
    { 
    tab1[i] = i; 
    tab2[i] = i; 
    tab3[i] = 0; 
    } 

test(tab1, tab2, tab3); 

for (i = 0; i < SIZE; i++) 
    printf(" tab3[%d] = %f\n", i, tab3[i]); 

return 0; 
} 

сгенерировать Ассемблер код с AT & T синтаксис:

gcc -std=c99 -c main_no_vectorized.c -O3 -S -o main_no_vectorized.s 

Вот код сборки:

.file "main_no_vectorized.c" 
    .section .text.unlikely,"ax",@progbits 
.LCOLDB0: 
    .text 
.LHOTB0: 
    .p2align 4,,15 
    .globl test 
    .type test, @function 
test: 
.LFB3: 
    .cfi_startproc 
    leaq 16(%rdx), %rax 
    leaq 16(%rsi), %rcx 
    cmpq %rax, %rsi 
    setae %r8b 
    cmpq %rcx, %rdx 
    setae %cl 
    orb %cl, %r8b 
    je .L7 
    cmpq %rax, %rdi 
    leaq 16(%rdi), %rax 
    setae %cl 
    cmpq %rax, %rdx 
    setae %al 
    orb %al, %cl 
    je .L7 
    testb $8, %dil 
    pushq %r12 
    .cfi_def_cfa_offset 16 
    .cfi_offset 12, -16 
    pushq %rbp 
    .cfi_def_cfa_offset 24 
    .cfi_offset 6, -24 
    pushq %rbx 
    .cfi_def_cfa_offset 32 
    .cfi_offset 3, -32 
    je .L8 
    movsd (%rdi), %xmm0 
    movl $9998, %ebp 
    movl $4999, %r9d 
    movl $9999, %r12d 
    movl $1, %r8d 
    movl $1, %ebx 
    addsd (%rsi), %xmm0 
    movsd %xmm0, (%rdx) 
.L3: 
    salq $3, %r8 
    xorl %eax, %eax 
    xorl %ecx, %ecx 
    leaq (%rdi,%r8), %r11 
    leaq (%rsi,%r8), %r10 
    addq %rdx, %r8 
    .p2align 4,,10 
    .p2align 3 
.L4: 
    movupd (%r10,%rax), %xmm0 
    addl $1, %ecx 
    addpd (%r11,%rax), %xmm0 
    movups %xmm0, (%r8,%rax) 
    addq $16, %rax 
    cmpl %r9d, %ecx 
    jb .L4 
    cmpl %ebp, %r12d 
    leal (%rbx,%rbp), %eax 
    je .L1 
    cltq 
    movsd (%rdi,%rax,8), %xmm0 
    addsd (%rsi,%rax,8), %xmm0 
    movsd %xmm0, (%rdx,%rax,8) 
.L1: 
    popq %rbx 
    .cfi_remember_state 
    .cfi_restore 3 
    .cfi_def_cfa_offset 24 
    popq %rbp 
    .cfi_restore 6 
    .cfi_def_cfa_offset 16 
    popq %r12 
    .cfi_restore 12 
    .cfi_def_cfa_offset 8 
    ret 
    .p2align 4,,10 
    .p2align 3 
.L8: 
    .cfi_restore_state 
    movl $10000, %ebp 
    movl $5000, %r9d 
    movl $10000, %r12d 
    xorl %r8d, %r8d 
    xorl %ebx, %ebx 
    jmp .L3 
.L7: 
    .cfi_def_cfa_offset 8 
    .cfi_restore 3 
    .cfi_restore 6 
    .cfi_restore 12 
    xorl %eax, %eax 
    .p2align 4,,10 
    .p2align 3 
.L2: 
    movsd (%rdi,%rax), %xmm0 
    addsd (%rsi,%rax), %xmm0 
    movsd %xmm0, (%rdx,%rax) 
    addq $8, %rax 
    cmpq $80000, %rax 
    jne .L2 
    rep ret 
    .cfi_endproc 
.LFE3: 
    .size test, .-test 
    .section .text.unlikely 
.LCOLDE0: 
    .text 
.LHOTE0: 
    .section .rodata.str1.1,"aMS",@progbits,1 
.LC3: 
    .string " tab3[%d] = %f\n" 
    .section .text.unlikely 
.LCOLDB4: 
    .section .text.startup,"ax",@progbits 
.LHOTB4: 
    .p2align 4,,15 
    .globl main 
    .type main, @function 
main: 
.LFB4: 
    .cfi_startproc 
    pushq %rbx 
    .cfi_def_cfa_offset 16 
    .cfi_offset 3, -16 
    xorl %eax, %eax 
    subq $240016, %rsp 
    .cfi_def_cfa_offset 240032 
    movdqa .LC2(%rip), %xmm3 
    leaq 32(%rsp), %rcx 
    leaq 80032(%rsp), %rdx 
    movdqa .LC1(%rip), %xmm1 
    .p2align 4,,10 
    .p2align 3 
.L21: 
    pshufd $238, %xmm1, %xmm0 
    cvtdq2pd %xmm1, %xmm2 
    paddd %xmm3, %xmm1 
    movaps %xmm2, 16(%rsp,%rax) 
    cvtdq2pd %xmm0, %xmm0 
    movaps %xmm2, 80016(%rsp,%rax) 
    movaps %xmm0, (%rcx,%rax) 
    movaps %xmm0, (%rdx,%rax) 
    addq $32, %rax 
    cmpq $80000, %rax 
    jne .L21 
    leaq 160016(%rsp), %rdi 
    movl $80000, %edx 
    xorl %esi, %esi 
    call memset 
    xorl %eax, %eax 
    .p2align 4,,10 
    .p2align 3 
.L22: 
    movapd 16(%rsp,%rax), %xmm0 
    addpd 80016(%rsp,%rax), %xmm0 
    movaps %xmm0, 160016(%rsp,%rax) 
    addq $16, %rax 
    cmpq $80000, %rax 
    jne .L22 
    xorl %ebx, %ebx 
    .p2align 4,,10 
    .p2align 3 
.L23: 
    movsd 160016(%rsp,%rbx,8), %xmm4 
    movl %ebx, %esi 
    movl $.LC3, %edi 
    movl $1, %eax 
    addq $1, %rbx 
    movapd %xmm4, %xmm0 
    movsd %xmm4, 8(%rsp) 
    call printf 
    cmpq $10000, %rbx 
    jne .L23 
    addq $240016, %rsp 
    .cfi_def_cfa_offset 16 
    xorl %eax, %eax 
    popq %rbx 
    .cfi_def_cfa_offset 8 
    ret 
    .cfi_endproc 
.LFE4: 
    .size main, .-main 
    .section .text.unlikely 
.LCOLDE4: 
    .section .text.startup 
.LHOTE4: 
    .section .rodata.cst16,"aM",@progbits,16 
    .align 16 
.LC1: 
    .long 0 
    .long 1 
    .long 2 
    .long 3 
    .align 16 
.LC2: 
    .long 4 
    .long 4 
    .long 4 
    .long 4 
    .ident "GCC: (Debian 4.9.1-16) 4.9.1" 
    .section .note.GNU-stack,"",@progbits 

Не могли бы вы объяснить мне основные шаги этого выше кода сборки в связи с C код, в частности, функцию «тест», цикл инициализации в основной функции и передаваемые параметры (т. е. где указаны команды push и pop для стека) и эффективное добавление массивов «a» и «b»?

Что соответствует сегментам .L2, .L3, ...? есть ли отношение к кэшу L2, кэш L3?

Извините за эти основные вопросы, но я начинаю с ассемблера Intel x86_64.

Спасибо за вашу драгоценную помощь

+0

Есть две вещи, которые помогут вам понять, что происходит. Во-первых, когда вы сбрасываете код сборки, вам нужно попытаться уменьшить оптимизацию до -O1 (или вообще нет), если есть заявления, которые, похоже, не имеют никакого смысла. Тяжелая оптимизация, в то время как отличная для компилятора, создает некоторый сборщик, который порой распознается только теми, кто пишет процедуры оптимизации. Затем, если прошло некоторое время с тех пор, как вы изучили сборку (например, переместитесь с 32-битного на 64-разрядный), вы столкнетесь с различиями в системных вызовах и в вызове. –

ответ

1

Сгенерированный код сборки довольно сложный. Сначала он проверяет, перекрываются ли массивы a, b и c таким образом, что приведет к сбою оптимизированного цикла. Например, если вы сделали это:

test(tab1, tab2, &tab1[1]); 

то перекрытие будет обнаружен и вызвать код, чтобы перейти к L7 (прямолинейный реализация). Кстати, L обозначает Label, а номера ярлыков генерируются компилятором без особого смысла. Таким образом, L1, L2, L3 и т. Д. Являются просто ярлыками, которые используются для того, чтобы код переходил в разные места. Проверка перекрытия начинается с .LFB3 и заканчивается последним je .L7.

Если не обнаружено перекрытия, будет использоваться оптимизированный цикл. Этот оптимизированный цикл будет пытаться добавить два удвоения за раз, а не только один. Первое, что оптимизированный цикл состоит в том, чтобы выяснить, выровнен ли массив a с 16-байтовой границей (инструкция testb $8, %dil). Если это так, он перейдет на L8, чтобы загрузить набор констант (например, r9 = 5000). Если массив не выровнен, если он пройдет и загрузит другой набор констант (например, r9 = 4999), а также обработает первый элемент.Это происходит из-за того, что негласному случаю потребуется выполнить 4999 итераций по два за один раз и обрабатывать первый и последний не выровненные элементы отдельно от цикла. Выровненный корпус будет просто выполнять 5000 итераций.

В любом случае, код достигает L3 следующим образом. Код в L3 и L4 является оптимизированным циклом, который добавляет два за один раз, используя команду addpd (неоптимизированный цикл на L7 используется addsd, чтобы сделать одно добавление за раз). После завершения цикла L4 он проверяет, нужно ли ему обрабатывать последний элемент (для неравномерного случая). Затем он возвращается с инструкцией ret.

Кстати, это помогает знать, что, когда test называется, a в rdi, b в rsi и c в rdx. Это соглашение о вызове для 64-битного. Поэтому в стек нет аргументов. Если вы не слишком хорошо разбираетесь в сборке x86, сконцентрируйтесь на коде, начинающемся с L7. Это неоптимизированная версия, и вы должны учесть эту часть, учитывая, что я сказал, что ваши три аргумента были в rdi, rsi и rdx.

0

.L2 и такие ярлыки, они используются для обозначения следующей инструкции. Они в значительной степени похожи на ярлыки на C, если вы использовали goto. Основное использование метки - это прыжок или ветвь, чтобы указать, куда идет переход.

Например, .L2 метки является начало тела вашего for (i = 0; i < SIZE; i++) петли в test(), он рассчитывает на 8 байт (размер с double) до 8 * 10000. Последняя инструкция в цикле jne .L2, которая перескакивает до .L2, если предыдущее сравнение не было равным.

Вы можете найти this reference (PDF) на x64 полезной.