memset在32位嵌入式平台上运行缓慢

时间:2019-07-26 14:58:58

标签: c++ embedded memset

我正在开发嵌入式设备(STM32,ARM-Cortex M4),并且期望memset和类似功能可以进行速度优化。但是,我注意到行为比预期慢得多。我正在使用带有arm-none-eabi-gcc优化标志的GNU ARM嵌入式编译器/链接器(-O3等)。

我查看了反汇编,memset函数一次写入一个字节,并在每次迭代时重新检查边界。

0x802e2c4 <memset>: add r2, r0
0x802e2c6 <memset+2>:   mov r3, r0
0x802e2c8 <memset+4>:   cmp r3, r2
0x802e2ca <memset+6>:   bne.n   0x802e2ce <memset+10>
0x802e2cc <memset+8>:   bx  lr
0x802e2ce <memset+10>:  strb.w  r1, [r3], #1
0x802e2d2 <memset+14>:  b.n 0x802e2c8

自然地,可以通过使用32位写入和/或循环展开来加速此代码,但以代码大小为代价。实施者有可能选择不对速度进行优化,以降低代码大小。

memset标头和库包含在以下位置:

C:\Program Files (x86)\GNU Tools Arm Embedded\7 2018-q2-update\arm-none-eabi\include\string.h
C:\Program Files (x86)\GNU Tools Arm Embedded\7 2018-q2-update\arm-none-eabi\include\c++\7.3.1\cmath

此问题与现有问题相似,但不同之处在于它针对嵌入式平台。

在GNU ARM嵌入式软件包中是否容易找到优化的内存集?如果可以,我该如何访问它?

2 个答案:

答案 0 :(得分:1)

链接 -specs=nano.specs。这将使用C库的版本,其中包括memset,该版本针对速度而不是大小进行了优化。这将引入许多其他功能(通常为printfmalloc)的较大版本,可以通过其他链接器选项再次对其进行优化。检查反汇编和链接器映射文件会有所帮助。

答案 1 :(得分:0)

不确定GNU Tools ARM Embedded是否具有优化的内存集,或者如何通过链接器选项访问它,但是可以在组装中手动对其进行优化。在定义了这个之后,链接器使用了这个版本而没有抱怨重新定义的函数,这对我来说似乎很奇怪。总体速度提高了大约9倍(也就是说,此版本所需的时间大约是原始字节方法的11%)。

// optimized version of memset
// we split up the region into several segments
//
// base_ptr
// * store single bytes
// mid1
// * store words, 4 at a time
// mid2
// * store words, 1 at a time
// mid3
// * store single bytes
// end
//
// For large buffers, most of the time is spent between mid1 and mid2 which is
// highly optimized.
void * memset(void * base_ptr, int x, size_t length) {
  const uint32_t int_size = sizeof(uint32_t);
  static_assert(sizeof(uint32_t) == 4, "only supports 32 bit size");
  // find first word-aligned address
  uint32_t ptr = (uint32_t) base_ptr;
  // get end of memory to set
  uint32_t end = ptr + length;
  // get location of first word-aligned address at/after the start, but not
  // after the end
  uint32_t mid1 = (ptr + int_size - 1) / int_size * int_size;
  if (mid1 > end) {
    mid1 = end;
  }
  // get location of last word-aligned address at/before the end
  uint32_t mid3 = end / int_size * int_size;
  // get end location of optimized section
  uint32_t mid2 = mid1 + (mid3 - mid1) / (4 * int_size) * (4 * int_size);
  // create a word-sized integer
  uint32_t value = 0;
  for (uint16_t i = 0; i < int_size; ++i) {
    value <<= 8;
    value |= (uint8_t) x;
  }
  __ASM volatile (
  // store bytes
  "b Compare1%=\n"
  "Store1%=:\n"
  "strb %[value], [%[ptr]], #1\n"
  "Compare1%=:\n"
  "cmp %[ptr], %[mid1]\n"
  "bcc Store1%=\n"
  // store words optimized
  "b Compare2%=\n"
  "Store2%=:\n"
  "str %[value], [%[ptr]], #4\n"
  "str %[value], [%[ptr]], #4\n"
  "str %[value], [%[ptr]], #4\n"
  "str %[value], [%[ptr]], #4\n"
  "Compare2%=:\n"
  "cmp %[ptr], %[mid2]\n"
  "bcc Store2%=\n"
  // store words
  "b Compare3%=\n"
  "Store3%=:\n"
  "str %[value], [%[ptr]], #4\n"
  "Compare3%=:\n"
  "cmp %[ptr], %[mid3]\n"
  "bcc Store3%=\n"
  // store bytes
  "b Compare4%=\n"
  "Store4%=:\n"
  "strb %[value], [%[ptr]], #1\n"
  "Compare4%=:\n"
  "cmp %[ptr], %[end]\n"
  "bcc Store4%=\n"
  : // no outputs
  : [value] "r"(value),
  [ptr] "r"(ptr),
  [mid1] "r"(mid1),
  [mid2] "r"(mid2),
  [mid3] "r"(mid3),
  [end] "r"(end)
  );
  return base_ptr;
}

在32kB数据上运行时的速度差异:

  • 原始记忆集:197045个刻度(每字节约6个)
  • 优化的内存集:22582个滴答声(每字节〜0.7)
  • 最高理论速度:16384次滴答声

每4个字节最大速度为2个滴答(str指令的速度)。

原始内存集需要16个字节的代码。新的占用98个字节。