【发布时间】:2024-01-06 14:36:01
【问题描述】:
我有一个循环,用于将数字与进位相加。
我想知道.done: align 是否会给我带来任何好处?毕竟,每次调用该函数时,它只会在那里分支一次。我知道 C 编译器可能会对齐所有受循环影响的分支。但我认为它不应该造成任何损失(特别是因为我们现在每天都有相当大的指令缓存)。
// // corresponding C function declaration
// int add(uint64_t * a, uint64_t const * b, uint64_t const * c, uint64_t size);
//
// Compile with: gcc -c add.s -o add.o
//
// WARNING: at this point I've not worked on the input registers & registers to save
// do not attempt to use in your C program with this very code.
.text
.p2align 4,,15
.globl add
.type add, @function
add:
test %rcx, %rcx
je .done
clc
xor %rbp, %rbp
.p2align 4,,10
.p2align 3
.loop:
mov (%rax, %rbp, 8), %rdx
adc (%rbx, %rbp, 8), %rdx
mov %rdx, (%rdi, %rbp, 8)
inc %rbp
dec %rcx
jrcxz .done
jmp .loop
// -- is alignment here necessary? --
.done:
setc %al
movzx %al, %rax
ret
英特尔或 AMD 是否有关于此特定案例的明确文档?
实际上我决定通过删除循环来进行简化,因为我只有 3 种尺寸(128、256 和 512),因此编写展开循环很容易。但是,我只需要添加,所以我真的不想为此使用 GMP。
这是应该在你的 C 程序中工作的最终代码。这个是专门针对 512 位的。只需将三个 add_with_carry 用于 256 位,一个用于 128 位版本。
// // corresponding C function declaration
// void add512(uint64_t * dst, uint64_t const * src);
//
.macro add_with_carry offset
mov \offset(%rsi), %rax
adc %rax, \offset(%rdi)
.endm
.text
.p2align 4,,15
.globl add512
.type add512, @function
add512:
mov (%rsi), %rax
add %rax, (%rdi)
add_with_carry 8
add_with_carry 16
add_with_carry 24
add_with_carry 32
add_with_carry 40
add_with_carry 48
add_with_carry 56
ret
请注意,我不需要clc,因为我第一次使用add(忽略进位)。我还把它添加到目的地(即 C 中的 dest[n] += src[n]),因为我的代码中不太可能需要副本。
偏移量允许我不增加指针,每次添加它们只使用一个额外的字节。
【问题讨论】:
-
哦,大声笑,您的尺码偏小且固定?是的,完全展开非常好,并且避免了旧 CPU 的所有部分标志问题。 memory-destination
add是高效的,但是 memory-destinationadc在 Intel CPU 上浪费了微指令;通常比 load +adc (mem), %reg+mov-store 更糟糕:SKL 上的 4 个融合域 uops 用于adc %reg, (mem),而add %reg, (mem)则为 2,因为 Intel CPU 在 uops 内的加载和存储之间没有一致的 TLB一条指令。 (有关英特尔架构师的详细信息,请参阅我在 What happens after a L2 TLB miss? 上的回答。)
标签: loops branch x86-64 memory-alignment micro-optimization