我必须将两个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之间的这种关系是由协议定义的。
谢谢!
答案 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万次迭代测试得出以下结果:
虽然不停止阅读 - 但仍有显着改进。
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 ;
}
进一步展开会产生不利影响,但目标和编译器结果可能会有所不同。
然后我宣布C
,srcmask
和dstmask
为register
,但没有任何不同之处:
register uint32_t C ;
register uint16_t srcmask ;
register uint32_t dstmask ;
我对结果感到震惊:
展开的效果在这里很重要 - 没有它,时间变为0.45秒,2x展开= 0.33秒。进一步展开的影响很小。将A和B声明为寄存器会略微降低性能 - 只有很多寄存器可供使用!再次YMMV。
因此,结论必须是您需要尝试一些技术来确定哪些方法最适合您的目标。在这里,更好的算法,循环展开和寄存器变量的组合产生了巨大的影响。使用不同编译器优化设置进行的实验也可能会产生影响,但是改进一个代码区域可能会损害其他代码区域,因此您可能不希望对所有代码应用相同的优化。