如何有效地将两个16位字组合成一个32位字?

时间:2014-02-28 16:58:24

标签: c performance embedded bitwise-operators

我必须将两个16位字组合成一个32位字数百次,这需要很多计算能力。我想找到一种更有效的方法来做到这一点。

我有2个16位字,名为A和B.我想要一个名为C的32位字.A中的位应复制到C中的偶数位.B中的位应复制到奇数位C中的数字位。例如:A:0b0000000000000000 B:0b1111111111111111处理后的C应为0b10101010101010101010101010101010。

我目前的解决方案如下:

for (i = 0; i < 32; i+=2)
{
    C |=  (A & (1 << (i/2))) << (i/2);
    C |=  (B & (1 << (i/2))) << (i/2 + 1);
}

当我有几百个C需要处理时,这个解决方案需要花费太多时间。我正在寻找一个更好的!

补充:此程序在TriCore上运行。我别无选择,只能以这种方式处理数据,因为AB和C之间的这种关系是由协议定义的。

谢谢!

7 个答案:

答案 0 :(得分:6)

结果显示Tricore有一条BMERGE指令可以完全按照您的意愿执行 - 它需要两个16位值并对这些位进行交错。如果您使用的是基于gcc的工具链,则应该能够使用单个内联asm语句 - 例如:

asm("bmerge %0,%1,%2" : "=r"(C) : "r"(A), "r"(B))

还有一条BSPLIT指令反过来。

答案 1 :(得分:1)

而不是一个循环,转移成组。

可能进一步简化,但下面是它的要点。它平均更快(或最坏情况)?简介找出。

#include <inttypes.h>
#include <stdint.h>

uint64_t Merge(uint32_t a, uint32_t b) {
  uint64_t A,B;
  A = ((a & 0x00000000FFFF0000ull) << 16) | (a & 0x000000000000FFFFull);
  A = ((A & 0x0000FF000000FF00ull) <<  8) | (A & 0x000000FF000000FFull);
  A = ((A & 0xF0F0F0F0F0F0F0F0ull) <<  4) | (A & 0x0F0F0F0F0F0F0F0Full);
  A = ((A & 0xCCCCCCCCCCCCCCCCull) <<  2) | (A & 0x0333333333333333ull);
  A = ((A & 0xAAAAAAAAAAAAAAAAull) <<  1) | (A & 0x5555555555555555ull);

  B = ((b & 0x00000000FFFF0000ull) << 16) | (b & 0x000000000000FFFFull);
  B = ((B & 0x0000FF000000FF00ull) <<  8) | (B & 0x000000FF000000FFull);
  B = ((B & 0xF0F0F0F0F0F0F0F0ull) <<  4) | (B & 0x0F0F0F0F0F0F0F0Full);
  B = ((B & 0xCCCCCCCCCCCCCCCCull) <<  2) | (B & 0x0333333333333333ull);
  B = ((B & 0xAAAAAAAAAAAAAAAAull) <<  1) | (B & 0x5555555555555555ull);

  return A | (B << 1);
}

void MergeTest(uint32_t a, uint32_t b) {
  uint64_t C = Merge(a,b);
  printf("a:%08" PRIX32 " b:%08" PRIX32 " c:%016" PRIX64 "\n", a,b,C);
}

void MergeTests(void) {
  MergeTest(0x00000000L, 0xFFFFFFFFL);
  MergeTest(0xFFFFFFFFL, 0x00000000L);
  MergeTest(0x00000000L, 0x00000001L);;
  MergeTest(0x00000000L, 0x00000010L);;
}

a:00000000 b:FFFFFFFF c:AAAAAAAAAAAAAAAA  
a:FFFFFFFF b:00000000 c:5555555555555555  
a:00000000 b:00000001 c:0000000000000002  
a:00000000 b:00000010 c:0000000000000200  

答案 2 :(得分:0)

试试这个:

for (i = 0; i < 32; i+=2)
{
    int i2 = i >> 1 ;
    int andval = 1 << i2 ;
    C |=  (A & andval) << i2;
    C |=  (B & andval) << (i2 + 1);
}

但是你的编译器可能已经完成了这个优化。

答案 3 :(得分:0)

在MCU上工作的最可能类型的解决方案(可能是8位且可能没有桶形移位器)是沿着这些线进行手动编码的组装(取A,{{1 }和B / CL作为16位寄存器):

CH

(显然,如果MCU为8位,则每个LOOP: MOV CNT, 16 RRC A ; rotate A right through the carry RRC CH ; carry enters C at the top RRC CL ; continue roll through CL RRC B RRC CH RRC CL DJNZ CNT,LOOP 变为2。

这个解决方案将这些位“混合”在一起,每个周期只旋转一位,这是任何MCU都能做到的。您可以尝试用C语言编写它,但是您需要一个非常好的优化器来从RRC

之类的命令生成这一系列指令

编辑:使用32位CPU,您可以考虑Bit Twiddling Hacks中列出的所有选项。

答案 4 :(得分:0)

似乎要快40%,但它确实依赖于编译器优化; - )

for (i=1, j=2, msk=1; i<0x100000000; i<<=2, j<<=2, msk<<=1) {
    if (A & msk) C |= i;
    if (B & msk) C |= j;
}

答案 5 :(得分:0)

这个问题也叫'莫顿数编码';即,将2-D或3-D坐标展平为单个数字。

这个blog entry总结了三种典型的方法:天真的循环,魔术位(如chux的答案)和查找表。基于LUT的方法是明显的赢家。

基本上必须选择一次处理多少位。通常,最佳点是8-> 16位或4-> 8位LUT,例如这里。

0001 --> 0 0 0 0 0 0 0 1
0010 --> 0 0 0 0 0 1 0 0
0011 --> 0 0 0 0 0 1 0 1  etc.

使用此表扩展两个uint8_t变量可通过以下公式实现:

uint16_t ans =  LUT[a & 15]       + (LUT[b & 15] << 1) +
               (LUT[a >> 4] << 8) + (LUT[b << 4] << 9);

同样,如果在给定的位数下更有效,则必须分析4个不同的表,每个表左移一个常量,或者手动执行移位。

答案 6 :(得分:0)

以下使用两个步行 - 一个掩码,一个用于测试源数据位,另一个用于屏蔽到目标。在compileonline.com进行1000万次迭代测试得出以下结果:

  • 原始算法:1.14秒
  • 此算法:0.81秒

虽然不停止阅读 - 但仍有显着改进。

    uint32_t C ;
    uint16_t srcmask ;
    uint32_t dstmask ;

    for( C = 0, srcmask = 1u, dstmask = 1u; 
         srcmask != 0; 
         srcmask <<= 1 )
    {
        if( (A & srcmask) != 0 )
        {
            C |= dstmask ;
        }
        dstmask <<= 1 ;

        if( (B & srcmask) != 0 )
        {
            C |= dstmask ;
        }
        dstmask <<= 1 ;
    }

理论上,性能可能会因1位数而异,但在我的测试中,这种差异无法衡量,但不同的目标和编译器可能产生不同的结果。

每次迭代将循环展开到4个源位具有边际效益(0.77秒):

    for( C = 0, srcmask = 1u, dstmask = 1u; 
         srcmask != 0; 
         srcmask <<= 1 )
    {
        // Unroll 1
        if( (A & srcmask) )
        {
            C |= dstmask ;
        }
        dstmask <<= 1 ;

        if( (B & srcmask) )
        {
            C |= dstmask ;
        }
        dstmask <<= 1 ;

        // Unroll 2
        srcmask <<= 1 ;
        if( (A & srcmask) )
        {
            C |= dstmask ;
        }
        dstmask <<= 1 ;

        if( (B & srcmask) )
        {
            C |= dstmask ;
        }
        dstmask <<= 1 ;

        // Unroll 3
        srcmask <<= 1 ;
        if( (A & srcmask) )
        {
            C |= dstmask ;
        }
        dstmask <<= 1 ;

        if( (B & srcmask) )
        {
            C |= dstmask ;
        }
        dstmask <<= 1 ;

        // Unroll 4
        srcmask <<= 1 ;
        if( (A & srcmask) )
        {
            C |= dstmask ;
        }
        dstmask <<= 1 ;

        if( (B & srcmask) )
        {
            C |= dstmask ;
        }
        dstmask <<= 1 ;
    }

进一步展开会产生不利影响,但目标和编译器结果可能会有所不同。

然后我宣布Csrcmaskdstmaskregister,但没有任何不同之处:

register uint32_t C ;
register uint16_t srcmask ;
register uint32_t dstmask ;

我对结果感到震惊:

  • 原始算法:1.19秒
  • 此算法: 0.29

展开的效果在这里很重要 - 没有它,时间变为0.45秒,2x展开= 0.33秒。进一步展开的影响很小。将A和B声明为寄存器会略微降低性能 - 只有很多寄存器可供使用!再次YMMV。

因此,结论必须是您需要尝试一些技术来确定哪些方法最适合您的目标。在这里,更好的算法,循环展开和寄存器变量的组合产生了巨大的影响。使用不同编译器优化设置进行的实验也可能会产生影响,但是改进一个代码区域可能会损害其他代码区域,因此您可能不希望对所有代码应用相同的优化。