从UINT16到UINT8提取和组合位的更快方法

问题描述

我正在寻找所需的特殊提取和合并操作的更快方法,如下所述:

+-------+-------+-------+-------+-------+-------+-------+-------+
| BIT 7 | BIT 6 | BIT 5 | BIT 4 | BIT 3 | BIT 2 | BIT 1 | BIT 0 |
+-------+-------+-------+-------+-------+-------+-------+-------+
|   D1  |  D0   |  C1   |  C0   |  B1   |  B0   |  A1   |   A0  |
+-------+-------+-------+-------+-------+-------+-------+-------+

A = A0 OR A1
B = B0 OR B1
C = C0 OR C1
D = D0 OR D1

+-------+-------+-------+-------+-------+-------+-------+-------+
| BIT 7 | BIT 6 | BIT 5 | BIT 4 | BIT 3 | BIT 2 | BIT 1 | BIT 0 |
+-------+-------+-------+-------+-------+-------+-------+-------+
|       |       |       |       |   D   |   C   |   B   |   A   |
+-------+-------+-------+-------+-------+-------+-------+-------+

为简单起见,上面仅是一个8位示例,对于16位值也是如此。应该在dsPIC33F微控制器上尽快实现它。

使用C语言的简单方法是:

PairFlags |= (ChannelFlags & 0x0003) ? 0x0001 : 0;
PairFlags |= (ChannelFlags & 0x000C) ? 0x0002 : 0;
PairFlags |= (ChannelFlags & 0x0030) ? 0x0004 : 0;
PairFlags |= (ChannelFlags & 0x00C0) ? 0x0008 : 0;
PairFlags |= (ChannelFlags & 0x0300) ? 0x0010 : 0;
PairFlags |= (ChannelFlags & 0x0C00) ? 0x0020 : 0;
PairFlags |= (ChannelFlags & 0x3000) ? 0x0040 : 0;
PairFlags |= (ChannelFlags & 0xC000) ? 0x0080 : 0;

这将产生大约。在我的情况下,40条指令(带有O3)对应于1µs。

指令周期数量应尽可能减少。在C或内联汇编中,有没有更快的方法

解决方法

以下应将16位值减小为8位(输出的每一位通过对一对输入的位进行“或”运算):

// Set even bits to bits in pair ORed together,and odd bits to 0...
PairFlags = (ChannelFlags | (ChannelFlags >> 1)) & 0x5555; // '0h0g0f0e0d0c0b0a'
// Compress the '00' or '01' bit pairs down to single '0' or '1' bits...
PairFlags = (PairFlags ^ (PairFlags >> 1)) & 0x3333; // '00hg00fe00dc00ba'
PairFlags = (PairFlags ^ (PairFlags >> 2)) & 0x0F0F; // '0000hgfe0000dcba'
PairFlags = (PairFlags ^ (PairFlags >> 4)) & 0x00FF; // '00000000hgfedcba'

注意:上面的^可以用|替换,以获得相同的结果。

,

假设我一切正常(未经测试),这似乎至少在x86(-O3)的gcc和clang上生成了良好的无分支代码:

uint8_t convert (uint8_t ChannelFlags)
{
  return ( ((ChannelFlags & A1A0)!=0) << A_POS ) |
         ( ((ChannelFlags & B1B0)!=0) << B_POS ) |
         ( ((ChannelFlags & C1C0)!=0) << C_POS ) |
         ( ((ChannelFlags & D1D0)!=0) << D_POS ) ;  
}

这将屏蔽每个单独的位集,然后检查零以在临时1中以0int结尾。在最终将所有内容按位进行“或”运算之前,此值将在结果中移位。完整代码:

#include <stdint.h>

#define A1A0  (3u << 0)
#define B1B0  (3u << 2)
#define C1C0  (3u << 4)
#define D1D0  (3u << 6)

#define A_POS 0
#define B_POS 1
#define C_POS 2
#define D_POS 3

uint8_t convert (uint8_t ChannelFlags)
{
  return ( ((ChannelFlags & A1A0)!=0) << A_POS ) |
         ( ((ChannelFlags & B1B0)!=0) << B_POS ) |
         ( ((ChannelFlags & C1C0)!=0) << C_POS ) |
         ( ((ChannelFlags & D1D0)!=0) << D_POS ) ;  
}

clang反汇编x86给出了18条免费分支指令:

convert:                                # @convert
        test    dil,3
        setne   al
        test    dil,12
        setne   cl
        add     cl,cl
        or      cl,al
        test    dil,48
        setne   al
        shl     al,2
        or      al,cl
        mov     ecx,edi
        shr     cl,7
        shr     dil,6
        and     dil,1
        or      dil,cl
        shl     dil,3
        or      al,dil
        ret
,

不确定是否效率更高,而不是使用三进制if,为什么不只使用按位运算呢?并使用bitshift运算符对其进行补偿

PairFlags = ((ChannelFlags & (0b1 << 0)) | (ChannelFlags & (0b10 << 0))) << 0;
PairFlags = ((ChannelFlags & (0b1 << 2)) | (ChannelFlags & (0b10 << 2))) << 1;
PairFlags = ((ChannelFlags & (0b1 << 4)) | (ChannelFlags & (0b10 << 4))) << 2;
//...
,

这是个主意。 在这里观察一件事:

A = A0 OR A1
B = B0 OR B1
C = C0 OR C1
D = D0 OR D1

您有4个操作。您可以通过1条指令执行所有操作:

PairFlags = (PairFlags | (PairFlags >> 1))

现在您的位是这样对齐的:

[D1][D1 or D0][D0 or C1][C1 or C0][C0 or B1][B1 or B0][B0 or A1][A1 or A0]

所以您只需要提取0、2、4、6位即可得到结果。

位0。已经可以了。

第1位应设置为第2位。

第2位应设置为第4位。

第3位应设置为第6位。

最终代码如下:

PairFlags = (PairFlags | (PairFlags >> 1))
PairFlags = (PairFlags&1) | ((PairFlags&4)>>1) | ((PairFlags&16)>>2) | ((PairFlags&64)>>3)