assembly - 复制到 NASM 中的阵列

标签 assembly x86-64 nasm

我必须编写汇编代码,循环复制内存中的 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 和汇编的新手,所以请帮助:-)

最佳答案

I know about rep movsb but task has been to make it in loop byte after byte, I don't know if it could be done better way.

如果您必须一次循环 1 个字节,以下是如何有效地执行此操作的方法。值得一提的是,因为有效循环对于 memcpy 以外的情况很有用。还有!

首先,您知道循环体应该至少运行一次,因此您可以使用底部有条件分支的普通循环结构。 (Why are loops always compiled into "do...while" style (tail jump)?)

其次,如果您根本不打算展开,那么您应该使用索引寻址模式以避免增加两个指针。 (但实际上最好展开)。

如果没有必要,不要使用 16 位寄存器。首选 32 位操作数大小 (ECX);写入 32 位寄存器会隐式零扩展到 64 位,因此使用索引作为寻址模式的一部分是安全的。


您可以使用索引加载但使用非索引存储,这样您的存储地址 uops 仍然可以在端口 7 上运行,这使得它在 Haswell/Skylake 上对超线程更加友好。并避免在 Sandybridge 上分层。显然,一次复制 1 个字节对于性能来说完全是垃圾,但有时您确实想要循环并实际上对寄存器中的每个字节做一些事情,并且您可以不需要使用 SSE2 手动对其进行矢量化(一次处理 16 个字节)。

您可以通过相对于 dst 索引 src 来实现此目的。

或者另一个技巧是将负索引计数到零,这样就可以避免额外的 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 之后使用它.


相对于 dst 索引 src:

通常你的输入和输出并不都在静态存储中,所以你必须sub运行时的地址。在这里,如果我们像您最初那样将它们放在同一部分中,mov rcx, a-b实际上会组装。但如果没有,NASM 就会拒绝。

事实上,我可以不使用 2 寄存器寻址模式,而是这样做 [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 CPU 上,CMP/JCC 可以宏融合到 1 uop,但 INC/JCC 不能,因此最后的额外 CMP 与索引基本上是免费的。 (代码大小除外)。

在英特尔上,这避免了索引存储。在这种情况下,负载是纯负载,因此无论如何它都是单个 uop,无需与 ALU uop 保持微融合。 Intel 可以宏熔断 inc/jcc所以这确实会花费额外的 uop 循环开销。

如果您正在展开,如果您不需要避免加载的索引寻址模式,那么这种循环方式很好。但如果您使用 ALU 指令的内存源,如 vaddps ymm0, ymm1, [rdi] ,那么是的,您应该分别递增两个指针,以便可以对加载和存储使用非索引寻址模式,因为 Intel CPU 这样效率更高。 (端口 7 存储 AGU 仅处理非索引,一些微熔断负载使用索引寻址模式未层压。Micro fusion and addressing modes)

关于assembly - 复制到 NASM 中的阵列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56409664/

相关文章:

c - 如何使ARM1136JFS (ARM v6) MMU在物理地址空间和虚拟地址空间之间有一对一的映射?

assembly - 64 位内核中读取高半地址时出现页面错误

assembly - 打印有符号整数优化

C++ 和汇编代码 (NASM) 相互调用

无法将参数从 C 传递到汇编代码

gcc - 如何强制 gcc 对 asm 中的操作数使用两个不同的寄存器?

c++ - 有没有运行时 C++ 汇编程序库?

ios - 超频崩溃:return NSMutableString as NSString to use

c++ - 在x64上使用非临时存储获取/释放语义

linux - Linux 如何知道它在 x86-64 架构上的最大物理地址范围?