嵌入式C - 开关/外壳和放大器之间的选择哈希表

时间:2016-08-31 07:18:27

标签: c

我在C中使用带有ARM芯片的自制电路板。我的代码必须快速执行,而且我还有空间限制。 现在,我必须编写一个"解析器"十六进制值。每个十六进制数必须与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个值吗?

感谢您的回答,如果您想要精确,请问!

安托

3 个答案:

答案 0 :(得分:3)

您可以将值放在256字节数组中以便快速访问:

static uint8_t const table[256] = { 2, 3, 1, 4, ... };
return table[i];

您只使用了256个值中的100个,因此存在浪费空间的“漏洞”,但它可以与switch竞争。

但是,由于您只需要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;

答案 1 :(得分:1)

正如您所指出的,有两种方法可以实现这一点。

为了确定哪种方法有利,您需要在两个方面分析每种方法:

  • 空间(内存消耗)
  • 时间(执行表现)

但是,您的问题的答案取决于平台。

为了分析内存消耗,您需要编译这两个函数,检查反汇编并确定每个函数使用的内存量。请记住,内存不仅用于变量,还用于代码。

为了分析执行性能,您基本上需要多次运行这两个函数,并测量每个函数的平均持续时间。请记住,运行时间还取决于底层硬件架构部署的缓存启发式,因此,例如,如果您在第二个函数之后立即测试第一个函数,然后再次测试第二个函数,则结果不一定是一致的在第一个功能之后立即。

以下是我平台上的内存消耗分析(x64上的VS2013编译器):

方法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;
    }
}

反汇编成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  

方法2:

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个字节的代码:

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字节的数据。

几点说明:

  1. 您在问题中提到过,在最坏的情况下,使用哈希表会比switch/case语句更快。现在,虽然这不是由C语言标准决定的,并且每个编译器可能以不同方式处理switch/case语句,但switch/case语句通常由单个分支组成,因此执行的操作量相同每一个案例。
  2. 请注意,我已将hash_table变量声明为static。结果,该数组驻留在数据部分而不是堆栈中,并作为可执行映像的一部分进行初始化(即硬编码),而不是每次调用函数时。同样,这不是由C语言标准决定的,但大多数C编译器以相同的方式处理它。我不能说它会改善内存消耗,因为它取决于分配给每个段(数据段和堆栈)的初始内存量。但它肯定会提高执行性能,因为哈希表将在可执行映像加载到内存时初始化。

答案 2 :(得分:1)

您可以使用查找表在O(1)空间中执行此操作:

#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;
}

输出:

2