在字对齐处理器上使用未对齐数据的最快方法?

dro*_*und 16 c arm memory-alignment micro-optimization cortex-m

我正在ARM Cortex M0上做一个项目,它不支持未对齐(4字节)访问,我正在尝试优化未对齐数据的操作速度.

我将蓝牙低功耗访问地址(48位)作为6字节数组存储在一些作为数据包缓冲区的打包结构中.由于打包,BLE地址不一定从字对齐的地址开始,并且在优化我对这些地址的访问功能时遇到了一些复杂问题.

第一种也是最明显的方法是对数组中每个字节单独运行的for循环.例如,检查两个地址是否相同可以这样做:

uint8_t ble_adv_addr_is_equal(uint8_t* addr1, uint8_t* addr2)
{
  for (uint32_t i = 0; i < 6; ++i)
  {
    if (addr1[i] != addr2[i])
      return 0;
  }
  return 1;
}
Run Code Online (Sandbox Code Playgroud)

我在我的项目中做了很多比较,我想看看是否可以从这个功能中挤出更多的速度.我意识到对于对齐的地址,我可以将它们转换为uint64_t,并与应用的48位掩码进行比较,即

((uint64_t)&addr1[0] & 0xFFFFFFFFFFFF) == ((uint64_t)&addr2[0] & 0xFFFFFFFFFFFF)
Run Code Online (Sandbox Code Playgroud)

可以对写入进行类似的操作,并且它适用于对齐版本.但是,由于我的地址并不总是字对齐(甚至是半字),所以我必须做一些额外的技巧来完成这项工作.

首先,我想出了编译器宏的未经优化的噩梦:

#define ADDR_ALIGNED(_addr) (uint64_t)(((*((uint64_t*)(((uint32_t)_addr) & ~0x03)) >> (8*(((uint32_t)_addr) & 0x03))) & 0x000000FFFFFFFF)\
                                    | (((*((uint64_t*)(((uint32_t)_addr+4) & ~0x03))) << (32-8*(((uint32_t)_addr) & 0x03)))) & 0x00FFFF00000000)
Run Code Online (Sandbox Code Playgroud)

它基本上将整个地址移位到前一个字对齐的存储器位置,而不管偏移量.例如:

    0       1       2       3
|-------|-------|-------|-------|
|.......|.......|.......|<ADDR0>|
|<ADDR1>|<ADDR2>|<ADDR3>|<ADDR4>|
|<ADDR5>|.......|.......|.......|
Run Code Online (Sandbox Code Playgroud)

    0       1       2       3
|-------|-------|-------|-------|
|<ADDR0>|<ADDR1>|<ADDR2>|<ADDR3>|
|<ADDR4>|<ADDR5>|.......|.......|
|.......|.......|.......|.......|
Run Code Online (Sandbox Code Playgroud)

我可以安全地对两个地址进行64位比较,无论它们的实际对齐如何:

ADDR_ALIGNED(addr1) == ADDR_ALIGNED(addr2)
Run Code Online (Sandbox Code Playgroud)

整齐!但是当使用ARM-MDK编译时,此操作需要71行汇编,而在简单的for循环中进行比较时需要53行(我只是忽略在此处分支指令中花费的额外时间),并且~30展开时.此外,它不适用于写入,因为对齐仅发生在寄存器中,而不是在内存中.再次取消对齐将需要类似的操作,并且整个方法通常看起来很糟糕.

对于像这样的情况,一个展开的for循环单独处理每个字节真的是最快的解决方案吗?有没有人有类似场景的经验,并想在这里分享他们的一些魔法?

Mic*_*gan 5

UPDATE

好的,因为你的数据没有任何对齐,你需要逐字节地读取所有数据到正确对齐的缓冲区然后进行真正快速的64位比较,或者,如果你之后不会使用数据比较,只是将数据读入字节并进行6比较,在这种情况下,调用memcmp()可能是更好的选择.


对于至少16位对齐:


 u16 *src1 = (u16 *)addr1; 
 u16 *src2 = (u16 *)addr2;

 for (int i = 0; i < 3; ++i)
 {
    if (src1[i] != src2[i])
      return 0;
 }

 return 1;
Run Code Online (Sandbox Code Playgroud)

将是字节比较的两倍,并且只要您的数据至少为2字节对齐,就可以合理地做到最好.我还希望编译器完全删除for循环,而只是使用有条件执行的if语句.

尝试32位对齐读取将不会更快,除非您可以保证source1和2类似地对齐(add1和0x03)==(addr2和0x03).如果是这种情况,那么您可以读取32位值,然后读取16位(反之亦然,取决于开始对齐)并删除1个比较.

由于16位是您的共享基础,您可以从那里开始,编译器应该生成漂亮的ldrh类型操作码.