c++optimizationloopscryengine

How does branch masking work in CryENGINE 3?


This part of the CryENGINE SDK headers caught my attention:

branchmask.h

#ifndef __BRANCHLESS_MASK__
#define __BRANCHLESS_MASK__

///////////////////////////////////////////
// helper functions for branch elimination
//
// msb/lsb - most/less significant byte
//
// mask - 0xFFFFFFFF
// nz   - not zero
// zr   - is zero

ILINE const uint32 nz2msb(const uint32 x)
{
    return -(int32)x | x;
}

ILINE const uint32 msb2mask(const uint32 x)
{
    return (int32)(x) >> 31;
}

ILINE const uint32 nz2one(const uint32 x)
{
    return nz2msb(x) >> 31; // int((bool)x);
}

ILINE const uint32 nz2mask(const uint32 x)
{
    return (int32)msb2mask(nz2msb(x)); // -(int32)(bool)x;
}


ILINE const uint32 iselmask(const uint32 mask, uint32 x, const uint32 y)// select integer with mask (0xFFFFFFFF or 0x0 only!!!)
{
    return (x & mask) | (y & ~mask);
}


ILINE const uint32 mask_nz_nz(const uint32 x, const uint32 y)// mask if( x != 0 && y != 0)
{
    return msb2mask(nz2msb(x) & nz2msb(y));
}

ILINE const uint32 mask_nz_zr(const uint32 x, const uint32 y)// mask if( x != 0 && y == 0)
{
    return msb2mask(nz2msb(x) & ~nz2msb(y));
}


ILINE const uint32 mask_zr_zr(const uint32 x, const uint32 y)// mask if( x == 0 && y == 0)
{
    return ~nz2mask(x | y);
}

#endif//__BRANCHLESS_MASK__

Could someone throw a short explanation how exactly are these functions intended to be used to reduce branches? ILINE I suppose is predefined force inline or something like that. I searched Google about it, but all I found were copies of the CryENGINE headers uploaded in different sites, but no discussions about this specific one.


Solution

  • These functions return bit-masks that can be and'd with results in other calculations, in order to perform operations without conditionals, and thus without introducing branches.

    For example:

    So if you have code like (with x86 instructions for reference):

    if(a != 0) x += y;
        //  test        ebx,ebx  
        //  je          skip  
        //  add         dword ptr [x],eax  
        // skip:
    

    You can replace it with:

    x += y & (nz2mask(a));
        //  mov     ecx,ebx  
        //  neg     ecx  
        //  or      ecx,ebx  
        //  sar     ecx,1Fh  
        //  and     ecx,eax  
        //  add     ecx,dword ptr [x]  
    

    It produces more instructions (at least on x86), but it avoids a branch.

    Then there are additional functions like iselmask() which allow the selection of either input based on the mask provided, so you could replace:

    x = (a != 0) ? r1 : r2;
    

    with

    x = iselmask(nz2mask(a), r1, r2);
    

    Again, these functions should inline and compile down to relatively efficient assembler, trading off a bit of extra maths for no branching.