嵌入式C-在开关/机箱和;哈希表
我用C语言使用一块自制的主板和一块ARM芯片。我的代码必须执行得很快,而且我还有空间限制。嵌入式C-在开关/机箱和;哈希表,c,C,我用C语言使用一块自制的主板和一块ARM芯片。我的代码必须执行得很快,而且我还有空间限制。 现在,我必须编写一个十六进制值的“解析器”。每个十六进制数必须与dec数(1;2;3或4)相关联。现在,我认为它在未来不会有太大变化,我有100个十六进制值要解析。十六进制值是“随机”的,没有特定的模式 我从一个开关/箱子开始,就像这样: switch (i) { case 0xF3: case 0xF7: case 0x02: return 1; break; case 0x20
现在,我必须编写一个十六进制值的“解析器”。每个十六进制数必须与dec数(1;2;3或4)相关联。现在,我认为它在未来不会有太大变化,我有100个十六进制值要解析。十六进制值是“随机”的,没有特定的模式 我从一个开关/箱子开始,就像这样:
switch (i)
{
case 0xF3:
case 0xF7:
case 0x02:
return 1;
break;
case 0x20:
case 0x40:
case 0xE0:
case 0xC0:
return 2;
break;
case 0x21:
case 0x41:
case 0x81:
case 0x61:
case 0xA1:
return 3;
break;
case 0xBB:
case 0xCC:
case 0x63:
return 4;
break;
default:
return 0;
break;
}
但我考虑的是一个哈希表。当然,在最坏的情况下它会更快,但是它会占用更多的空间,并且哈希表真的值得100个值吗
谢谢你的回答,如果你想要精确,尽管问吧
Antoine您可以将值放入256字节数组中,以便快速访问:
static uint8_t const table[256] = { 2, 3, 1, 4, ... };
return table[i];
您只使用256个值中的100个,因此存在浪费空间的“洞”,但它可以与开关
竞争
但由于您只需要4个值,因此可以在2位中重新生成这些值。您可以将四个值打包为一个字节。只需使用值0-3而不是1-4:
#define PACK4(a, b, c, d) \
(((a)-1 << 0) | ((b)-1 << 2) | ((c)-1 << 4) | ((d)-1 << 6))
static uint8_t const table[64] = { PACK4(2, 3, 1, 4), PACK4(... };
int byteOffset = i / 4;
int bitOffset = i % 4 * 2;
return (table[byteOffset] >> bitOffset & 0x03) + 1;
定义PACK4(a、b、c、d)\
((a)-1正如您所指出的,有两种方法可以实现这一点 为了决定哪种方法是有利的,您需要从两个方面分析每种方法:
- 空间(内存消耗)
- 时间(执行性能)
以下是我的平台(VS2013编译器超过x64)上的内存消耗分析: 方法1:
uint8_t func1(uint8_t i)
{
switch (i)
{
case 0x02:
case 0xF3:
case 0xF7:
return 1;
case 0x20:
case 0x40:
case 0xC0:
case 0xE0:
return 2;
case 0x21:
case 0x41:
case 0x61:
case 0x81:
case 0xA1:
return 3;
case 0x63:
case 0xBB:
case 0xCC:
return 4;
default:
return 0;
}
}
uint8_t func2(uint8_t i)
{
static const uint8_t hash_table[] =
{
/* 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, */
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 - 0x0F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1F */
2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3F */
2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5F */
0, 3, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x6F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7F */
0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9F */
0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xA0 - 0xAF */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, /* 0xB0 - 0xBF */
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, /* 0xC0 - 0xCF */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xD0 - 0xDF */
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xE0 - 0xEF */
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xF0 - 0xFF */
};
return hash_table[i];
}
分解为114字节的代码:
00007FF778131050 mov byte ptr [rsp+8],cl
00007FF778131054 push rdi
00007FF778131055 sub rsp,10h
00007FF778131059 mov rdi,rsp
00007FF77813105C mov ecx,4
00007FF778131061 mov eax,0CCCCCCCCh
00007FF778131066 rep stos dword ptr [rdi]
00007FF778131068 movzx ecx,byte ptr [i]
00007FF77813106D movzx eax,byte ptr [i]
00007FF778131072 mov dword ptr [rsp],eax
00007FF778131075 mov eax,dword ptr [rsp]
00007FF778131078 sub eax,2
00007FF77813107B mov dword ptr [rsp],eax
00007FF77813107E cmp dword ptr [rsp],0F5h
00007FF778131085 ja $LN5+10h (07FF7781310B6h)
00007FF778131087 movsxd rax,dword ptr [rsp]
00007FF77813108B lea rcx,[__ImageBase (07FF778130000h)]
00007FF778131092 movzx eax,byte ptr [rcx+rax+10D4h]
00007FF77813109A mov eax,dword ptr [rcx+rax*4+10C0h]
00007FF7781310A1 add rax,rcx
00007FF7781310A4 jmp rax
00007FF7781310A6 mov al,1
00007FF7781310A8 jmp $LN5+12h (07FF7781310B8h)
00007FF7781310AA mov al,2
00007FF7781310AC jmp $LN5+12h (07FF7781310B8h)
00007FF7781310AE mov al,3
00007FF7781310B0 jmp $LN5+12h (07FF7781310B8h)
00007FF7781310B2 mov al,4
00007FF7781310B4 jmp $LN5+12h (07FF7781310B8h)
00007FF7781310B6 xor al,al
00007FF7781310B8 add rsp,10h
00007FF7781310BC pop rdi
00007FF7781310BD ret
00007FF7781310BE xchg ax,ax
00007FF7781310C0 cmps byte ptr [rsi],byte ptr [rdi]
00007FF7781310C1 adc byte ptr [rax],al
00007FF778131030 mov byte ptr [rsp+8],cl
00007FF778131034 push rdi
00007FF778131035 movzx eax,byte ptr [i]
00007FF77813103A lea rcx,[hash_table (07FF7781368C0h)]
00007FF778131041 movzx eax,byte ptr [rcx+rax]
00007FF778131045 pop rdi
00007FF778131046 ret
方法2:
uint8_t func1(uint8_t i)
{
switch (i)
{
case 0x02:
case 0xF3:
case 0xF7:
return 1;
case 0x20:
case 0x40:
case 0xC0:
case 0xE0:
return 2;
case 0x21:
case 0x41:
case 0x61:
case 0x81:
case 0xA1:
return 3;
case 0x63:
case 0xBB:
case 0xCC:
return 4;
default:
return 0;
}
}
uint8_t func2(uint8_t i)
{
static const uint8_t hash_table[] =
{
/* 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, */
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 - 0x0F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1F */
2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3F */
2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5F */
0, 3, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x6F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7F */
0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9F */
0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xA0 - 0xAF */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, /* 0xB0 - 0xBF */
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, /* 0xC0 - 0xCF */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xD0 - 0xDF */
2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xE0 - 0xEF */
0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xF0 - 0xFF */
};
return hash_table[i];
}
反汇编成23字节的代码:
00007FF778131050 mov byte ptr [rsp+8],cl
00007FF778131054 push rdi
00007FF778131055 sub rsp,10h
00007FF778131059 mov rdi,rsp
00007FF77813105C mov ecx,4
00007FF778131061 mov eax,0CCCCCCCCh
00007FF778131066 rep stos dword ptr [rdi]
00007FF778131068 movzx ecx,byte ptr [i]
00007FF77813106D movzx eax,byte ptr [i]
00007FF778131072 mov dword ptr [rsp],eax
00007FF778131075 mov eax,dword ptr [rsp]
00007FF778131078 sub eax,2
00007FF77813107B mov dword ptr [rsp],eax
00007FF77813107E cmp dword ptr [rsp],0F5h
00007FF778131085 ja $LN5+10h (07FF7781310B6h)
00007FF778131087 movsxd rax,dword ptr [rsp]
00007FF77813108B lea rcx,[__ImageBase (07FF778130000h)]
00007FF778131092 movzx eax,byte ptr [rcx+rax+10D4h]
00007FF77813109A mov eax,dword ptr [rcx+rax*4+10C0h]
00007FF7781310A1 add rax,rcx
00007FF7781310A4 jmp rax
00007FF7781310A6 mov al,1
00007FF7781310A8 jmp $LN5+12h (07FF7781310B8h)
00007FF7781310AA mov al,2
00007FF7781310AC jmp $LN5+12h (07FF7781310B8h)
00007FF7781310AE mov al,3
00007FF7781310B0 jmp $LN5+12h (07FF7781310B8h)
00007FF7781310B2 mov al,4
00007FF7781310B4 jmp $LN5+12h (07FF7781310B8h)
00007FF7781310B6 xor al,al
00007FF7781310B8 add rsp,10h
00007FF7781310BC pop rdi
00007FF7781310BD ret
00007FF7781310BE xchg ax,ax
00007FF7781310C0 cmps byte ptr [rsi],byte ptr [rdi]
00007FF7781310C1 adc byte ptr [rax],al
00007FF778131030 mov byte ptr [rsp+8],cl
00007FF778131034 push rdi
00007FF778131035 movzx eax,byte ptr [i]
00007FF77813103A lea rcx,[hash_table (07FF7781368C0h)]
00007FF778131041 movzx eax,byte ptr [rcx+rax]
00007FF778131045 pop rdi
00007FF778131046 ret
当然,还有256字节的数据
几点注意:
switch/case
语句更快。现在,尽管这不是由C语言标准规定的,并且每个编译器可能会以不同的方式处理switch/case
语句,但是switch/case
语句通常由单个b语句组成因此,在每种情况下执行的操作量是相同的hash_table
变量声明为static
。因此,此数组驻留在数据部分而不是堆栈中,并作为可执行映像的一部分初始化(即硬编码),而不是每次调用函数时。同样,这不是C语言标准规定的,但大多数C编译器都以相同的方式处理。我不能说它会提高内存消耗,因为它取决于分配给每个段(数据段和堆栈)的初始内存量。但它肯定会提高执行性能,因为哈希表将在可执行映像加载到内存时初始化#include <stdio.h>
static const unsigned char keymap[] = {
0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
2,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,3,0,4,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,0,
2,0,0,0,0,0,0,0,0,0,0,0,4,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0
};
static int find(int id)
{
if ((id >= 0) && (id < 256)) {
return keymap[id];
}
return 0;
}
int main(void)
{
int id = 0x20;
printf("%d\n", find(id));
return 0;
}
为什么您认为在最坏的情况下它会更快?
switch/case
是一个分支,每种情况下执行的操作量都是相同的。此外,哈希表在数据部分(或堆栈中)的位置更大,但是开关/case
在代码部分占据更多的位置,因此最终它们可能消耗大致相同的内存量。在您的示例代码中,所有十六进制值都有两位数字。这是给定的约束条件吗?换句话说,要识别的数字保证在0x00到0xFF的范围内?我认为这不会造成错误Eng.一些编译器,至少是GCC,在适用时将长开关/CASE转换为哈希表。所有值都低于0xFF吗?你可以考虑只填充一个静态查找表,也可以考虑使用一个跳转表来实现开关/情况,它可能已经类似于哈希表。,可能没有办法对可能的解决方案进行基准测试,以找到最快(或最小)的解决方案。“没有特定的模式。”你确定吗?有时会有……比如,如果设置了位0,而不是位3和位4,那么它就是1(示例)在C99中,您可以使用指定的初始值设定项:static uint8_t const table[256]={[0x02]=1,[0x63]=4,…};
。对于打包,位字段将删除打包/解包的所有代码。有5个不同的返回值(0、1、2、3、4);您不能将其打包为2位。@ElderBug True。我选择不使用位字段,因为解包需要条件来选择成员。@rud如果确实需要0,那么当然需要3位。然后您将每个字节只打包2个值。或者,使用另一个uint8_t数组[32]
,其中一位表示主表中是否存在值。Ops,答案相同,但您的答案更详细:),只有一个问题:为什么要使用uint8\u t
?无符号字符
保证为1byte@AlterMann:OP的值似乎适合8位,因此我使用了uint8\u t
。如果char\u位
(在文件limits.hchar
理论上可以是16位