【问题标题】:Branchless Overflow Handling无分支溢出处理
【发布时间】:2017-02-13 03:25:14
【问题描述】:

我正在尝试创建一种安全缓冲区,它可以自动处理溢出而无需任何分支。缓冲区大小是 2 的幂,并且只能具有有效的正数(即不包括零)索引。它还允许检查删除,如果存储在该索引处的元素等于搜索键,则在给定索引处删除。

我基本上是为了这样的事情

Element *buffer[256];

inline void buffer_insert(size_t index, Element *elem){
  buffer[index < 256 && index] = elem;
}

//Optional: checked insert to prevent overwrite. Will only insert
//if the buffer holds NULL at index.
inline void buffer_checkedInsert(size_t index, Element * elem){
  buffer[index && !buffer[index < 256 && index]] = elem;  
}

inline void buffer_checkedRemove(size_t index, Element *elem){
  buffer[0] = NULL; //Maybe useful if buffer[0] stores elem
  buffer[((elem == buffer[index < 256 && index)) && index] = NULL;
}

所以我基本上想在传入的索引超出范围时访问索引 0,因为buffer[0] 不是有效的缓冲区索引。而且我还想在要删除的元素不等于传递给删除的元素时访问索引 0,如果缓冲区包含索引处的内容,我可能还想访问索引 0。

我的问题是:

  • 我所拥有的真的没有分支吗?因为如果 C 编译器决定对 && 使用短路,代码可能会分支。
  • 如果 && 导致分支,在这种情况下是否有替代方案具有相同行为但不涉及分支?
  • 这能比基本的溢出检查更快吗?或者 C 编译器能否以某种方式提供if(index &lt; 256) buffer[index] = elem 的无分支版本?

【问题讨论】:

  • &amp;&amp; 设计为短路。它的使用通常会发出一个分支。使用比较运算符的结果作为值也可能导致发出分支,具体取决于架构(在 x86 上不会)。
  • 作为一个概念性问题:想想是否让越界读取和写入静默不做任何事情而不是崩溃,这是否真的更好。还要考虑无分支代码是否真的值得额外的长度。几乎从不进行的跳转非常便宜,我认为您不会偶尔触发溢出检查。
  • &amp;&amp; 不会像您认为的那样做。例如&amp;&amp; 的结果只能是 01
  • 你想要(index &lt; 256) * index
  • @Navneeth:答案完全取决于架构、全局数组是否为const、编译器实现、代码的热度(确定数组是否保存在缓存行中)等. 除了实际测试之外,没有办法知道。奇怪的是,乘法不会变慢;整数乘法在现代芯片上速度很快,只要您没有在附近执行其他乘法运算,乘法将在(摊销的)1-2 个周期内完成,这与按位与(通常可以分派具有更多并行性,但负载可能会花费您)。

标签: c assembly branch-prediction


【解决方案1】:

我所拥有的真的没有分支吗?因为如果 C 编译器决定在 && 上使用短路,代码可能会分支。

也许吧。在这些情况下,编译器可能足够聪明,可以发出无分支机器代码,但你不能依赖它。

如果 && 导致分支,在这种情况下是否有替代方案具有相同行为但不涉及分支?

你的问题有点混乱。编译器可以发出分支代码来实现&amp;&amp; 操作的事实源于该操作的定义行为。任何具有相同行为的替代方案都必须提供相同的分支可能性。

另一方面,如果您的意思是问是否有一个替代方案可以在所有情况下计算相同的结果,那么是的,您可以重写这些表达式来这样做,而不会出现分支.例如,您可以像这样使用&amp;* 运算符:

buffer[(index < 256) & (index != 0)] = elem;

或者,您可以实现您真正想要的行为:

buffer[(index < 256) * index] = elem;

没有理由认为编译器会为这些计算中的任何一个发出分支指令;如果确实如此,那可能是因为它认为这会提高目标架构的性能。

这能比基本的溢出检查更快吗?或者 C 编译器能否以某种方式提供 if(index

无分支版本当然可以更快。在执行大量(非)分支的工作负载上,它们最有可能明显更快,并且没有易于识别的模式可供选择。但如果(非)分支大多遵循常规模式,特别是如果它几乎总是单向,那么 CPU 的分支预测单元可以进行普通的有效性检查,至少与无分支分配一样快。

最终,如果不对代码在真实数据上的实际性能进行基准测试或对其进行良好的复制,就没有充分的理由担心这一点。结果可能取决于数据,它是否重要取决于您询问的函数花费了多少程序运行时间。除非您有良好的基准要求,否则您应该为清晰和可维护性编写代码。

【讨论】:

  • 谢谢,关于它的介绍。我将它用于内存分配器实现中的缓存。分支缓存实现似乎使分配器在每个测试用例中都变慢了,所以我试图看看这是否会有所改进。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-27
  • 2016-03-30
  • 1970-01-01
相关资源
最近更新 更多