memset 在 32 位嵌入式平台上速度慢
memset slow on 32-bit embedded platform
我正在嵌入式设备(STM32,ARM-Cortex M4)上进行开发,预计 memset
和类似功能将针对速度进行优化。但是,我注意到行为比预期的要慢得多。我正在使用带有 -O3
优化标志的嵌入式 GNU ARM compiler/linker(arm-none-eabi-gcc
,等等)。
我查看了反汇编,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 位写入 and/or 循环展开来加速此代码,但代价是代码大小。实施者可能选择不优化速度以减少代码大小。
memset
header 和库来自:
C:\Program Files (x86)\GNU Tools Arm Embedded 2018-q2-update\arm-none-eabi\include\string.h
C:\Program Files (x86)\GNU Tools Arm Embedded 2018-q2-update\arm-none-eabi\include\c++.3.1\cmath
此问题与现有问题类似,但不同之处在于它针对的是嵌入式平台。
GNU ARM 嵌入式软件包中是否有现成可用的优化内存集?如果可以,我该如何访问它?
不确定 GNU Tools ARM Embedded 是否有优化的 memset,或者如何通过链接器选项访问它,但它可以在汇编中手动优化。定义这个之后,链接器使用了这个版本而没有抱怨重新定义的函数,这对我来说似乎很奇怪。整体速度提高了大约 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 个)
- 优化的 memset:22582 ticks(~0.7 每字节)
- 最大理论速度:16384 ticks
最大速度为每 4 个字节 2 个刻度(str
指令的速度)。
原来的memset需要16个字节的代码。新的占用98个字节。
Link 没有 -specs=nano.specs
。这将使用 C 库的版本,其中包括 memset
,它针对速度而不是大小进行了优化。这将引入许多其他函数的更大版本(通常怀疑:printf
和 malloc
),这些函数可以再次通过附加链接器选项进行优化。检查反汇编和链接器映射文件会有所帮助。
我正在嵌入式设备(STM32,ARM-Cortex M4)上进行开发,预计 memset
和类似功能将针对速度进行优化。但是,我注意到行为比预期的要慢得多。我正在使用带有 -O3
优化标志的嵌入式 GNU ARM compiler/linker(arm-none-eabi-gcc
,等等)。
我查看了反汇编,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 位写入 and/or 循环展开来加速此代码,但代价是代码大小。实施者可能选择不优化速度以减少代码大小。
memset
header 和库来自:
C:\Program Files (x86)\GNU Tools Arm Embedded 2018-q2-update\arm-none-eabi\include\string.h
C:\Program Files (x86)\GNU Tools Arm Embedded 2018-q2-update\arm-none-eabi\include\c++.3.1\cmath
此问题与现有问题类似,但不同之处在于它针对的是嵌入式平台。
GNU ARM 嵌入式软件包中是否有现成可用的优化内存集?如果可以,我该如何访问它?
不确定 GNU Tools ARM Embedded 是否有优化的 memset,或者如何通过链接器选项访问它,但它可以在汇编中手动优化。定义这个之后,链接器使用了这个版本而没有抱怨重新定义的函数,这对我来说似乎很奇怪。整体速度提高了大约 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 个)
- 优化的 memset:22582 ticks(~0.7 每字节)
- 最大理论速度:16384 ticks
最大速度为每 4 个字节 2 个刻度(str
指令的速度)。
原来的memset需要16个字节的代码。新的占用98个字节。
Link 没有 -specs=nano.specs
。这将使用 C 库的版本,其中包括 memset
,它针对速度而不是大小进行了优化。这将引入许多其他函数的更大版本(通常怀疑:printf
和 malloc
),这些函数可以再次通过附加链接器选项进行优化。检查反汇编和链接器映射文件会有所帮助。