减去并检测下溢,最有效的方法? (x86 / 64与海湾合作委员会)

时间:2014-07-25 14:48:56

标签: gcc assembly x86 underflow carryflag

我使用GCC 4.8.1编译C代码,我需要检测x86 / 64架构上的减法是否发生下溢。两者都是UNSIGNED。我知道在汇编中非常容易,但是我想知道我是否可以用C代码完成它并让GCC以某种方式优化它,因为我无法找到它。这是一个非常常用的函数(或低级,是术语吗?)所以我需要它才能高效,但是GCC似乎太愚蠢了,无法识别这个简单的操作?我尝试了很多方法在C中给它提示,但它总是使用两个寄存器而不是一个子和一个条件跳转。说实话,我很生气地看到这么多次编写的愚蠢代码(函数被称为 lot )。

我在C中的最佳方法似乎如下:

if((a-=b)+b < b) {
  // underflow here
}

基本上,从a中减去b,如果结果下溢检测到它并进行一些条件处理(例如,它与某个值无关,则会带来错误等)。

GCC似乎太愚蠢了,不能将上面的内容简化为一个sub和一个条件跳转,并且相信我在C代码中尝试了很多方法,并尝试了很多命令行选项(-O3和-Os包括课程)。 GCC的作用是这样的(英特尔语法汇编):

mov rax, rcx  ; 'a' is in rcx
sub rcx, rdx  ; 'b' is in rdx
cmp rax, rdx  ; useless comparison since sub already sets flags
jc underflow

毋庸置疑,上述内容是愚蠢的,只要它需要的是:

sub rcx, rdx
jc underflow

这太烦人了,因为GCC确实理解sub修改标志的方式,因为如果我将它转换为&#34; int&#34;除了使用&#34; js&#34;它将生成上面的确切内容。这是带符号跳转而不是进位,如果无符号值差异足以设置高位,则无法工作。然而,它表明它知道影响那些标志的子指令。

现在,也许我应该放弃尝试让GCC正确地优化它并使用内联汇编进行,我没有遇到任何问题。不幸的是,这需要&#34; asm goto&#34;因为我需要一个有条件的JUMP,并且asm goto对输出的效率不高,因为它是不稳定的。

我尝试了一些东西,但我不知道它是否安全&#34;使用与否。 asm goto由于某种原因无法输出。我不想让它将所有寄存器刷新到内存中,这会杀死我执行此操作的整个点,这就是效率。但是如果我使用空的asm语句,输出设置为&#39; a&#39;它之前和之后的变量,是否有效并且安全吗?这是我的宏:

#define subchk(a,b,g) { typeof(a) _a=a; \
  asm("":"+rm"(_a)::"cc"); \
  asm goto("sub %1,%0;jc %l2"::"r,m,r"(_a),"r,r,m"(b):"cc":g); \
  asm("":"+rm"(_a)::"cc"); }

并像这样使用它:

subchk(a,b,underflow)
// normal code with no underflow
// ...

underflow:
  // underflow occured here

它有点难看,但效果很好。在我的测试场景中,它只编译FINE而没有易失性开销(将寄存器刷新到内存中)而不会产生任何不好的东西,它似乎工作正常,但这只是一个有限的测试,我无法在我使用的任何地方测试它这个函数/宏,正如我所说的那样使用很多,所以我想知道某人是否知识渊博,上述结构是否存在不安全之处?

特别是&#39; a&#39;如果发生下溢则不需要,所以考虑到这一点,我的内联asm宏可能会发生任何副作用或不安全的事情吗?如果没有,我会毫无问题地使用它,直到他们优化编译器,所以我可以在我猜之后将其替换回来。

请不要把这变成关于过早优化的讨论,或者不要讨论问题的主题,我完全清楚这一点,谢谢。

3 个答案:

答案 0 :(得分:3)

我可能会错过一些明显的东西,但为什么这不好呢?

extern void underflow(void) __attribute__((noreturn));
unsigned foo(unsigned a, unsigned b)
{
    unsigned r = a - b;
    if (r > a)
    {
        underflow();
    }
    return r;
}

我已经检查过,gcc根据你的需要优化它:

foo:
    movl    %edi, %eax
    subl    %esi, %eax
    jb      .L6
    rep
    ret
.L6:
    pushq   %rax
    call    underflow

当然你可以根据需要处理下溢,我刚刚这样做是为了让asm变得简单。

答案 1 :(得分:0)

以下汇编代码怎么样(你可以把它换成GCC格式):

   sub  rcx, rdx  ; assuming operands are in rcx, rdx
   setc al        ; capture carry bit int AL (see Intel "setxx" instructions)
   ; return AL as boolean to compiler  

然后调用/内联汇编代码,并对结果布尔值进行分支。

答案 2 :(得分:0)

您是否测试过这是否实际更快?现代x86-microarchitectures使用微代码,将单个汇编指令转换为更简单的微操作序列。其中一些还进行微操作融合,其中一系列汇编指令被转换为单个微操作。特别是像test %reg, %reg; jcc target这样的序列被融合,可能是因为全局处理器标志是性能的祸根 如果cmp %reg, %reg; jcc target是mOp融合的,gcc可能会使用它来获得更快的代码。根据我的经验,gcc 非常擅长调度和类似的低级优化。