【问题标题】:How does branch masking work in CryENGINE 3?CryENGINE 3 中的分支屏蔽如何工作?
【发布时间】:2012-12-03 09:02:28
【问题描述】:

这部分 CryENGINE SDK 标头引起了我的注意:

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__

有人能简单解释一下这些函数究竟是如何用于减少分支的吗? ILINE 我想是预定义的强制内联或类似的东西。我搜索了谷歌,但我发现的只是上传到不同网站的 CryENGINE 标头的副本,但没有关于这个特定的讨论。

【问题讨论】:

  • 我猜有更好的分支预测
  • @didierc 或者更确切地说,这些示例完全删除了分支。

标签: c++ optimization loops cryengine


【解决方案1】:

这些函数返回位掩码,可以在其他计算中与结果一起使用,以便在没有条件的情况下执行操作,因此不引入分支。

例如:

  • 如果参数是 0nz2mask 返回 0,否则返回 0xffffffff
  • 如果参数的最高位是0msb2mask 返回0,如果是1,则返回0xffffffff

所以如果你有类似的代码(带有 x86 指令供参考):

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

您可以将其替换为:

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

它产生更多指令(至少在 x86 上),但它避免了分支。

还有像iselmask()这样的附加函数,它允许根据提供的掩码选择任一输入,因此您可以替换:

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

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

同样,这些函数应该内联并编译成相对高效的汇编程序,用一些额外的数学运算来换取没有分支。

【讨论】:

  • 赞成。在第一个示例中,我们可以看到有一个测试(if 指令),它在机器代码级别被转换为条件分支。在替换指令中,没有找到更多的测试,机器码不会包含分支。
  • 感谢您的回答,我想我现在会用这个替换我的 if elses :D
  • 在盲目地做这种事情之前一定要分析结果——这并不总是一场胜利。在尝试消除分支之前,您必须知道分支会降低性能。
  • 我做了一个小测试,结果是:输入执行次数 10000 有分支的执行速度是:2926 没有分支的执行速度是:2929 按任意键继续。 . .但是所有程序在 if'else 括号中所做的只是做了大量的 int 操作,也许我应该尝试一些更复杂的东西?编辑:执行 10 万次时,分支几乎快 1000 毫秒 o_O
猜你喜欢
  • 2012-04-29
  • 2014-06-16
  • 2013-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-19
  • 1970-01-01
  • 2018-01-11
相关资源
最近更新 更多