【问题标题】:How to round a byte to 0 or 255 using shift operators如何使用移位运算符将字节舍入为 0 或 255
【发布时间】:2014-06-21 14:41:37
【问题描述】:

为了在我的 C# 项目中重新计算位图图像中的像素,我想将我的 RGB 值四舍五入到 255 或向下舍入到 0;

每个值都是一个字节。

现在我正在做以下事情:

(byte)(Pixels[i] < 128 ? 0 : 255);

我确信这可以更快地实现,并且无需使用按位操作进行类型转换。我该怎么做呢?

【问题讨论】:

  • 嗯,只有 256 个。将它们以二进制形式列出,然后观察是否存在小于 128 的特征与大于 127 的特征不同的特征。然后使用该功能。
  • 嗯,改成整数,右移 7 位,得到 0 或 1。取反,得到 0 或 -1。转换回字节,可以吗?避免分支,但实际上可能会更慢。
  • 这是一个合理的问题。三元语句构成条件分支,这会损害超标量处理器中的指令级并行性。
  • 您已将问题更改为现在要求更快解决方案。请记住,如果不在您的实际硬件上尝试,就无法回答性能问题,请记住您需要优化最慢的东西 - 这可能不是它 - 还要记住你不想要更快的解决方案,你想要 足够快 的解决方案。设定一个目标,然后检查你是否达到了目标。如果您实现了目标,去海滩,这是美好的一天。不要浪费时间让已经足够快的代码更快。
  • @EricLippert:去海滩?这应该比摆弄比特更令人着迷吗?

标签: c# bit-manipulation rounding


【解决方案1】:
   (byte)(Pixels[i] < 128 ? 0 : 255)

是的,如果位图包含太多随机数据,由于分支预测不佳,这往往会表现不佳。抖动不会为这样的语句生成条件移动。

您可以使用一个技巧,右移会保留有符号整数值的符号位。这使得这段代码工作:

   (byte)((sbyte)Pixels[i] >> 7)

生成没有分支的代码:

000000a7  movsx       eax,byte ptr [edx+eax+8]  ; Pixels[i], sign extended to 32-bits
000000ac  sar         eax,7                     ; >> operator
000000af  and         eax,0FFh                  ; (byte) cast

【讨论】:

  • 你可能需要unchecked 来做最后的演员,不是吗?
  • 字节是规则的某种例外吗?我只知道从Int32 转换为UInt32 时我需要unchecked,因为我想要将负值翻转为正值。
  • 当然,IL 不支持对较小值类型的操作。表达式的结果是 int,而不是 sbyte
  • 您可以(可能)更快地使用 32 位 int 一次处理 4 个值,如下所示:((pixel[i] &gt;&gt; 7) &amp; 0x01010101) * 0x000000FF 和 64 位一次处理 8 个值的类似操作。您只需将每个字节的 msb 向下移动并屏蔽掉每个字节的高位。乘法将 now-lsb 扇形到字节中的所有其他位位置。
【解决方案2】:

几种可能性:

  • (byte)((b &lt;&lt; 24) &gt;&gt; 31);
  • (byte)((sbyte)b &gt;&gt; 31);
  • (uint)(int)(sbyte)b &gt;&gt; 24;

前两个技巧是将大数映射为负值,然后使用有符号右移将结果转换为 -1 或 0,最后转换回字节。

最后一个在理论上更好,因为它可以编译成movsx eax,... shr eax, 24,最后不用屏蔽。但我怀疑 .NET JITter 是否意识到这一点。

只能在未经检查的上下文中使用。

【讨论】:

  • 我不知道字节中有 32 位! ;)
  • @TheBuzzSaw 移位提升为 32 位整数 ;)
【解决方案3】:

使用(预填充的)查找表可能会获得最佳性能。

var lookup = Enumerable.Range(0, 256).Select(i => i < 128 ? (byte)0 : 255).ToArray();

由于您只有 256 个值,因此它可以驻留在 L1 缓存中,这意味着它的访问不会比算术计算更昂贵:

Pixels[i] = lookup[Pixels[i]];

【讨论】:

  • 根据我的经验,数组边界检查会使这样的代码减慢到低于基于移位的解决方案的性能
  • @CodesInChaos:好点;但是,由于边界检查总是会成功,因此它的成本很可能可以由分支预测器摊销。
  • 我的经验是,尽管边界检查总是成功并且分支预测器运行良好,但它们在实践中仍然很昂贵。 JITter 有时可以优化它们,但我很确定在您的示例中不会这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多