0

我正在循环对两个数组求和。我的目标是通过避免进位检查来做到这一点c = a + b; carry = (c<a)。当我使用指令CF进行循环测试时,我丢失了。cmp

目前,我正在使用 andJESTC测试和设置之前保存的CF. 但是跳跃需要更少的7个周期,这对于我想要的来说已经很多了。

   //This one is working
   asm(
        "cmp $0,%0;"
        "je 0f;"
        "stc;"
    "0:"   
        "adcq %2, %1;"
        "setc %0"

    : "+r" (carry), "+r" (anum)
    : "r" (bnum)
   );

我已经尝试过使用SAHF(2 + 2(mov) 循环),但这不起作用。

   //Do not works
   asm(
        "mov %0, %%ah;"
        "sahf;"
        "adcq %2, %1;"
        "setc %0"

        : "+r" (carry), "+r" (anum)
        : "r" (bnum)
   );

有谁知道设置CF更快的方法?像直接移动或类似的东西..

4

2 回答 2

3

没有破坏的循环CF会更快。请参阅该链接以获得更好的 asm 循环。

不要试图adc在 C 循环中只编写 with inline asm。这不可能是最优的,因为你不能要求 gcc 不要破坏标志。尝试使用 GNU C 内联 asm 学习 asm 比编写独立函数更难,尤其是。在这种情况下,您试图保留进位标志。

您可以使用setnc %[carry]来保存和subb $1, %[carry]恢复。(或者cmpb $1, %[carry]我猜。)或者正如斯蒂芬指出的那样,negb %[carry]

0 - 1产生进位,但1 - 1没有。

使用uint8_tto 变量来保存进位,因为您永远不会将它直接添加到%[anum]. 这避免了任何部分寄存器减速的机会。例如

uint8_t carry = 0;
int64_t numa, numb;

for (...) {
    asm ( "negb   %[carry]\n\t"
          "adc    %[bnum], %[anum]\n\t"
          "setc   %[carry]\n\t"
          : [carry] "+&r" (carry), [anum] "+r" (anum)
          : [bnum] "rme" (bnum)
          : // no clobbers
        );
}

您还可以为寄存器源 reg/mem dest 提供替代约束模式。我使用x86"e"约束而不是"i",因为 64 位模式仍然只允许 32 位符号扩展立即数。gcc 必须自己将更大的编译时常量放入寄存器中。进位是早期破坏的,所以即使它和bnum两者都1开始,gcc 也不能为两个输入使用相同的寄存器。

这仍然很糟糕,并且将循环携带的依赖链的长度从 2c 增加到 4c(Intel pre-Broadwell),或从 1c 增加到 3c(Intel BDW/Skylake 和 AMD)。

因此,您的循环以 1/3 的速度运行,因为您使用的是 kludge 而不是在 asm 中编写整个循环。


此答案的先前版本建议直接添加进位,而不是将其恢复为CF. 这种方法有一个致命的缺陷:它将进入本次迭代的进位与进入下一次迭代的输出进位混合在一起。


此外,sahf是从标志设置 AH。 lahf是将 AH 加载到标志中(并且它对标志的整个低 8 位进行操作。将这些指令配对;不要使用lahfsetc.

阅读 insn set 参考手册,了解任何似乎没有按照您的预期做的 insn。请参阅https://stackoverflow.com/tags/x86/info

于 2016-02-09T18:09:11.523 回答
0

如果在编译时知道数组大小,则可以执行以下操作:

#include <inttypes.h>
#include <malloc.h>
#include <stdio.h>
#include <memory.h>

#define str(s) #s
#define xstr(s) str(s)

#define ARRAYSIZE 4

asm(".macro AddArray2 p1, p2, from, to\n\t"
    "movq (\\from*8)(\\p2), %rax\n\t"
    "adcq %rax, (\\from*8)(\\p1)\n\t"
    ".if \\to-\\from\n\t"
    "   AddArray2 \\p1, \\p2, \"(\\from+1)\", \\to\n\t"
    ".endif\n\t"
    ".endm\n");

asm(".macro AddArray p1, p2, p3\n\t"
    "movq (\\p2), %rax\n\t"
    "addq %rax, (\\p1)\n\t"
    ".if \\p3-1\n\t"
    "   AddArray2 \\p1, \\p2, 1, (\\p3-1)\n\t"
    ".endif\n\t"
    ".endm");

int main()
{
   unsigned char carry;

   // assert(ARRAYSIZE > 0);

   // Create the arrays
   uint64_t *anum = (uint64_t *)malloc(ARRAYSIZE * sizeof(uint64_t));
   uint64_t *bnum = (uint64_t *)malloc(ARRAYSIZE * sizeof(uint64_t));

   // Put some data in
   memset(anum, 0xff, ARRAYSIZE * sizeof(uint64_t));
   memset(bnum, 0, ARRAYSIZE * sizeof(uint64_t));
   bnum[0] = 1;

   // Print the arrays before the add
   printf("anum: ");
   for (int x=0; x < ARRAYSIZE; x++)
   {
      printf("%I64x ", anum[x]);
   }
   printf("\nbnum: ");
   for (int x=0; x < ARRAYSIZE; x++)
   {
      printf("%I64x ", bnum[x]);
   }
   printf("\n");

   // Add the arrays
   asm ("AddArray %[anum], %[bnum], " xstr(ARRAYSIZE) "\n\t"
        "setc %[carry]" // Get the flags from the final add

       : [carry] "=q"(carry)
       : [anum] "r" (anum), [bnum] "r" (bnum)
       : "rax", "cc", "memory"
   );

   // Print the result
   printf("Result: ");
   for (int x=0; x < ARRAYSIZE; x++)
   {
      printf("%I64x ", anum[x]);
   }
   printf(": %d\n", carry);
}

这给出了这样的代码:

mov    (%rsi),%rax
add    %rax,(%rbx)
mov    0x8(%rsi),%rax
adc    %rax,0x8(%rbx)
mov    0x10(%rsi),%rax
adc    %rax,0x10(%rbx)
mov    0x18(%rsi),%rax
adc    %rax,0x18(%rbx)
setb   %bpl

由于对所有 f 加 1 将完全溢出所有内容,因此上面代码的输出为:

anum: ffffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffff
bnum: 1 0 0 0
Result: 0 0 0 0 : 1

正如所写,ARRAYSIZE 最多可以包含大约 100 个元素(由于 gnu 的宏深度嵌套限制)。好像应该够了。。。

于 2016-02-10T03:35:41.987 回答