仅使用位操作限制0到4095之间的16位带符号值(无分支)

时间:2012-09-06 21:42:20

标签: c++ c optimization bit-manipulation

我想将signed short变量的值限制在0到4095之间,之后我将最重要的8位作为我在其他地方使用的最终值。现在我正在以下面的基本方式做到这一点:

short color     = /* some external source */;
/* 
 * I get the color value as a 16 bit signed integer from an
 * external source I cannot trust. 16 bits are being used here
 * for higher precision.
 */

if ( color < 0 ) {
    color = 0;
}
else if ( color > 4095 ) {
    color = 4095;
}

unsigned char color8bit  = 0xFF & (color >> 4);
/*
 * color8bit is my final value which I would actually use
 * in my application.
 */

有没有办法只使用位操作,即不使用任何条件?它可能有助于加快速度,因为这个操作在代码中发生了数千次。

以下内容无效,因为它不会处理负值和溢出等边缘情况:

unsigned char color8bit = 0xFF & (( 0x0FFF & color ) >> 4 );

编辑: Adam Rosenfield's answer是采用正确方法但未正确实施的方法。 ouah's answer给出了正确的结果,但采用了我原本打算查找的不同方法。

这是我最终使用的:

const static short min = 0;
const static short max = 4095;
color = min ^ (( min ^ color ) & -( min < color ));
color = max ^ (( color ^ max ) & -( color < max ));
unsigned char color8bit = 0xFF & (( 0x0FFF & color ) >> 4 );

7 个答案:

答案 0 :(得分:7)

是的,请参阅these bit-twiddling hacks

short color = ...;
color = color ^ (color & -(color < 0));  // color = max(color, 0)
color = 4096 ^ ((color ^ 4096) & -(color < 4096));  // color = min(color, 4096)

unsigned char color8bit  = 0xFF & (color >> 4);

这实际上是否真的变得更快,我不知道 - 你应该剖析。如今,大多数现代x86和x86-64芯片都支持“条件移动”指令(cmov),它有条件地存储取决于EFLAGS状态位的值,优化编译器通常会从color >= 0 ? color : 0这样的三元表达式生成这些指令。这些可能会最快,但它们不会在较旧的x86芯片上运行。

答案 1 :(得分:5)

您可以执行以下操作:

BYTE data[0x10000] = { ..... };

BYTE byte_color = data[(unsiged short)short_color];

在你的日子里,64kb表并不是一件令人发指的事情,可能是可以接受的。与其他可能的方法相比,此代码变体中的汇编程序命令数量绝对最小。

答案 2 :(得分:2)

我假设short是16位。

删除否定值:

int16_t mask=-(int16_t)((uint16_t)color>>15);//0xFFFF if +ve, 0 if -ve
short value=color&mask;//0 if -ve, colour if +ve

value现在介于0和32767之间。

然后,您可以执行类似的操作来限制值:

mask=(uint16_t)(value-4096)>>15;//1 if <=4095, 0 if >4095
--mask;//0 if <=4095, 0xFFFF if >4095
mask&=0xFFF;//0 if <=4095, 4095 if >4095

value|=mask;//4095 if >4095, color if <4095

答案 3 :(得分:2)

short color = /* ... */
color =   ((((!!(color >> 12)) * 0xFFF)) | (!(color >> 12) * color ))
        & (!(color >> 15) * 0xFFF);

unsigned char color8bit  = 0xFF & (color >> 4);

它假设两个补码表示。

这具有不使用任何相等或关系运算符的优点。在某些情况下,您需要不惜一切代价避免分支:在某些安全应用程序中,您不希望攻击者执行分支预测。没有分支(特别是在嵌入式处理器中),您可以使所有输入的函数在恒定时间内运行。

请注意:x * 0xFFF可以进一步缩减为(x << 12) - x。此外,(!(color >> 12) * color )中的乘法也可以进一步优化,因为此处*的左操作数为01

修改

我添加了一些解释:上面的表达式与下面的表达式完全相同而不使用条件和关系运算符:

y =   ((y > 4095 ? 4095 : 0) | (y > 4095 ? 0 : y))
    & (y < 0 ? 0 : 4095);

<强> EDIT2:

正如@HotLicks在评论中正确指出的那样,!仍然是一个概念分支。然而,它也可以使用按位运算符计算。例如,!!a可以通过简单的方式完成:

b = (a >> 15 | a >> 14 | ... | a >> 1 | a) & 1

!a可以b ^ 1完成。而且我确信有一个很好的黑客可以更有效地做到这一点。

答案 4 :(得分:1)

您还可以使用Intel's SSE intrinsics轻松地对其进行矢量化。一个128位寄存器将保存8个short,并且有一些函数可以并行地对所有这些寄存器进行最小/最大/移位/屏蔽。在循环中,可以将min / max的常量预加载到寄存器中。 pshufb指令(SSSE3的一部分)甚至会为您打包字节。

答案 5 :(得分:0)

即使它没有直接回答原始问题,我也会留下答案,因为最后我认为你会发现它更有用。

我假设您的颜色来自以12位运行的相机或图像扫描仪,然后是一些未确定的处理步骤,可能会创建超出0到4095范围的值。如果是这种情况,则几乎可以肯定地以线性方式得出值。问题是显示器是伽马校正的,因此从12位到8位的转换将需要非线性伽马函数而不是简单的右移。这将比您的问题试图优化的夹紧操作慢得多。如果您不使用伽玛功能,图像将显得太暗。

short color     = /* some external source */;
unsigned char color8bit;
if (color <= 0)
    color8bit = 0;
else if (color >= 4095)
    color8bit = 255;
else
    color8bit = (unsigned char)(255.99 * pow(color / 4095.0, 1/2.2));

此时您可能会考虑查找表as suggested by Kirill Kobelev

答案 6 :(得分:0)

这有点类似于Tom Seddon的答案,但是使用一种稍微更清洁的方式来做上面的夹子。请注意,Seddon先生的回答和我的回答都避免了ouah的回答问题,即将签名值转移到右边是实现定义的行为,因此无法保证在所有的结构上都能正常工作。

#include <inttypes.h>
#include <iostream>

int16_t clamp(int16_t value)
{
    // clampBelow is 0xffff for -ve, 0x0000 for +ve
        int16_t const clampBelow = -static_cast<int16_t>(static_cast<uint16_t>(value) >> 15);

    // value is now clamped below at zero
    value &= ~clampBelow;
    // subtract 4095 so we can do the same trick again
    value -= 4095;
    // clampAbove is 0xffff for -ve, 0x0000 for +ve,
    // i.e. 0xffff for original value < 4095, 0x0000 for original >= 4096
        int16_t const clampAbove = -static_cast<int16_t>(static_cast<uint16_t>(value) >> 15);

    // adjusted value now clamped above at zero
    value &= clampAbove;
    // and restore to original value.
    value += 4095;
    return value;
}

void verify(int16_t value)
{
    int16_t const clamped = clamp(value);
    int16_t const check = (value < 0 ? 0 : value > 4095 ? 4095 : value);
    if (clamped != check)
    {
        std::cout << "Verification falure for value: " << value << ", clamped: " << clamped << ", check: " << check << std::endl;
    }
}

int main()
{
    for (int16_t i = 0x4000; i != 0x3fff; i++)
    {
        verify(i);
    }
    return 0;
}

这是一个完整的测试程序(好的,所以它不会测试0x3fff - 起诉我。;)),你可以从中提取clamp()例程以满足你的需要。

我也打破了每一行的一步&#34;&#34;为清楚起见。如果你的编译器有一个不错的优化器,你可以保持原样并依赖编译器产生最好的代码。如果您的编译器的优化器不是那么好,那么无论如何,它都可以减少行数,尽管代价是可读性稍差。

&#34;永远不要为了提高效率而牺牲清晰度&#34; - Bob Buckley,1980年英格兰考文垂U-Warwick的comp sci教授。

我得到的最佳建议。 ;)