Копирование в массивы в NASM - PullRequest
1 голос
/ 01 июня 2019

Я должен написать код ассемблера, который скопирует 100 байтов в памяти в цикле. Я написал это так:

section .data
    a times 100 db 1 ;reserve 100 bytes and fill with 1
    b times 100 db 0 ;reserve 100 bytes and fill with 0

    section _start
    global _start

    _start:
    mov rsi, a ;get array a address
    mov rdi, b ;get arrat b address

    _for: ;początek pętli
    cmp cx, 100     ;loop
    jae _end_for        ;loop
    push cx         ;loop

    mov byte al, [rsi]  ;get one byte from array a from al
    mov byte [rdi], al  ;put one byte from al to array b
    inc rsi         ;set rsi to next byte in array a
    inc rdi         ;set rdi to next byte in array b

    pop cx          ;loop
    inc cx          ;loop
    jmp _for        ;loop

_end_for:

_end:
    mov rax, 60
    mov rdi, 0
    syscall

Я не уверен насчет копирующей части. Я читаю значение из адреса в регистр, а затем помещаю его в другой. Это выглядит хорошо для меня, но я не уверен насчет увеличения rsi и rdi.

Достаточно ли этого?
Я новичок в NASM и сборке, поэтому, пожалуйста, помогите: -)

Ответы [ 2 ]

3 голосов
/ 02 июня 2019

Я знаю о rep movsb, но задача состояла в том, чтобы сделать его в цикле байт за байтом, я не знаю, можно ли это сделать лучше.

Если у вас есть для зацикливания 1 байта за раз, вот как это сделать эффективно.Стоит упомянуть, потому что эффективный цикл полезен и для других случаев, кроме memcpy!

Прежде всего, вы знаете, что ваше тело цикла должно выполняться хотя бы один раз, поэтому вы можете использовать нормальную структуру цикла сусловная ветвь внизу.( Почему циклы всегда компилируются в стиле "do ... while" (прыжок в хвост)? )

Во-вторых, если вы вообще не собираетесь разворачиваться, вам следует использоватьИндексированный режим адресации, чтобы избежать увеличения обоих указателей.(Но на самом деле было бы лучше развернуть).

И не используйте 16-битные регистры, если вам не нужно.Предпочитают 32-битный размер операнда (ECX);запись 32-битного регистра неявно расширяется до 64-битного, поэтому можно безопасно использовать индекс как часть режима адресации.


Вы можете использовать индексированную загрузку, но неиндексированное хранилище, так чтоваш моп-адрес магазина может все еще работать на порту 7, что делает его более дружественным к гиперпоточности на Haswell / Skylake.И избегать без ламинирования на Sandybridge.Очевидно, что копирование 1 байта за раз является полным мусором для производительности , но иногда вы действительно хотите зацикливаться, и на самом деле делает что-то с каждым байтом, пока он находится в регистре, и вы не можетевручную векторизовать его с помощью SSE2 (чтобы сделать 16 байтов за раз).

Вы можете сделать это путем индексации src относительно dst.

Или другой трюк состоит в подсчете отрицательного индексадо нуля, поэтому вы избегаете лишних cmp.Давайте сначала это сделаем:

default rel       ; use RIP-relative addressing modes by default

ARR_SIZE  equ 100
section .data
    a:  times ARR_SIZE db 1

section .bss
    b:  resb ARR_SIZE       ;reserve n bytes of space in the BSS

    ;section _start   ; do *not* use custom section names unless you have a good reason
                      ; they might get linked with unexpected read/write/exec permission

section .text
global _start
_start:
    lea     rsi, [a+ARR_SIZE]   ; pointers to one-past-the-end of the arrays
    lea     rdi, [b+ARR_SIZE]   ; RIP-relative LEA is better than mov r64, imm64

    mov     rcx, -ARR_SIZE

.copy_loop:                 ; do {
    movzx   eax, byte [rsi+rcx]  ; load without a false dependency on the old value of RAX
    mov     [rdi+rcx], al
    inc     rcx
    jnz    .copy_loop       ; }while(++idx != 0);

.end:
    mov  eax, 60
    xor  edi, edi
    syscall             ; sys_exit(0)

В позиционно-зависимом коде, таком как статический (или другой не-PIE) исполняемый файл Linux, mov edi, b+ARR_SIZE является наиболее эффективным способом помещения статического адреса в регистр.

Не используйте _ для всех названий ваших этикеток._start назван таким образом, потому что имена символов C, начинающиеся с _, зарезервированы для использования реализацией.Это не то, что вы должны скопировать;на самом деле все наоборот.

Используйте .foo для локального имени метки внутри функции.Например, .foo: является сокращением для _start.foo:, если вы используете его после _start.


Индексирование src относительно dst:

Обычно ваш ввод ивыходные данные не находятся в статическом хранилище, поэтому вам нужно sub адресов во время выполнения.Здесь , если мы поместим их обоих в один и тот же раздел, как вы делали изначально, mov rcx, a-b фактически соберется.Но если нет, NASM отказывается.

На самом деле вместо режима адресации с двумя регистрами я мог бы просто делать [rdi + (a-b)] или просто [rdi - ARR_SIZE], потому что я знаю, что они смежные.

_start:
    lea     rdi, [b]   ; RIP-relative LEA is better than mov r64, imm64
    mov     rcx, a-b   ; distance between arrays so  [rdi+rcx] = [a]
;;; for a-b to assemble, I had to move b back to the .data section.

    lea     rdx, [rdi+ARR_SIZE]    ; end_dst pointer

.copy_loop:                 ; do {
    movzx   eax, byte [rdi + rcx]    ; src = dst+(src-dst)
    mov     [rdi], al
    inc     rdi

    cmp     rdi, rdx
    jbe    .copy_loop       ; }while(dst < end_dst);

Указатель конца массива - это то же самое, что вы делали бы в C ++ с foo.end(), чтобы получить указатель / итератор в один конец.

Это необходимоINC + CMP / JCC как издержки цикла.В процессорах AMD CMP / JCC могут макросжаться в 1 моп, а INC / JCC - нет, поэтому дополнительная CMP по сравнению с индексированием с конца в основном бесплатна.(За исключением размера кода).

На Intel это позволяет избежать индексированного хранилища.В этом случае нагрузка является чистой нагрузкой, так что в любом случае это один моп без необходимости микроплавления с меру ALU.Intel может макрособъединить inc/jcc, так что это потребует дополнительных потерь в цикле.

Этот способ зацикливания хорош при развертывании, если вам не нужно избегать режима индексированной адресации длягрузы.Но если вы используете источник памяти для инструкции ALU, такой как vaddps ymm0, ymm1, [rdi], то да, вы должны увеличивать оба указателя по отдельности, чтобы вы могли использовать неиндексированные режимы адресации как для загрузки, так и для хранения, потому что процессоры Intel более эффективны в этом отношении.(Порт AGU хранилища 7 обрабатывает только неиндексированные данные, и некоторые микроплавкие нагрузки не ламинируются в режиме индексированной адресации. Режимы микросинтеза и адресации )

1 голос
/ 02 июня 2019

Достаточно ли этого?

Да; приведенный код достаточен для копирования массива.

Для производительности / оптимизации код, который вы показали, мог бы быть лучше; но оптимизация - это скользкий уклон, который идет в обход «rep movsb лучше для размера кода», проходит через «SIMD с разверткой цикла» и заканчивается на «вы можете избежать необходимости копировать массив».

...