Branchless code that maps zero, negative, and positive to 0, 1, 2

后端 未结 10 2313
南笙
南笙 2020-12-01 11:21

Write a branchless function that returns 0, 1, or 2 if the difference between two signed integers is zero, negative, or positive.

Here\'s a version with branching:

相关标签:
10条回答
  • 2020-12-01 11:38

    Assuming 2s complement, arithmetic right shift, and no overflow in the subtraction:

    #define SHIFT (CHARBIT*sizeof(int) - 1)
    
    int Compare(int x, int y)
    {
        int diff = x - y;
        return -(diff >> SHIFT) - (((-diff) >> SHIFT) << 1);
    }
    
    0 讨论(0)
  • 2020-12-01 11:40

    Good god, this has haunted me.

    Whatever, I think I squeezed out a last drop of performance:

    int compare(int a, int b) {
        return (a != b) << (a > b);
    }
    

    Although, compiling with -O3 in GCC will give (bear with me I'm doing it from memory)

    xorl  %eax, %eax
    cmpl  %esi, %edi
    setne %al
    cmpl  %esi, %edi
    setgt %dl
    sall  %dl, %eax
    ret
    

    But the second comparison seems (according to a tiny bit of testing; I suck at ASM) to be redundant, leaving the small and beautiful

    xorl  %eax, %eax
    cmpl  %esi, %edi
    setne %al
    setgt %dl
    sall  %dl, %eax
    ret
    

    (Sall may totally not be an ASM instruction, but I don't remember exactly)

    So... if you don't mind running your benchmark once more, I'd love to hear the results (mine gave a 3% improvement, but it may be wrong).

    0 讨论(0)
  • 2020-12-01 11:42
    int Compare(int x, int y)
    {
        int diff = x - y;
    
        int absdiff = 0x7fffffff & diff; // diff with sign bit 0
        int absdiff_not_zero = (int) (0 != udiff);
    
        return
            (absdiff_not_zero << 1)      // 2 iff abs(diff) > 0
            -
            ((0x80000000 & diff) >> 31); // 1 iff diff < 0
    }
    
    0 讨论(0)
  • 2020-12-01 11:43

    Combining Stephen Canon and Tordek's answers:

    int Compare(int x, int y)
    {
        int diff = x - y; 
        return -(diff >> 31) + (2 & (-diff >> 30));
    } 
    

    Yields: (g++ -O3)

    subl     %esi,%edi 
    movl     %edi,%eax
    sarl     $31,%edi
    negl     %eax
    sarl     $30,%eax
    andl     $2,%eax
    subl     %edi,%eax 
    ret 
    

    Tight! However, Paul Hsieh's version has even fewer instructions:

    subl     %esi,%edi
    leal     0x7fffffff(%rdi),%eax
    sarl     $30,%edi
    andl     $2,%edi
    shrl     $31,%eax
    leal     (%rdi,%rax,1),%eax
    ret
    
    0 讨论(0)
  • 2020-12-01 11:51

    For 32 signed integers (like in Java), try:

    return 2 - ((((x >> 30) & 2) + (((x-1) >> 30) & 2))) >> 1;
    

    where (x >> 30) & 2 returns 2 for negative numbers and 0 otherwise.

    x would be the difference of the two input integers

    0 讨论(0)
  • 2020-12-01 12:02

    Two's complement:

    #include <limits.h>
    #define INT_BITS (CHAR_BITS * sizeof (int))
    
    int Compare(int x, int y) {
        int d = y - x;
        int p = (d + INT_MAX) >> (INT_BITS - 1);
        d = d >> (INT_BITS - 2);
        return (d & 2) + (p & 1);
    }
    

    Assuming a sane compiler, this will not invoke the comparison hardware of your system, nor is it using a comparison in the language. To verify: if x == y then d and p will clearly be 0 so the final result will be zero. If (x - y) > 0 then ((x - y) + INT_MAX) will set the high bit of the integer otherwise it will be unset. So p will have its lowest bit set if and only if (x - y) > 0. If (x - y) < 0 then its high bit will be set and d will set its second to lowest bit.

    0 讨论(0)
提交回复
热议问题