我不确定这是否是您要找的内容,我的汇编技能肯定不是最好的(例如缺乏后缀),但这使用了
ADC
,应该解决了您的问题。
请注意省略了C++循环;我们需要在asm中循环,因为我们需要
CF
在迭代之间保持不变。(GCC6具有输出约束标志,但没有输入约束标志;没有办法要求编译器将FLAGS从一个asm语句传递到另一个asm语句,即使有语法也可能会导致gcc通过setc/cmp低效地执行。)
#include <cstdint>
#include <iostream>
#define N 4
int main(int argc, char *argv[]) {
uint64_t ans[N];
const uint64_t a[N] = {UINT64_MAX, UINT64_MAX, 0, 0};
const uint64_t b[N] = {2, 1, 3, 1};
const uint64_t i = N;
asm volatile (
"xor %%eax, %%eax\n\t"
"mov %3, %%rdi\n\t"
".L_loop:\n\t"
"mov (%%rax,%1), %%rdx\n\t"
"adc (%%rax,%2), %%rdx\n\t"
"mov %%rdx, (%%rax, %0)\n\t"
"lea 8(%%rax), %%rax\n\t"
"dec %%rdi\n\t"
"jnz .L_loop\n\t"
:
: "r" (ans), "r" (a), "r" (b), "r" (i)
: "%rax", "%rbx", "%rdx", "%rdi", "memory"
);
for (int i = 0; i < N; ++i)
std::cout << ans[i] << std::endl;
return 0;
}
为了避免设置进位标志(CF),我需要倒数到0以避免执行CMP指令。DEC不会设置进位标志,因此它可能是这个应用程序的完美选择。但是,我不知道如何使用%rdi从数组开头更快地进行索引,而不需要额外的指令和寄存器。
volatile和"memory"清除是必要的,因为我们只向编译器请求指针输入,并没有告诉它我们实际读写的内存。
在一些旧的CPU上,特别是Core2/Nehalem,inc后的adc会导致部分标志停顿。参见ADC/SBB和INC/DEC在某些CPU上紧密循环中的问题。但在现代CPU上,这非常高效。
编辑:
正如@PeterCordes所指出的那样,我的inc %rax
和使用lea缩放8倍是极其低效的(现在想想还很愚蠢)。现在,它只是lea 8(%rax),%rax
。
编辑注:通过使用从数组末尾开始的负索引,我们可以使用更少的指令,向0计数并使用inc / jnz
。
(这将在硬编码中将数组大小设置为4。您可以通过请求数组长度作为立即常量和-i
作为输入来使其更加灵活。或者要求指向结尾的指针。)
asm volatile (
"mov $-3, %[idx]\n\t"
"mov (%[a]), %%rdx \n\t"
"add (%[b]), %%rdx \n\t"
"mov %%rdx, (%0) \n\t"
".L_loop:\n\t"
"mov 8*4(%[a], %[idx], 8), %%rdx\n\t"
"adc 8*4(%[b], %[idx], 8), %%rdx\n\t"
"mov %%rdx, 8*4(%[ans], %[idx], 8)\n\t"
"inc %[idx]\n\t"
"jnz .L_loop\n\t"
: [idx] "+&r" (i)
: [ans] "r" (ans), [a] "r" (a), [b] "r" (b)
: "rdx", "memory"
);
循环标签应该使用
%%=
,以防GCC复制此代码,或者使用像
1:
这样的编号本地标签。
使用缩放索引寻址模式不比我们之前使用的常规索引寻址模式(2个寄存器)更昂贵。理想情况下,我们将为
adc
或存储使用单寄存器寻址模式,可能相对于
ans
索引其他两个数组,通过在输入时减去指针。
但是,然后我们需要单独使用LEA增加8,因为我们仍然需要避免破坏CF。但是,在Haswell及更高版本中,索引存储无法使用端口7上的AGU,在Sandybridge/Ivybridge上,它们会取消层压到2个uops。因此,对于Intel SnB系列,在此处避免索引存储将是有益的,因为我们每次迭代需要2倍的加载+1倍的存储。请参见
Micro fusion and addressing modes
早期的英特尔CPU(Core2 / Nehalem)在上述循环中将出现部分标志停顿,因此上述问题对它们来说是不相关的。
AMD CPU可能可以处理上述循环。
Agner Fog的优化和微体系结构指南没有提到任何严重的问题。不过,对于AMD或英特尔来说,稍微展开一下也无妨。
_addcarry_u64
。 - Christopher Oicles