X86、ARM与Xtensa上微优化字符串比较
这篇文章讨论在X86、ARM与Xtensa上,当字符串对齐与未对齐时,对于逐字比较、整体比较与调用memcmp()函数比较字符串时GCC生成的机器码的不同。文章将会分析不同情况下GCC生成的机器码的体积、操作量和内存占用以探讨性能。
C语言, 汇编, GCC, 优化, X86, ARM, Xtensa, memcmp, 栈
--by Captdam @ Sep 27, 2026Index
我在嵌入式系统上开发一个轻量级网站服务器时想到了这个问题:怎样更快处理文字?
比如说,以下代码展示了服务器收到TCP包后检测HTTP请求方法放入例子:
if (http_request_header[0:3] == "GET ") {
get_request();
} else if (http_request_header[0:3] == "POST") {
post_request();
} else {
bad_request();
}
其中:
-
http_request_header- 目标字符串,也即是要被比较的字符串。 -
"GET "- 参考字符串,也就是用于对比的字符串。
我同意很多人都觉得微优化有点矫枉过正,应该把更多精力放在影响更大的项目上。因为我的背景是电子与计算机工程,我对CPU的工作方式和编译器的生成结果更感兴趣。因此,我决定做一些微优化的实验,并把我的发现放在这。
在这篇文章中,我将测试在以下三种平台上比较字符串:
-
X86 - 高性能的电脑CPU。
-
ARM-M0+ - 树莓派RP2040所使用的CPU,一款ARM架构的设计给嵌入式系统使用的CPU。
-
Xtensa - ESP32所使用的CPU,一款为使用WiFi通讯的嵌入式系统设计的芯片。
我将使用GCC编译C语言代码,优化等级为-O2。
gcc -v
Target: x86_64-linux-gnu
gcc version 8.3.0 (Debian 8.3.0-6)
arm-none-eabi-gcc -v
Target: arm-none-eabi
gcc version 7.3.1 20180622 (release) [ARM/embedded-7-branch revision 261907] (15:7-2018-q2-6)
xtensa-esp32-elf-gcc -v
Target: xtensa-esp-elf
gcc version 13.2.0 (crosstool-NG esp-13.2.0_20240530)
比较对齐的字符串
逐字比较对齐的字符串
我们都知道,一个字符为8比特宽,能被表示为一个字节(char)。4个字符就是32比特宽,并能被4个字节表示,或者说是一个32字节的词(word)。如果我们逐字比较目标字符串,我就将需要比较4次。参考以下C语言代码:
#include <stdint.h>
volatile __attribute__((aligned(4096))) char target[4096] = {0, 1, 2, 3};
int compare_char_aligned() {
return (target[0] == '0' && target[1] == '1' && target[2] == '2' && target[3] == '3') ? 0 : 1; // Return 0 if equal
}
我们为X86、ARM和Xtensa编译这段代码,然后反汇编:
gcc -c *.c -O2 -o x86.out
objdump -DxS x86.out > x86.txt
arm-none-eabi-gcc -c *.c -mcpu=cortex-m0plus -O2 -o arm.out
arm-none-eabi-objdump --disassembler-options=force-thumb -DxS arm.out > arm.txt
xtensa-esp-elf\bin\xtensa-esp32-elf-gcc -c main.c -O2 -o esp32.out
xtensa-esp-elf\bin\xtensa-esp32-elf-objdump -DxS esp32.out > esp32.txt
X86逐字比较对齐的字符串
以下为原始机器码:
0000000000000000 <compare_char_aligned>:
0: 0f b6 15 00 00 00 00 movzbl 0x0(%rip),%edx # 7 <compare_char_aligned+0x7>
3: R_X86_64_PC32 target-0x4
7: b8 01 00 00 00 mov $0x1,%eax
c: 80 fa 30 cmp $0x30,%dl
f: 74 07 je 18 <compare_char_aligned+0x18>
11: c3 retq
12: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
18: 0f b6 15 00 00 00 00 movzbl 0x0(%rip),%edx # 1f <compare_char_aligned+0x1f>
1b: R_X86_64_PC32 target-0x3
1f: 80 fa 31 cmp $0x31,%dl
22: 75 ed jne 11 <compare_char_aligned+0x11>
24: 0f b6 15 00 00 00 00 movzbl 0x0(%rip),%edx # 2b <compare_char_aligned+0x2b>
27: R_X86_64_PC32 target-0x2
2b: 80 fa 32 cmp $0x32,%dl
2e: 75 e1 jne 11 <compare_char_aligned+0x11>
30: 0f b6 05 00 00 00 00 movzbl 0x0(%rip),%eax # 37 <compare_char_aligned+0x37>
33: R_X86_64_PC32 target-0x1
37: 3c 33 cmp $0x33,%al
39: 0f 95 c0 setne %al
3c: 0f b6 c0 movzbl %al,%eax
3f: c3 retq
7: b8 01 00 00 00 mov $0x1,%eax
先将返回值寄存器寄存器A预设为1表示不相等。
0: 0f b6 15 00 00 00 00 movzbl 0x0(%rip),%edx # target-0x4
c: 80 fa 30 cmp $0x30,%dl
f: 74 07 je 18
11: c3 retq
将目标字符串第0个字符读入寄存器D,然后与参考字符串第0个字符'0'(0x30)(硬编码在指令中)进行比较。
如果相等,继续执行步骤18。否则,返回寄存器A的预设值1。
18: 0f b6 15 00 00 00 00 movzbl 0x0(%rip),%edx # target-0x3
1f: 80 fa 31 cmp $0x31,%dl
22: 75 ed jne 11
将目标字符串第1个字符读入寄存器D,然后与参考字符串第1个字符'1'(0x31)(硬编码在指令中)进行比较。
如果相等,继续。否则,跳转步骤11以返回寄存器A的预设值1。
24: 0f b6 15 00 00 00 00 movzbl 0x0(%rip),%edx # target-0x2
2b: 80 fa 32 cmp $0x32,%dl
2e: 75 e1 jne 11
将目标字符串第2个字符读入寄存器D,然后与参考字符串第2个字符'2'(0x32)(硬编码在指令中)进行比较。
如果相等,继续。否则,跳转步骤11以返回寄存器A的预设值1。
30: 0f b6 05 00 00 00 00 movzbl 0x0(%rip),%eax # target-0x1
37: 3c 33 cmp $0x33,%al
39: 0f 95 c0 setne %al
3c: 0f b6 c0 movzbl %al,%eax
3f: c3 retq
将目标字符串第3个字符读入寄存器A,然后与参考字符串第3个字符'3'(0x33)(硬编码在指令中)进行比较。
如果相等,清零寄存器A为0。否则,设置寄存器A为1。扩展寄存器A并返回。
ARM逐字比较对齐的字符串
以下为原始机器码:
00000000 <compare_char_aligned>:
0: 4b08 ldr r3, [pc, #32] ; (24 <compare_char_aligned+0x24>)
2: 2001 movs r0, #1
4: 781a ldrb r2, [r3, #0]
6: 2a30 cmp r2, #48 ; 0x30
8: d000 beq.n c <compare_char_aligned+0xc>
a: 4770 bx lr
c: 785a ldrb r2, [r3, #1]
e: 2a31 cmp r2, #49 ; 0x31
10: d1fb bne.n a <compare_char_aligned+0xa>
12: 789a ldrb r2, [r3, #2]
14: 2a32 cmp r2, #50 ; 0x32
16: d1f8 bne.n a <compare_char_aligned+0xa>
18: 78d8 ldrb r0, [r3, #3]
1a: 3833 subs r0, #51 ; 0x33
1c: 1e43 subs r3, r0, #1
1e: 4198 sbcs r0, r3
20: e7f3 b.n a <compare_char_aligned+0xa>
22: 46c0 nop ; (mov r8, r8)
24: 0000 movs r0, r0
24: R_ARM_ABS32 target
2: 2001 movs r0, #1
先将返回值寄存器寄存器R0预设为1表示不相等。
0: 4b08 ldr r3, [pc, #32] ; 24
24: 0000 R_ARM_ABS32 target
将目标字符串的地址读入寄存器R3。
4: 781a ldrb r2, [r3, #0]
6: 2a30 cmp r2, #48 ; 0x30
8: d000 beq.n c
a: 4770 bx lr
将目标字符串第0个字符读入寄存器寄存器R2,然后与参考字符串第0个字符'0'(0x30)(硬编码在指令中)进行比较。
如果相等,继续执行步骤c。否则,返回寄存器R0的预设值1。
c: 785a ldrb r2, [r3, #1]
e: 2a31 cmp r2, #49 ; 0x31
10: d1fb bne.n a
将目标字符串第1个字符读入寄存器寄存器R2,然后与参考字符串第1个字符'1'(0x31)(硬编码在指令中)进行比较。
如果相等,继续。否则,跳转步骤a以返回寄存器R0的预设值1。
12: 789a ldrb r2, [r3, #2]
14: 2a32 cmp r2, #50 ; 0x32
16: d1f8 bne.n a
将目标字符串第2个字符读入寄存器寄存器R2,然后与参考字符串第2个字符'2'(0x32)(硬编码在指令中)进行比较。
如果相等,继续。否则,跳转步骤a以返回寄存器R0的预设值1。
18: 78d8 ldrb r0, [r3, #3]
1a: 3833 subs r0, #51 ; 0x33
1c: 1e43 subs r3, r0, #1
1e: 4198 sbcs r0, r3
20: e7f3 b.n a
将目标字符串第3个字符读入寄存器寄存器R0,然后与参考字符串第3个字符'3'(0x33)(硬编码在指令中)进行比较。
如果相等:
1a: r0 = '3' - 0x33 = 0
1c: r3 = 0 - 1 = -1, carry = 1
1e: r0 = 0 - (-1) - 1 = 0
如果不相等:
1a: r0 = any(!'3') - 0x33 = some
1c: r3 = some - 1, carry = 0 ; 要使X-1溢出,X只能是1。
1e: r0 = some - (some - 1) - 0 = 1
返回寄存器R0中的值。
Xtensa逐字比较对齐的字符串
以下为原始机器码:
00000000 <compare_char_aligned>:
0: 004136 entry a1, 32
3: 000091 l32r a9, fffc0004 <compare_str_unknown+0xfffbfeb8>
3: R_XTENSA_SLOT0_OP .literal
6: 0a3c movi.n a10, 48
8: 0020c0 memw
b: 000982 l8ui a8, a9, 0
e: 120c movi.n a2, 1
10: 748080 extui a8, a8, 0, 8
13: 2d98a7 bne a8, a10, 44 <compare_char_aligned+0x44>
13: R_XTENSA_SLOT0_OP .text+0x44
16: 0020c0 memw
19: 010982 l8ui a8, a9, 1
1c: 1a3c movi.n a10, 49
1e: 748080 extui a8, a8, 0, 8
21: 1f98a7 bne a8, a10, 44 <compare_char_aligned+0x44>
21: R_XTENSA_SLOT0_OP .text+0x44
24: 0020c0 memw
27: 020982 l8ui a8, a9, 2
2a: 2a3c movi.n a10, 50
2c: 748080 extui a8, a8, 0, 8
2f: 1198a7 bne a8, a10, 44 <compare_char_aligned+0x44>
2f: R_XTENSA_SLOT0_OP .text+0x44
32: 0020c0 memw
35: 030982 l8ui a8, a9, 3
38: 00a092 movi a9, 0
3b: 748080 extui a8, a8, 0, 8
3e: cdc882 addi a8, a8, -51
41: 832980 moveqz a2, a9, a8
44: f01d retw.n
00000000 <.literal>:
...
0: R_XTENSA_32 target
4: R_XTENSA_32 target
8: R_XTENSA_32 target
c: cccdced0
10: 00000000
10: R_XTENSA_32 target
14: cccdced0
...
18: R_XTENSA_32 .rodata
1c: R_XTENSA_32 target
20: R_XTENSA_32 .rodata
24: R_XTENSA_32 target+0x1
28: R_XTENSA_32 .rodata
2c: R_XTENSA_32 target_ext
0: 004136 entry a1, 32
将窗口移动32字节以保存母函数的寄存器。这等于是创建32字节的栈并将寄存器入栈。
e: 120c movi.n a2, 1
先将返回值寄存器寄存器A2预设为1表示不相等。
3: 000091 l32r a9, fffc0004
3: R_XTENSA_SLOT0_OP .literal
00000000 <.literal>:
0: R_XTENSA_32 target
将目标字符串的地址读入寄存器A9。
6: 0a3c movi.n a10, 48
8: 0020c0 memw
b: 000982 l8ui a8, a9, 0
10: 748080 extui a8, a8, 0, 8
13: 2d98a7 bne a8, a10, 44
将目标字符串第0个字符读入寄存器A8并扩展,将参考字符串第0个字符(u32_t)'0'(48)(硬编码在指令中)读入寄存器A10,然后比较。
如果相等,继续。否则,跳转步骤44以返回寄存器A2的预设值1。
16: 0020c0 memw
19: 010982 l8ui a8, a9, 1
1c: 1a3c movi.n a10, 49
1e: 748080 extui a8, a8, 0, 8
21: 1f98a7 bne a8, a10, 44
将目标字符串第1个字符读入寄存器A8并扩展,将参考字符串第1个字符(u32_t)'1'(49)(硬编码在指令中)读入寄存器A10,然后比较。
如果相等,继续。否则,跳转步骤44以返回寄存器A2的预设值1。
24: 0020c0 memw
27: 020982 l8ui a8, a9, 2
2a: 2a3c movi.n a10, 50
2c: 748080 extui a8, a8, 0, 8
2f: 1198a7 bne a8, a10, 44
将目标字符串第2个字符读入寄存器A8并扩展,将参考字符串第2个字符(u32_t)'2'(50)(硬编码在指令中)读入寄存器A10,然后比较。
如果相等,继续。否则,跳转步骤44以返回寄存器A2的预设值1。
32: 0020c0 memw
35: 030982 l8ui a8, a9, 3
38: 00a092 movi a9, 0
3b: 748080 extui a8, a8, 0, 8
3e: cdc882 addi a8, a8, -51
41: 832980 moveqz a2, a9, a8
44: f01d retw.n
将目标字符串第3个字符读入寄存器A8并扩展,并于参考字符串第3个字符(u32_t)'3'(51)(硬编码在指令中)的负值相加。
如果相等(寄存器A8中加法结果为零),设置寄存器A2为寄存器A9(值为0)。否则,寄存器A2不变(值为1)。返回寄存器A2的值并复原窗口。
可见,在三种平台上,GCC生成了的机器码使用相同算法:
-
逐字比较字符串。
-
参考字符串的每个字符被编码在指令中(立即寻址模式)。
整体比较对齐的字符串
如果我们将目标字符串当作一个32字节的整体的词(也就是32位CPU的一个数据单元),就只用进行一次比较。参考以下C语言代码:
int compare_u32_aligned() {
return (*((uint32_t*)target) == *((uint32_t*)((char[]){'0', '1', '2', '3'}))) ? 0 : 1;
}
在这个例子中,我们将4字节长的参考字符串转换为一个32比特无符号整数:
-
(char[]){'0', '1', '2', '3'}- 指向内联字符串的指针:4字符,各8比特。 -
(uint32_t*)((char[]){'0', '1', '2', '3'})- 该指针转换为32比特无符号整数的指针。 -
*((uint32_t*)((char[]){'0', '1', '2', '3'}))- 以32比特无符号整数的方式从该指针取值。
这个写法将参考字符串:
-
变成一个只需一次比较的单一数据单位,而不是一个需要
sizeof(array) / sizeof(array[0])次比较的数组。 -
嵌入程序文本内存中,免除分配空间并复制到数据内存中的需求。
在CPU的硬件层面上来说并没有数据类型这个概念,C语言中转换指针类型并不会更改其指向的值。
X86整体比较对齐的字符串
以下为原始机器码:
0000000000000080 <compare_u32_aligned>:
80: 31 c0 xor %eax,%eax
82: 81 3d 00 00 00 00 30 cmpl $0x33323130,0x0(%rip) # 8c <compare_u32_aligned+0xc>
89: 31 32 33
84: R_X86_64_PC32 target-0x8
8c: 0f 95 c0 setne %al
8f: c3 retq
80: 31 c0 xor %eax,%eax
清空返回寄存器A。
82: 81 3d 00 00 00 00 30 31 32 33 cmpl $0x33323130,0x0(%rip)
84: R_X86_64_PC32 target-0x8
比较目标字符串的第0个词(包含4个字符)和参考字符串"3210"(0x33323130)。注意,第一个字符在低位。
8c: 0f 95 c0 setne %al
8f: c3 retq
如果相等,清零寄存器A为0。否则,设置寄存器A为1。然后,返回寄存器A的值。
ARM整体比较对齐的字符串
以下为原始机器码:
00000050 <compare_u32_aligned>:
50: 4b03 ldr r3, [pc, #12] ; (60 <compare_u32_aligned+0x10>)
52: 6818 ldr r0, [r3, #0]
54: 4b03 ldr r3, [pc, #12] ; (64 <compare_u32_aligned+0x14>)
56: 681b ldr r3, [r3, #0]
58: 1ac0 subs r0, r0, r3
5a: 1e43 subs r3, r0, #1
5c: 4198 sbcs r0, r3
5e: 4770 bx lr
...
60: R_ARM_ABS32 target
64: R_ARM_ABS32 .rodata
00000000 <.rodata>:
0: 3130 adds r1, #48 ; 0x30
2: 3332 adds r3, #50 ; 0x32
50: 4b03 ldr r3, [pc, #12] ; 60
52: 6818 ldr r0, [r3, #0]
60: R_ARM_ABS32 target
将目标字符串的地址读入寄存器R3。然后,将其第0个词(包含4个字符)读入寄存器R0。
54: 4b03 ldr r3, [pc, #12] ; 64
56: 681b ldr r3, [r3, #0]
64: R_ARM_ABS32 .rodata
00000000 <.rodata>:
0: 3130
2: 3332
将参考字符串的地址读入寄存器R3。然后,将其第0个词(包含4个字符)读入寄存器R3。
58: 1ac0 subs r0, r0, r3
5a: 1e43 subs r3, r0, #1
5c: 4198 sbcs r0, r3
5e: 4770 bx lr
通过相减,比较目标字符串与参考字符串的第0个词(包含4个字符)。
如果相等:
58: r0 = "3210" - 0x33323130 = 0
5a: r3 = 0 - 1 = -1, carry = 1
5c: r0 = 0 - (-1) - 1 = 0
如果不相等:
58: r0 = any(!"3210") - 0x33323130 = some
5a: r3 = some - 1, carry = 0 ; 要使X-1溢出,X只能是1。
5c: r0 = some - (some - 1) - 0 = 1
返回寄存器R0中的值。
Xtensa整体比较对齐的字符串
以下为原始机器码:
00000090 <compare_u32_aligned>:
90: 004136 entry a1, 32
93: 000081 l32r a8, fffc0094 <compare_str_unknown+0xfffbff48>
93: R_XTENSA_SLOT0_OP .literal+0x8
96: 000091 l32r a9, fffc0098 <compare_str_unknown+0xfffbff4c>
96: R_XTENSA_SLOT0_OP .literal+0xc
99: 0888 l32i.n a8, a8, 0
9b: 120c movi.n a2, 1
9d: 889a add.n a8, a8, a9
9f: 090c movi.n a9, 0
a1: 832980 moveqz a2, a9, a8
a4: f01d retw.n
00000000 <.literal>:
...
0: R_XTENSA_32 target
4: R_XTENSA_32 target
8: R_XTENSA_32 target
c: cccdced0
10: 00000000
10: R_XTENSA_32 target
14: cccdced0
...
18: R_XTENSA_32 .rodata
1c: R_XTENSA_32 target
20: R_XTENSA_32 .rodata
24: R_XTENSA_32 target+0x1
28: R_XTENSA_32 .rodata
2c: R_XTENSA_32 target_ext
90: 004136 entry a1, 32
将窗口移动32字节以保存母函数的寄存器。
9b: 120c movi.n a2, 1
先将返回值寄存器寄存器A2预设为1表示不相等。
93: 000081 l32r a8, fffc0094
93: R_XTENSA_SLOT0_OP .literal+0x8
96: 000091 l32r a9, fffc0098
96: R_XTENSA_SLOT0_OP .literal+0xc
99: 0888 l32i.n a8, a8, 0
00000000 <.literal>:
8: R_XTENSA_32 target
c: cccdced0
将目标字符串的地址读入寄存器A8。然后,将其第0个词(包含4个字符)读入寄存器A8。
将参考字符串的第0个词(包含4个字符)的负值读入寄存器A9。
9d: 889a add.n a8, a8, a9
9f: 090c movi.n a9, 0
a1: 832980 moveqz a2, a9, a8
a4: f01d retw.n
通过与参考字符串的负值相加作比较。
如果相等(寄存器A8中加法结果为零),设置寄存器A2为寄存器A9(值为0)。否则,寄存器A2不变(值为1)。返回寄存器A2的值并复原窗口。
可见,在三种平台上,GCC生成了的机器码将只执行读与比较一次。自然,这将比前面的方法要快不少。另外,将4个字符看作一个整体来比较所需的代码体积也更小。
比较未对齐的字符串
在之前的例子中,CPU从内存中32比特对齐的地址读取32位数据。要是数据并没32比特对齐呢?
要模拟未对齐的目标字符串,我们可以给对齐的目标字符串的地址+1,例如target[1]或target + 1。
逐字比较未对齐的字符串
参考以下C语言代码:
int compare_char_misaligned() {
return (target[1] == '0' && target[2] == '1' && target[3] == '2' && target[4] == '3') ? 0: 1;
}
在三种平台上,未对齐并不会对逐字比较方法造成影响。所有三种平台都支持从任何地址读取字节。
下面展示了X86逐字比较未对齐的字符串:
0000000000000040 <compare_char_misaligned>:
40: 0f b6 15 00 00 00 00 movzbl 0x0(%rip),%edx # 47 <compare_char_misaligned+0x7>
43: R_X86_64_PC32 target-0x3
47: b8 01 00 00 00 mov $0x1,%eax
4c: 80 fa 30 cmp $0x30,%dl
4f: 74 07 je 58 <compare_char_misaligned+0x18>
51: c3 retq
52: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
58: 0f b6 15 00 00 00 00 movzbl 0x0(%rip),%edx # 5f <compare_char_misaligned+0x1f>
5b: R_X86_64_PC32 target-0x2
5f: 80 fa 31 cmp $0x31,%dl
62: 75 ed jne 51 <compare_char_misaligned+0x11>
64: 0f b6 15 00 00 00 00 movzbl 0x0(%rip),%edx # 6b <compare_char_misaligned+0x2b>
67: R_X86_64_PC32 target-0x1
6b: 80 fa 32 cmp $0x32,%dl
6e: 75 e1 jne 51 <compare_char_misaligned+0x11>
70: 0f b6 05 00 00 00 00 movzbl 0x0(%rip),%eax # 77 <compare_char_misaligned+0x37>
73: R_X86_64_PC32 target
77: 3c 33 cmp $0x33,%al
79: 0f 95 c0 setne %al
7c: 0f b6 c0 movzbl %al,%eax
7f: c3 retq
下面展示了ARM逐字比较未对齐的字符串:
00000028 <compare_char_misaligned>:
28: 4b08 ldr r3, [pc, #32] ; (4c <compare_char_misaligned+0x24>)
2a: 2001 movs r0, #1
2c: 785a ldrb r2, [r3, #1]
2e: 2a30 cmp r2, #48 ; 0x30
30: d000 beq.n 34 <compare_char_misaligned+0xc>
32: 4770 bx lr
34: 789a ldrb r2, [r3, #2]
36: 2a31 cmp r2, #49 ; 0x31
38: d1fb bne.n 32 <compare_char_misaligned+0xa>
3a: 78da ldrb r2, [r3, #3]
3c: 2a32 cmp r2, #50 ; 0x32
3e: d1f8 bne.n 32 <compare_char_misaligned+0xa>
40: 7918 ldrb r0, [r3, #4]
42: 3833 subs r0, #51 ; 0x33
44: 1e43 subs r3, r0, #1
46: 4198 sbcs r0, r3
48: e7f3 b.n 32 <compare_char_misaligned+0xa>
4a: 46c0 nop ; (mov r8, r8)
4c: 0000 movs r0, r0
4c: R_ARM_ABS32 target
下面展示了Xtensa逐字比较未对齐的字符串:
00000048 <compare_char_misaligned>:
48: 004136 entry a1, 32
4b: 000091 l32r a9, fffc004c <compare_str_unknown+0xfffbff00>
4b: R_XTENSA_SLOT0_OP .literal+0x4
4e: 0a3c movi.n a10, 48
50: 0020c0 memw
53: 010982 l8ui a8, a9, 1
56: 120c movi.n a2, 1
58: 748080 extui a8, a8, 0, 8
5b: 2d98a7 bne a8, a10, 8c <compare_char_misaligned+0x44>
5b: R_XTENSA_SLOT0_OP .text+0x8c
5e: 0020c0 memw
61: 020982 l8ui a8, a9, 2
64: 1a3c movi.n a10, 49
66: 748080 extui a8, a8, 0, 8
69: 1f98a7 bne a8, a10, 8c <compare_char_misaligned+0x44>
69: R_XTENSA_SLOT0_OP .text+0x8c
6c: 0020c0 memw
6f: 030982 l8ui a8, a9, 3
72: 2a3c movi.n a10, 50
74: 748080 extui a8, a8, 0, 8
77: 1198a7 bne a8, a10, 8c <compare_char_misaligned+0x44>
77: R_XTENSA_SLOT0_OP .text+0x8c
7a: 0020c0 memw
7d: 040982 l8ui a8, a9, 4
80: 00a092 movi a9, 0
83: 748080 extui a8, a8, 0, 8
86: cdc882 addi a8, a8, -51
89: 832980 moveqz a2, a9, a8
8c: f01d retw.n
00000000 <.literal>:
...
0: R_XTENSA_32 target
4: R_XTENSA_32 target
8: R_XTENSA_32 target
c: cccdced0
10: 00000000
10: R_XTENSA_32 target
14: cccdced0
...
18: R_XTENSA_32 .rodata
1c: R_XTENSA_32 target
20: R_XTENSA_32 .rodata
24: R_XTENSA_32 target+0x1
28: R_XTENSA_32 .rodata
2c: R_XTENSA_32 target_ext
整体比较未对齐的字符串
参考以下C语言代码:
int compare_u32_misaligned() {
return (*((uint32_t*)(target+1)) == *((uint32_t*)((char[]){'0', '1', '2', '3'}))) ? 0 : 1;
}
X86整体比较未对齐的字符串
以下为原始机器码:
0000000000000090 <compare_u32_misaligned>:
90: 31 c0 xor %eax,%eax
92: 81 3d 00 00 00 00 30 cmpl $0x33323130,0x0(%rip) # 9c <compare_u32_misaligned+0xc>
99: 31 32 33
94: R_X86_64_PC32 target-0x7
9c: 0f 95 c0 setne %al
9f: c3 retq
90: 31 c0 xor %eax,%eax
清空返回寄存器A。
92: 81 3d 00 00 00 00 30 31 32 33 cmpl $0x33323130,0x0(%rip)
94: R_X86_64_PC32 target-0x7
比较目标字符串的第0个词(包含4个字符)和参考字符串"3210"(0x33323130)。
9c: 0f 95 c0 setne %al
9f: c3 retq
如果相等,清零寄存器A为0。否则,设置寄存器A为1。然后,返回寄存器A的值。
可见,X86支持从未对齐的地址读取多字节数据。因此,未对齐并不会X86的代码造成任何不同。但是,现代X86架构CPU可以在内部将输入的机器码转写成不同的微代码。也就是说,在内部,CPU也许会执行多次对齐的读取。
ARM整体比较未对齐的字符串
以下为原始机器码:
00000068 <compare_u32_misaligned>:
68: 4b08 ldr r3, [pc, #32] ; (8c <compare_u32_misaligned+0x24>)
6a: 1c5a adds r2, r3, #1
6c: 7858 ldrb r0, [r3, #1]
6e: 789b ldrb r3, [r3, #2]
70: 021b lsls r3, r3, #8
72: 4303 orrs r3, r0
74: 7890 ldrb r0, [r2, #2]
76: 0400 lsls r0, r0, #16
78: 4303 orrs r3, r0
7a: 78d0 ldrb r0, [r2, #3]
7c: 0600 lsls r0, r0, #24
7e: 4318 orrs r0, r3
80: 4b03 ldr r3, [pc, #12] ; (90 <compare_u32_misaligned+0x28>)
82: 681b ldr r3, [r3, #0]
84: 1ac0 subs r0, r0, r3
86: 1e43 subs r3, r0, #1
88: 4198 sbcs r0, r3
8a: 4770 bx lr
...
8c: R_ARM_ABS32 target
90: R_ARM_ABS32 .rodata
00000000 <.rodata>:
0: 3130 adds r1, #48 ; 0x30
2: 3332 adds r3, #50 ; 0x32
68: 4b08 ldr r3, [pc, #32] ; 8c
6a: 1c5a adds r2, r3, #1
8c: R_ARM_ABS32 target
将对齐的目标字符串的地址读入寄存器R3。然后,加上1以模拟未对齐的目标字符串地址,写入寄存器R2。
换言之,寄存器R2就是目标字符串地址,寄存器R3就是目标字符串地址-1。
6c: 7858 ldrb r0, [r3, #1]
6e: 789b ldrb r3, [r3, #2]
70: 021b lsls r3, r3, #8
72: 4303 orrs r3, r0
74: 7890 ldrb r0, [r2, #2]
76: 0400 lsls r0, r0, #16
78: 4303 orrs r3, r0
7a: 78d0 ldrb r0, [r2, #3]
7c: 0600 lsls r0, r0, #24
7e: 4318 orrs r0, r3
逐字读取目标字符串,在寄存器R0中构建第0个词(包含4个字符):
6c: r0 = target[ -1 + 1 ] = target[0]
6e: r3 = target[ -1 + 2 ] = target[1]
70: r3 = r3 << 8 = target[1] << 8
72: r3 = r3 | r0 = (target[1] << 8) | target[0]
74: r0 = target[ 0 + 2 ] = target[2]
76: r0 = r0 << 16 = target[2] << 16
78: r3 = r3 | r0 = (target[2] << 16) | (target[1] << 8) | target[0]
7a: r0 = target[ 0 + 3 ] = target[3]
7c: r0 = r0 << 24 = target[3] << 24
7e: r3 = r3 | r0 = (target[3] << 24) | (target[2] << 16) | (target[1] << 8) | target[0]
80: 4b03 ldr r3, [pc, #12] ; 90
82: 681b ldr r3, [r3, #0]
90: R_ARM_ABS32 .rodata
00000000 <.rodata>:
0: 3130
2: 3332
将参考字符串的地址读入寄存器R3。然后,将其第0个词(包含4个字符)读入寄存器R3。
84: 1ac0 subs r0, r0, r3
86: 1e43 subs r3, r0, #1
88: 4198 sbcs r0, r3
8a: 4770 bx lr
使用和之前ARM整体比较对齐的字符串相同的算法,比较目标字符串与参考字符串的第0个词(包含4个字符)
返回寄存器R0的值。
未对齐在ARM整体比较字符串时导致了严重的性能问题。CPU必须逐字读取,然后再通过位移和比特或来构建数据单位,之后才能比较。
Xtensa整体比较未对齐的字符串
以下为原始机器码:
000000a8 <compare_u32_misaligned>:
a8: 004136 entry a1, 32
ab: 000081 l32r a8, fffc00ac <compare_str_unknown+0xfffbff60>
ab: R_XTENSA_SLOT0_OP .literal+0x10
ae: 120c movi.n a2, 1
b0: 0208a2 l8ui a10, a8, 2
b3: 0108b2 l8ui a11, a8, 1
b6: 030892 l8ui a9, a8, 3
b9: 11aa80 slli a10, a10, 8
bc: 040882 l8ui a8, a8, 4
bf: 20aab0 or a10, a10, a11
c2: 119900 slli a9, a9, 16
c5: 2099a0 or a9, a9, a10
c8: 018880 slli a8, a8, 24
cb: 208890 or a8, a8, a9
ce: 000091 l32r a9, fffc00d0 <compare_str_unknown+0xfffbff84>
ce: R_XTENSA_SLOT0_OP .literal+0x14
d1: 889a add.n a8, a8, a9
d3: 090c movi.n a9, 0
d5: 832980 moveqz a2, a9, a8
d8: f01d retw.n
00000000 <.literal>:
...
0: R_XTENSA_32 target
4: R_XTENSA_32 target
8: R_XTENSA_32 target
c: cccdced0
10: 00000000
10: R_XTENSA_32 target
14: cccdced0
...
18: R_XTENSA_32 .rodata
1c: R_XTENSA_32 target
20: R_XTENSA_32 .rodata
24: R_XTENSA_32 target+0x1
28: R_XTENSA_32 .rodata
2c: R_XTENSA_32 target_ext
a8: 004136 entry a1, 32
将窗口移动32字节以保存母函数的寄存器。
ae: 120c movi.n a2, 1
先将返回值寄存器寄存器A2预设为1表示不相等。
ab: 000081 l32r a8, fffc00ac
ab: R_XTENSA_SLOT0_OP .literal+0x10
b0: 0208a2 l8ui a10, a8, 2
b3: 0108b2 l8ui a11, a8, 1
b6: 030892 l8ui a9, a8, 3
b9: 11aa80 slli a10, a10, 8
bc: 040882 l8ui a8, a8, 4
bf: 20aab0 or a10, a10, a11
c2: 119900 slli a9, a9, 16
c5: 2099a0 or a9, a9, a10
c8: 018880 slli a8, a8, 24
cb: 208890 or a8, a8, a9
00000000 <.literal>:
10: 00000000
10: R_XTENSA_32 target
将对齐的目标字符串的地址读入寄存器A8。模拟未对齐的目标字符串地址-1。
逐字读取对齐的目标字符串的第1到4个字符,即未对齐的目标字符串的第0到3个字符。在寄存器A8中构建第0个词(包含4个字符):
b0: a10 = target[ -1 + 2 ] = target[1]
b3: a11 = target[ -1 + 1 ] = target[0]
b6: a9 = target[ -1 + 3 ] = target[2]
b9: a10 = a10 << 8 = target[1] << 8
bc: a8 = target[ -1 + 4 ] = target[4]
bf: a10 = a10 | a11 = (target[1] << 8) | target[0]
c2: a9 = a9 << 16 = target[2] << 16
c5: a9 = a9 | a10 = (target[2] << 16) | (target[1] << 8) | target[0]
c8: a8 = a8 << 24 = target[3] << 24
cb: a8 = a8 | a9 = (target[3] << 24) | (target[2] << 16) | (target[1] << 8) | target[0]
ce: 000091 l32r a9, fffc00d0
ce: R_XTENSA_SLOT0_OP .literal+0x14
d1: 889a add.n a8, a8, a9
d3: 090c movi.n a9, 0
d5: 832980 moveqz a2, a9, a8
d8: f01d retw.n
00000000 <.literal>:
14: cccdced0
将参考字符串的第0个词(包含4个字符)的负值读入寄存器A9,然后通过与参考字符串的负值相加作比较。
如果相等(寄存器A8中加法结果为零),设置寄存器A2为寄存器A9(值为0)。否则,寄存器A2不变(值为1)。返回寄存器 A2的值并复原窗口。
和ARM的情况类似,未对齐在Xtensa整体比较字符串时也导致了严重的性能问题。CPU必须逐字读取,然后再通过位移和比特或来构建数据单位,之后才能比较。
性能总结
| 在第N个字符发现不同 | 0(操作) | 1(操作) | 2(操作) | 3(操作) | 相等(操作) | 平均(操作) | 代码体积(字节) |
|---|---|---|---|---|---|---|---|
| X86 - 逐字 - 对齐 | 5 | 8 | 11 | 15 | 15 | 10.8 | 64 |
| X86 - 整体 - 对齐 | 4 | 4 | 4 | 4 | 4 (+73%) | 4 (+63%) | 16 (+75%) |
| X86 - 逐字 - 未对齐 | 5 | 8 | 11 | 15 | 15 | 10.8 | 64 |
| X86 - 整体 - 未对齐 | 4 | 4 | 4 | 4 | 4 (+73%) | 4 (+63%) | 16 (+75%) |
| ARM - 逐字 - 对齐 | 6 | 9 | 12 | 17 | 17 | 12.2 | 34 |
| ARM - 整体 - 对齐 | 8 | 8 | 8 | 8 | 8 (+53%) | 8 (+34%) | 16 (+53%) |
| ARM - 逐字 - 未对齐 | 6 | 9 | 12 | 17 | 17 | 12.2 | 34 |
| ARM - 整体 - 未对齐 | 18 | 18 | 18 | 18 | 18 (-6%) | 18 (-48%) | 36 (-6%) |
| Xtensa - 逐字 - 对齐 | 9 | 14 | 19 | 25 | 25 | 18.4 | 70 |
| Xtensa - 整体 - 对齐 | 9 | 9 | 9 | 9 | 9 (+64%) | 9 (+51%) | 22 (+68%) |
| Xtensa - 逐字 - 未对齐 | 9 | 14 | 19 | 25 | 25 | 18.4 | 70 |
| Xtensa - 整体 - 未对齐 | 18 | 18 | 18 | 18 | 18 (+28%) | 18 (+2%) | 50 (+29%) |
操作数
逐字比较时,如果函数在最后一个字符前就发现了不同,函数就可以提前结束。
对于长的字符串(大于一个词),很可能前半部分的词都相同,然后,在某个词的位置,发现那个词的某个字不同。在这种情况下,我们就需要对前半部分的词参考“等于”情况的操作数,对最后一个词参考“平均”情况的操作数。例如,如果一个长的字符串的第X个字符发现不同,那么操作数就应该为:
(int)(X / sizeof(word)) * executedInstructionCount_equal + executedInstructionCount_average
当整体比较时,CPU将会在比较前读取所有字符。因此,函数内的所有指令都会被执行。也就是说,无论字符串相等还是不等,操作数(和执行时间)将会相同。
当逐字比较时,在三种平台下,无论是否对齐,“平均”情况的操作数都约为“相等”情况的操作数的3/4。
因为X86支持非对齐多字节读取,无论是否对齐,整体比较比逐字比较的操作数更少。也就是说,要快73%/63%(相等/平均)。
CPU不支持非对齐多字节读取的话,数据对齐就很重要了。ARM上,对齐时整体比较比逐字比较快53%/34%(相等/平均),非对齐时整体比较比逐字比较反而慢6%/48%(相等/平均)。Xtensa上,对齐时整体比较比逐字比较快64%/51%(相等/平均),但是非对齐时整体比较比逐字只有快28%/2%(相等/平均)。
我们假设执行时间和操作数呈正比(ARM和Xtensa都是RISC架构,X86为CISC架构)。实际性能受到内存总线拥挤、流水线阻塞、分支预测和其它优化的影响。
代码体积
另外,我们更倾向于更小的代码体积,这不仅节约储存空间,还能避免缓存缺失。因此,也能提高效率。
因为X86支持非对齐多字节读取,无论是否对齐,整体比较的代码体积都比逐字比较的代码体积小75%。
CPU不支持非对齐多字节读取的话,数据对齐就很重要了。ARM上,对齐时整体比较比逐字比较小53%,非对齐时反而大6%。Xtensa上,对齐时整体比较比逐字比较小68%,非对齐时整体比较比逐字比较只有小29%。
总言之,如果数据对齐,整体比较的代码体积比逐字比较更小,执行代码数也更少。
现代桌面CPU可以在硬件层面进行乱序执行、并行运行、指令改写。这能在执行时在内部优化编译器生成的指令。因此,实际速度不定。
低端、嵌入式CPU没有这些硬件层面的优化(以节省成本、节约能耗、避免错误)。分析编译器生成的机器码就能让我们大致了解实际性能。
使用<string.h>中的memcmp()函数
C语言标准库<string.h>提供了一系列字符串(包括二进制字符,即内存)操作函数,其中一个就是内存比较:memcmp(const void* target, const void* reference, size_t size)。
在这一个章节,我们将测试memcmp()是否能整体比较多个字符。
当比较长度未知时使用memcmp()
当比较长度未知时,GCC就会链接并使用库提供的函数构造。参考以下C语言代码:
int compare_str_prototype(size_t size) {
return (memcmp((void*)target, (char[]){'0', '1', '2', '3'}, size) == 0) ? 0 : 1;
}
在X86平台上编译为:
0000000000000000 *UND* 0000000000000000 memcmp
00000000000000f0 <compare_str_prototype>:
f0: 48 83 ec 18 sub $0x18,%rsp
f4: 48 89 fa mov %rdi,%rdx
f7: 48 8d 3d 00 00 00 00 lea 0x0(%rip),%rdi # fe <compare_str_prototype+0xe>
fa: R_X86_64_PC32 target-0x4
fe: 48 8d 74 24 0c lea 0xc(%rsp),%rsi
103: c7 44 24 0c 30 31 32 movl $0x33323130,0xc(%rsp)
10a: 33
10b: e8 00 00 00 00 callq 110 <compare_str_prototype+0x20>
10c: R_X86_64_PLT32 memcmp-0x4
110: 85 c0 test %eax,%eax
112: 0f 95 c0 setne %al
115: 48 83 c4 18 add $0x18,%rsp
119: 0f b6 c0 movzbl %al,%eax
11c: c3 retq
在ARM平台上编译为:
00000000 *UND* 00000000 memcmp
0000014c <compare_str_prototype>:
14c: b500 push {lr}
14e: 4b06 ldr r3, [pc, #24] ; (168 <compare_str_prototype+0x1c>)
150: b083 sub sp, #12
152: 681b ldr r3, [r3, #0]
154: 0002 movs r2, r0
156: a901 add r1, sp, #4
158: 4804 ldr r0, [pc, #16] ; (16c <compare_str_prototype+0x20>)
15a: 9301 str r3, [sp, #4]
15c: f7ff fffe bl 0 <memcmp>
15c: R_ARM_THM_CALL memcmp
160: 1e43 subs r3, r0, #1
162: 4198 sbcs r0, r3
164: b003 add sp, #12
166: bd00 pop {pc}
...
168: R_ARM_ABS32 .rodata
16c: R_ARM_ABS32 target
00000000 <.rodata>:
0: 3130 adds r1, #48 ; 0x30
2: 3332 adds r3, #50 ; 0x32
在Xtensa平台上编译为:
00000000 *UND* 00000000 memcmp
00000184 <compare_str_prototype>:
184: 006136 entry a1, 48
187: 000081 l32r a8, fffc0188 <compare_str_prototype+0xfffc0004>
187: R_XTENSA_SLOT0_OP .literal+0x30
18a: 0000a1 l32r a10, fffc018c <compare_str_prototype+0xfffc0008>
18a: R_XTENSA_SLOT0_OP .literal+0x34
18d: 0888 l32i.n a8, a8, 0
18f: 02cd mov.n a12, a2
191: 01bd mov.n a11, a1
193: 0189 s32i.n a8, a1, 0
195: 000025 call8 198 <compare_str_prototype+0x14>
195: R_XTENSA_SLOT0_OP memcmp
198: 080c movi.n a8, 0
19a: 120c movi.n a2, 1
19c: 8328a0 moveqz a2, a8, a10
19f: f01d retw.n
00000000 <.literal>:
...
0: R_XTENSA_32 target
4: R_XTENSA_32 target
8: R_XTENSA_32 target
c: cccdced0
10: 00000000
10: R_XTENSA_32 target
14: cccdced0
...
18: R_XTENSA_32 .rodata
1c: R_XTENSA_32 target
20: R_XTENSA_32 .rodata
24: R_XTENSA_32 target+0x1
28: R_XTENSA_32 .rodata
2c: R_XTENSA_32 target_ext
30: R_XTENSA_32 .rodata
34: R_XTENSA_32 target
也就是说,在说有三种平台上,都将调用外部提供的memcmp()函数,其地址将在链接阶段确定。
X86对已知长度调用memcmp()
而对于已知长度,GCC这会内联比较函数。参考以下C语言代码:
int compare_str_aligned() {
return (memcmp((void*)target, (char[]){'0', '1', '2', '3'}, 4) == 0) ? 0 : 1;
}
int compare_str_misaligned() {
return (memcmp((void*)(target+1), (char[]){'0', '1', '2', '3'}, 4) == 0) ? 0 : 1;
}
extern volatile char target_ext[];
int compare_str_unknown() {
return (memcmp((void*)target_ext, (char[]){'0', '1', '2', '3'}, 4) == 0) ? 0 : 1;
}
-
compare_str_aligned()- 目标字符串已对齐。 -
compare_str_misaligned()- 目标字符串未对齐。 -
compare_str_unknown()- 外部目标字符串的地址待链接阶段确定,因此,其对齐情况未知。换言之,该函数无论是否对齐都可用。
X86调用memcmp()比较对齐的与未对齐的字符串
以下为原始机器码:
00000000000000a0 <compare_str_aligned>:
a0: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # a6 <compare_str_aligned+0x6>
a2: R_X86_64_PC32 target-0x4
a6: c7 44 24 fc 30 31 32 movl $0x33323130,-0x4(%rsp)
ad: 33
ae: 39 44 24 fc cmp %eax,-0x4(%rsp)
b2: 0f 95 c0 setne %al
b5: 0f b6 c0 movzbl %al,%eax
b8: c3 retq
b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
00000000000000c0 <compare_str_misaligned>:
c0: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # c6 <compare_str_misaligned+0x6>
c2: R_X86_64_PC32 target-0x3
c6: c7 44 24 fc 30 31 32 movl $0x33323130,-0x4(%rsp)
cd: 33
ce: 39 44 24 fc cmp %eax,-0x4(%rsp)
d2: 0f 95 c0 setne %al
d5: 0f b6 c0 movzbl %al,%eax
d8: c3 retq
d9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
因为X86支持未对齐的多字节读取,无论是否对齐,机器码都相同:
a6: c7 44 24 fc 30 31 32 33 movl $0x33323130,-0x4(%rsp)
将参考字符串(硬编码在指令中)入栈,地址-4。
a0: 8b 05 00 00 00 00 mov 0x0(%rip),%eax
a2: R_X86_64_PC32 target-0x4
ae: 39 44 24 fc cmp %eax,-0x4(%rsp)
将目标字符串的第0个词(包含4个字符)读入寄存器A,然后与栈内参考字符串比较。
b2: 0f 95 c0 setne %al
b5: 0f b6 c0 movzbl %al,%eax
b8: c3 retq
如果相等,清零寄存器A为0。否则,设置寄存器A为1。扩展寄存器A并返回。
X86调用memcmp()比较外部(未知对齐)字符串
以下为原始机器码:
00000000000000e0 <compare_str_unknown>:
e0: 31 c0 xor %eax,%eax
e2: 81 3d 00 00 00 00 30 cmpl $0x33323130,0x0(%rip) # ec
e9: 31 32 33
e4: R_X86_64_PC32 target_ext-0x8
ec: 0f 95 c0 setne %al
ef: c3 retq
e0: 31 c0 xor %eax,%eax
清空返回寄存器A。
e2: 81 3d 00 00 00 00 30 31 32 33 cmpl $0x33323130,0x0(%rip) # ec <compare_str_unknown+0xc>
e4: R_X86_64_PC32 target_ext-0x8
比较目标字符串与参考字符串的第0个词(包含4个字符)"3210"(0x33323130)。
ec: 0f 95 c0 setne %al
ef: c3 retq
如果相等,清零寄存器A为0。否则,设置寄存器A为1。然后,返回寄存器A的值。
X86上,内联的memcmp()函数总是整体比较字符串。
对于栈使用:
-
目标字符串对齐状况已知时,无论是否对齐,都分配4字节栈空间。
-
对于外部字符串,未知对齐,不使用栈。
ARM对已知长度调用memcmp()
ARM调用memcmp()比较对齐的字符串
以下为原始机器码:
00000094 <compare_str_aligned>:
94: 4b05 ldr r3, [pc, #20] ; (ac <compare_str_aligned+0x18>)
96: b082 sub sp, #8
98: 6818 ldr r0, [r3, #0]
9a: 4b05 ldr r3, [pc, #20] ; (b0 <compare_str_aligned+0x1c>)
9c: 9001 str r0, [sp, #4]
9e: 681b ldr r3, [r3, #0]
a0: 1ac0 subs r0, r0, r3
a2: 1e43 subs r3, r0, #1
a4: 4198 sbcs r0, r3
a6: b002 add sp, #8
a8: 4770 bx lr
aa: 46c0 nop ; (mov r8, r8)
...
ac: R_ARM_ABS32 .rodata
b0: R_ARM_ABS32 target
00000000 <.rodata>:
0: 3130 adds r1, #48 ; 0x30
2: 3332 adds r3, #50 ; 0x32
96: b082 sub sp, #8
栈指针SP下移8字节。
94: 4b05 ldr r3, [pc, #20] ; ac
98: 6818 ldr r0, [r3, #0]
9c: 9001 str r0, [sp, #4]
ac: R_ARM_ABS32 .rodata
00000000 <.rodata>:
0: 3130
2: 3332
将参考字符串读入寄存器R0。然后入栈,地址-4 = SP + 4(相较于母函数的栈)。
然而,栈内的参考字符串并未被使用,而是直接直接使用寄存器R0中的原始副本进行比较。
我也不知道为啥不用栈还要去创建。也许是因为memcmp()函数要求输入缓冲必须在数据内存中,即栈堆而非文本。
9a: 4b05 ldr r3, [pc, #20] ; b0
9e: 681b ldr r3, [r3, #0]
b0: R_ARM_ABS32 target
将目标字符串以32比特数据整体读入寄存器R3。
a0: 1ac0 subs r0, r0, r3
a2: 1e43 subs r3, r0, #1
a4: 4198 sbcs r0, r3
a6: b002 add sp, #8
a8: 4770 bx lr
通过相减,比较目标字符串与参考字符串的第0个词(包含4个字符)。
如果相等:
a0: r0 = "3210" - 0x33323130 = 0
a2: r3 = 0 - 1 = -1, carry = 1
a4: r0 = 0 - (-1) - 1 = 0
如果不相等:
a0: r0 = any(!"3210") - 0x33323130 = some
a2: r3 = some - 1, carry = 0 ; 要使X-1溢出,X只能是1。
a4: r0 = some - (some - 1) - 0 = 1
返回寄存器R0中的值。
ARM调用memcmp()比较未对齐的字符串
以下为原始机器码:
000000b4 <compare_str_misaligned>:
b4: 4b10 ldr r3, [pc, #64] ; (f8 <compare_str_misaligned+0x44>)
b6: b084 sub sp, #16
b8: 681b ldr r3, [r3, #0]
ba: aa02 add r2, sp, #8
bc: 9303 str r3, [sp, #12]
be: 9301 str r3, [sp, #4]
c0: 4b0e ldr r3, [pc, #56] ; (fc <compare_str_misaligned+0x48>)
c2: 7912 ldrb r2, [r2, #4]
c4: 7819 ldrb r1, [r3, #0]
c6: 4291 cmp r1, r2
c8: d004 beq.n d4 <compare_str_misaligned+0x20>
ca: 2001 movs r0, #1
cc: 1e43 subs r3, r0, #1
ce: 4198 sbcs r0, r3
d0: b004 add sp, #16
d2: 4770 bx lr
d4: aa02 add r2, sp, #8
d6: 7859 ldrb r1, [r3, #1]
d8: 7952 ldrb r2, [r2, #5]
da: 4291 cmp r1, r2
dc: d1f5 bne.n ca <compare_str_misaligned+0x16>
de: aa02 add r2, sp, #8
e0: 7899 ldrb r1, [r3, #2]
e2: 7992 ldrb r2, [r2, #6]
e4: 4291 cmp r1, r2
e6: d1f0 bne.n ca <compare_str_misaligned+0x16>
e8: 78da ldrb r2, [r3, #3]
ea: ab02 add r3, sp, #8
ec: 79db ldrb r3, [r3, #7]
ee: 2000 movs r0, #0
f0: 429a cmp r2, r3
f2: d1ea bne.n ca <compare_str_misaligned+0x16>
f4: e7ea b.n cc <compare_str_misaligned+0x18>
f6: 46c0 nop ; (mov r8, r8)
f8: 0000 movs r0, r0
f8: R_ARM_ABS32 .rodata
fa: 0000 movs r0, r0
fc: 0001 movs r1, r0
fc: R_ARM_ABS32 target
00000000 <.rodata>:
0: 3130 adds r1, #48 ; 0x30
2: 3332 adds r3, #50 ; 0x32
b6: b084 sub sp, #16
栈指针SP下移16字节。
b4: 4b10 ldr r3, [pc, #64] ; f8
b8: 681b ldr r3, [r3, #0]
bc: 9303 str r3, [sp, #12]
be: 9301 str r3, [sp, #4]
f8: 0000 R_ARM_ABS32 .rodata
00000000 <.rodata>:
0: 3130
2: 3332
将参考字符串读入寄存器R3。然后入栈,地址-4 = SP + 12和-12 = SP + 4。
我不理解为啥非要创建两份参考字符串的副本。
c0: 4b0e ldr r3, [pc, #56] ; fc
fc: 0001 R_ARM_ABS32 target
将未对齐的目标字符串的地址读入寄存器R3。注意相较于对齐的目标字符串位移0001,即a1[+1]。
ba: aa02 add r2, sp, #8
c2: 7912 ldrb r2, [r2, #4]
c4: 7819 ldrb r1, [r3, #0]
c6: 4291 cmp r1, r2
c8: d004 beq.n d4
创建框架指针R2,指向-8 = SP + 8。
将目标字符串的第0个字符读入寄存器R1,将参考字符串的第0个字符从栈地址-4 = R2 + 4读入寄存器R2。然后,进行比较。
如果相等,继续执行步骤d4。
为什么要创建框架指针R2。我们本可以使用ldrb r2, [sp, #12]。
ca: 2001 movs r0, #1
cc: 1e43 subs r3, r0, #1
ce: 4198 sbcs r0, r3
d0: b004 add sp, #16
d2: 4770 bx lr
如果相等则返回0,否则返回1:
如果相等,进入点为步骤cc, 寄存器R0已在第ee步骤预设为0:
cc: r3 = 0 - 1 = -1, 进位 = 1
ce: r0 = 0 - (-1) - 1 = 0
如果不相等,进入点为步骤ca:
ca: r0 = 1
cc: r3 = 1 - 1 = 0, 进位 = 0
ce: r0 = 1 - 0 - 0 = 1
复原栈并返回寄存器R0的值。
为何不直接返回?
d4: aa02 add r2, sp, #8
d6: 7859 ldrb r1, [r3, #1]
d8: 7952 ldrb r2, [r2, #5]
da: 4291 cmp r1, r2
dc: d1f5 bne.n ca
创建框架指针R2,指向-8 = SP + 8。
将目标字符串的第1个字符读入寄存器R1,将参考字符串的第1个字符从栈地址-3 = R2 + 5读入寄存器R2。然后,进行比较。
如果相等,继续。否则,跳转步骤ca。
de: aa02 add r2, sp, #8
e0: 7899 ldrb r1, [r3, #2]
e2: 7992 ldrb r2, [r2, #6]
e4: 4291 cmp r1, r2
e6: d1f0 bne.n ca
创建框架指针R2,指向-8 = SP + 8。
将目标字符串的第2个字符读入寄存器R1,将参考字符串的第2个字符从栈地址-2 = R2 + 6读入寄存器R2。然后,进行比较。
如果相等,继续。否则,跳转步骤ca。
e8: 78da ldrb r2, [r3, #3]
ea: ab02 add r3, sp, #8
ec: 79db ldrb r3, [r3, #7]
ee: 2000 movs r0, #0
f0: 429a cmp r2, r3
f2: d1ea bne.n ca
f4: e7ea b.n cc
创建框架指针R3,指向-8 = SP + 8。
将目标字符串的第3个字符读入寄存器R2,将参考字符串的第3个字符从栈地址-1 = R2 + 7读入寄存器R3。然后,进行比较。
如果相等,清零寄存器R0为0并跳转步骤cc。否则,跳转步骤ca。
ARM调用memcmp()比较外部(未知对齐)字符串
以下为原始机器码:
00000100 <compare_str_unknown>:
100: 4b10 ldr r3, [pc, #64] ; (144 <compare_str_unknown+0x44>)
102: b084 sub sp, #16
104: 681b ldr r3, [r3, #0]
106: 4a10 ldr r2, [pc, #64] ; (148 <compare_str_unknown+0x48>)
108: 9301 str r3, [sp, #4]
10a: 9303 str r3, [sp, #12]
10c: 466b mov r3, sp
10e: 7811 ldrb r1, [r2, #0]
110: 791b ldrb r3, [r3, #4]
112: 4299 cmp r1, r3
114: d004 beq.n 120 <compare_str_unknown+0x20>
116: 2001 movs r0, #1
118: 1e43 subs r3, r0, #1
11a: 4198 sbcs r0, r3
11c: b004 add sp, #16
11e: 4770 bx lr
120: ab02 add r3, sp, #8
122: 7851 ldrb r1, [r2, #1]
124: 795b ldrb r3, [r3, #5]
126: 4299 cmp r1, r3
128: d1f5 bne.n 116 <compare_str_unknown+0x16>
12a: ab02 add r3, sp, #8
12c: 7891 ldrb r1, [r2, #2]
12e: 799b ldrb r3, [r3, #6]
130: 4299 cmp r1, r3
132: d1f0 bne.n 116 <compare_str_unknown+0x16>
134: ab02 add r3, sp, #8
136: 78d2 ldrb r2, [r2, #3]
138: 79db ldrb r3, [r3, #7]
13a: 2000 movs r0, #0
13c: 429a cmp r2, r3
13e: d1ea bne.n 116 <compare_str_unknown+0x16>
140: e7ea b.n 118 <compare_str_unknown+0x18>
142: 46c0 nop ; (mov r8, r8)
...
144: R_ARM_ABS32 .rodata
148: R_ARM_ABS32 target_ext
00000000 <.rodata>:
0: 3130 adds r1, #48 ; 0x30
2: 3332 adds r3, #50 ; 0x32
102: b084 sub sp, #16
栈指针SP下移16字节。
100: 4b10 ldr r3, [pc, #64] ; 144
104: 681b ldr r3, [r3, #0]
108: 9301 str r3, [sp, #4]
10a: 9303 str r3, [sp, #12]
144: R_ARM_ABS32 .rodata
00000000 <.rodata>:
0: 3130
2: 3332
将参考字符串读入寄存器R3。然后入栈,地址-4 = SP + 12和-12 = SP + 4。
同样。栈内两份副本。
106: 4a10 ldr r2, [pc, #64] ; 148
148: R_ARM_ABS32 target_ext
将外部目标字符串的地址读入寄存器R2。
10c: 466b mov r3, sp
10e: 7811 ldrb r1, [r2, #0]
110: 791b ldrb r3, [r3, #4]
112: 4299 cmp r1, r3
114: d004 beq.n 120
创建框架指针R3,指向-16 = SP + 0。
将目标字符串的第0个字符读入寄存器R1,将参考字符串的第0个字符从栈地址-12 = R3 + 4读入寄存器R3。然后,进行比较。
如果相等,继续执行步骤120。
同样。为什么要创建框架指针R3。我们本可以使用ldrb r3, [sp, #4]。
116: 2001 movs r0, #1
118: 1e43 subs r3, r0, #1
11a: 4198 sbcs r0, r3
11c: b004 add sp, #16
11e: 4770 bx lr
如果相等则返回0,否则返回1,使用同上一个ARM例子相同的算法。
复原栈并返回寄存器R0的值。
同样。为何不直接返回?
120: ab02 add r3, sp, #8
122: 7851 ldrb r1, [r2, #1]
124: 795b ldrb r3, [r3, #5]
126: 4299 cmp r1, r3
128: d1f5 bne.n 116
创建框架指针R3,指向-8 = SP + 8。
将目标字符串的第1个字符读入寄存器R1,将参考字符串的第1个字符从栈地址-3 = R3 + 5读入寄存器R3。然后,进行比较。
如果相等,继续。否则,跳转步骤116。
12a: ab02 add r3, sp, #8
12c: 7891 ldrb r1, [r2, #2]
12e: 799b ldrb r3, [r3, #6]
130: 4299 cmp r1, r3
132: d1f0 bne.n 116
创建框架指针R3,指向-8 = SP + 8。
将目标字符串的第2个字符读入寄存器R1,将参考字符串的第2个字符从栈地址-2 = R3 + 6读入寄存器R3。然后,进行比较。
如果相等,继续。否则,跳转步骤116。
134: ab02 add r3, sp, #8
136: 78d2 ldrb r2, [r2, #3]
138: 79db ldrb r3, [r3, #7]
13a: 2000 movs r0, #0
13c: 429a cmp r2, r3
13e: d1ea bne.n 116
140: e7ea b.n 118
创建框架指针R3,指向-8 = SP + 8。
将目标字符串的第3个字符读入寄存器R2,将参考字符串的第3个字符从栈地址-1 = R3 + 7读入寄存器R3。然后,进行比较。
如果相等,清零寄存器R0为0并跳转步骤118。否则,跳转步骤116。
ARM上调用memcpy()时,GCC对非对齐和外部字符串生成的机器码非常相似。通向的算法,同样的代码体积,同样的流程,以及同样的问题。
Xtensa对已知长度调用memcmp()
Xtensa调用memcmp()比较对齐字的符串
以下为原始机器码:
000000dc <compare_str_aligned>:
dc: 006136 entry a1, 48
df: 000081 l32r a8, fffc00e0 <compare_str_prototype+0xfffbff5c>
df: R_XTENSA_SLOT0_OP .literal+0x18
e2: 0a0c movi.n a10, 0
e4: 0898 l32i.n a9, a8, 0
e6: 000081 l32r a8, fffc00e8 <compare_str_prototype+0xfffbff64>
e6: R_XTENSA_SLOT0_OP .literal+0x1c
e9: 120c movi.n a2, 1
eb: 0020c0 memw
ee: 0888 l32i.n a8, a8, 0
f0: 0199 s32i.n a9, a1, 0
f2: c08890 sub a8, a8, a9
f5: 832a80 moveqz a2, a10, a8
f8: f01d retw.n
00000000 <.literal>:
...
0: R_XTENSA_32 target
4: R_XTENSA_32 target
8: R_XTENSA_32 target
c: cccdced0
10: 00000000
10: R_XTENSA_32 target
14: cccdced0
...
18: R_XTENSA_32 .rodata
1c: R_XTENSA_32 target
20: R_XTENSA_32 .rodata
24: R_XTENSA_32 target+0x1
28: R_XTENSA_32 .rodata
2c: R_XTENSA_32 target_ext
30: R_XTENSA_32 .rodata
34: R_XTENSA_32 target
Disassembly of section .rodata:
00000000 <.rodata>:
0: 323130 orbc b3, b1, b3
3: 33 .byte 0x33
dc: 006136 entry a1, 48
将窗口移动48字节以保存母函数的寄存器并创建栈空间。
df: 000081 l32r a8, fffc00e0
df: R_XTENSA_SLOT0_OP .literal+0x18
e4: 0898 l32i.n a9, a8, 0
f0: 0199 s32i.n a9, a1, 0
00000000 <.literal>:
18: R_XTENSA_32 .rodata
00000000 <.rodata>:
0: 323130
3: 33
将参考字符串读入寄存器A9。然后入栈,地址-48 = A1 + 0(相较于母函数的栈)。
然而,栈内的参考字符串并未被使用,而是直接直接使用寄存器A9中的原始副本进行比较。
我也不知道为啥不用栈还要去创建。也许是因为memcmp()函数要求输入缓冲必须在数据内存中,即栈堆而非文本。
e6: 000081 l32r a8, fffc00e8
e6: R_XTENSA_SLOT0_OP .literal+0x1c
eb: 0020c0 memw
ee: 0888 l32i.n a8, a8, 0
00000000 <.literal>:
1c: R_XTENSA_32 target
将目标字符串以32比特数据整体读入寄存器A8。
e2: 0a0c movi.n a10, 0
e9: 120c movi.n a2, 1
f2: c08890 sub a8, a8, a9
f5: 832a80 moveqz a2, a10, a8
f8: f01d retw.n
通过相减,比较目标字符串与参考字符串的第0个词(包含4个字符)。
如果相等(寄存器A8为零),设置寄存器A2为寄存器A10(值为0)。否则,寄存器A2不变(值为1)。返回寄存器 A2的值并复原窗口。
On Xtensa, the inlined memcmp() function compares the string as a whole data unit when the string is aligned.
However, when using memcmp(), it requires 48 bytes of stack (window), that is 16 bytes more than without using memcmp().
Xtensa调用memcmp()比较未对齐的字符串
以下为原始机器码:
000000fc <compare_str_misaligned>:
fc: 006136 entry a1, 48
ff: 000081 l32r a8, fffc0100 <compare_str_prototype+0xfffbff7c>
ff: R_XTENSA_SLOT0_OP .literal+0x20
102: 000091 l32r a9, fffc0104 <compare_str_prototype+0xfffbff80>
102: R_XTENSA_SLOT0_OP .literal+0x24
105: 08a8 l32i.n a10, a8, 0
107: 0020c0 memw
10a: 000982 l8ui a8, a9, 0
10d: 0061a2 s32i a10, a1, 0
110: 0001a2 l8ui a10, a1, 0
113: 748080 extui a8, a8, 0, 8
116: 2e98a7 bne a8, a10, 148 <compare_str_misaligned+0x4c>
116: R_XTENSA_SLOT0_OP .text+0x148
119: 0020c0 memw
11c: 010982 l8ui a8, a9, 1
11f: 0101a2 l8ui a10, a1, 1
122: 748080 extui a8, a8, 0, 8
125: 1f98a7 bne a8, a10, 148 <compare_str_misaligned+0x4c>
125: R_XTENSA_SLOT0_OP .text+0x148
128: 0020c0 memw
12b: 020982 l8ui a8, a9, 2
12e: 0201a2 l8ui a10, a1, 2
131: 748080 extui a8, a8, 0, 8
134: 1098a7 bne a8, a10, 148 <compare_str_misaligned+0x4c>
134: R_XTENSA_SLOT0_OP .text+0x148
137: 0020c0 memw
13a: 030982 l8ui a8, a9, 3
13d: 030192 l8ui a9, a1, 3
140: 748080 extui a8, a8, 0, 8
143: 020c movi.n a2, 0
145: 011897 beq a8, a9, 14a <compare_str_misaligned+0x4e>
145: R_XTENSA_SLOT0_OP .text+0x14a
148: 120c movi.n a2, 1
14a: f01d retw.n
00000000 <.literal>:
...
0: R_XTENSA_32 target
4: R_XTENSA_32 target
8: R_XTENSA_32 target
c: cccdced0
10: 00000000
10: R_XTENSA_32 target
14: cccdced0
...
18: R_XTENSA_32 .rodata
1c: R_XTENSA_32 target
20: R_XTENSA_32 .rodata
24: R_XTENSA_32 target+0x1
28: R_XTENSA_32 .rodata
2c: R_XTENSA_32 target_ext
30: R_XTENSA_32 .rodata
34: R_XTENSA_32 target
Disassembly of section .rodata:
00000000 <.rodata>:
0: 323130 orbc b3, b1, b3
3: 33 .byte 0x33
fc: 006136 entry a1, 48
将窗口移动48字节以保存母函数的寄存器并创建栈空间。
ff: 000081 l32r a8, fffc0100
ff: R_XTENSA_SLOT0_OP .literal+0x20
102: 000091 l32r a9, fffc0104
102: R_XTENSA_SLOT0_OP .literal+0x24
105: 08a8 l32i.n a10, a8, 0
10d: 0061a2 s32i a10, a1, 0
00000000 <.literal>:
20: R_XTENSA_32 .rodata
24: R_XTENSA_32 target+0x1
00000000 <.rodata>:
0: 323130
3: 33
将未对齐的目标字符串的地址读入寄存器A9。注意非对齐的目标字符串的地址为对齐的目标字符串的地址+1。
将参考字符串读入寄存器A10。然后入栈,地址-48 = A1 + 0。
107: 0020c0 memw
10a: 000982 l8ui a8, a9, 0
110: 0001a2 l8ui a10, a1, 0
113: 748080 extui a8, a8, 0, 8
116: 2e98a7 bne a8, a10, 148
将参考字符串的第0个字符从栈地址-48 = A1 + 0读入寄存器A10,将目标字符串的第0个字符读入寄存器A8并扩展,然后比较。
如果相等,继续。否则,跳转步骤148。
119: 0020c0 memw
11c: 010982 l8ui a8, a9, 1
11f: 0101a2 l8ui a10, a1, 1
122: 748080 extui a8, a8, 0, 8
125: 1f98a7 bne a8, a10, 148
将参考字符串的第1个字符从栈地址-47 = A1 + 1读入寄存器A10,将目标字符串的第1个字符读入寄存器A8并扩展,然后比较。
如果相等,继续。否则,跳转步骤148。
128: 0020c0 memw
12b: 020982 l8ui a8, a9, 2
12e: 0201a2 l8ui a10, a1, 2
131: 748080 extui a8, a8, 0, 8
134: 1098a7 bne a8, a10, 148
将参考字符串的第2个字符从栈地址-46 = A1 + 2读入寄存器A10,将目标字符串的第2个字符读入寄存器A8并扩展,然后比较。
如果相等,继续。否则,跳转步骤148。
137: 0020c0 memw
13a: 030982 l8ui a8, a9, 3
13d: 030192 l8ui a9, a1, 3
140: 748080 extui a8, a8, 0, 8
143: 020c movi.n a2, 0
145: 011897 beq a8, a9, 14a
148: 120c movi.n a2, 1
14a: f01d retw.n
清空寄存器A2,表示相等。
将参考字符串的第3个字符从栈地址-45 = A1 + 3读入寄存器A9,将目标字符串的第3个字符读入寄存器A8并扩展,然后比较。
如果相等,继续执行步骤14a以返回寄存器A2的值(为0)。否则,设置寄存器A2为1并返回。同时,复原窗口。
Xtensa上比较后确定返回值的机器码要比ARM直接很多。
Xtensa调用memcmp()比较外部(未知对齐)字符串
以下为原始机器码:
0000014c <compare_str_unknown>:
14c: 006136 entry a1, 48
14f: 000081 l32r a8, fffc0150 <compare_str_prototype+0xfffbffcc>
14f: R_XTENSA_SLOT0_OP .literal+0x28
152: 000091 l32r a9, fffc0154 <compare_str_prototype+0xfffbffd0>
152: R_XTENSA_SLOT0_OP .literal+0x2c
155: 0888 l32i.n a8, a8, 0
157: 0009a2 l8ui a10, a9, 0
15a: 006182 s32i a8, a1, 0
15d: 748080 extui a8, a8, 0, 8
160: 1c9a87 bne a10, a8, 180 <compare_str_unknown+0x34>
160: R_XTENSA_SLOT0_OP .text+0x180
163: 0109a2 l8ui a10, a9, 1
166: 010182 l8ui a8, a1, 1
169: 139a87 bne a10, a8, 180 <compare_str_unknown+0x34>
169: R_XTENSA_SLOT0_OP .text+0x180
16c: 0209a2 l8ui a10, a9, 2
16f: 020182 l8ui a8, a1, 2
172: 0a9a87 bne a10, a8, 180 <compare_str_unknown+0x34>
172: R_XTENSA_SLOT0_OP .text+0x180
175: 030992 l8ui a9, a9, 3
178: 030182 l8ui a8, a1, 3
17b: 020c movi.n a2, 0
17d: 011987 beq a9, a8, 182 <compare_str_unknown+0x36>
17d: R_XTENSA_SLOT0_OP .text+0x182
180: 120c movi.n a2, 1
182: f01d retw.n
00000000 <.literal>:
...
0: R_XTENSA_32 target
4: R_XTENSA_32 target
8: R_XTENSA_32 target
c: cccdced0
10: 00000000
10: R_XTENSA_32 target
14: cccdced0
...
18: R_XTENSA_32 .rodata
1c: R_XTENSA_32 target
20: R_XTENSA_32 .rodata
24: R_XTENSA_32 target+0x1
28: R_XTENSA_32 .rodata
2c: R_XTENSA_32 target_ext
30: R_XTENSA_32 .rodata
34: R_XTENSA_32 target
14c: 006136 entry a1, 48
将窗口移动48字节以保存母函数的寄存器并创建栈空间。
14f: 000081 l32r a8, fffc0150
14f: R_XTENSA_SLOT0_OP .literal+0x28
152: 000091 l32r a9, fffc0154
152: R_XTENSA_SLOT0_OP .literal+0x2c
155: 0888 l32i.n a8, a8, 0
15a: 006182 s32i a8, a1, 0
00000000 <.literal>:
28: R_XTENSA_32 .rodata
2c: R_XTENSA_32 target_ext
00000000 <.rodata>:
0: 323130
3: 33
将外部目标字符串的地址读入寄存器A9。
将参考字符串读入寄存器A8。然后入栈,地址-48 = A1 + 0。
157: 0009a2 l8ui a10, a9, 0
15d: 748080 extui a8, a8, 0, 8
160: 1c9a87 bne a10, a8, 180
扩展寄存器A8中的参考字符串的低8比特以只保留第0个字符,将目标字符串的第0个字符读入寄存器A10,然后比较。
如果相等,继续。否则,跳转步骤180。
163: 0109a2 l8ui a10, a9, 1
166: 010182 l8ui a8, a1, 1
169: 139a87 bne a10, a8, 180
将参考字符串的第1个字符从栈地址-47 = A1 + 1读入寄存器A8,将目标字符串的第1个字符读入寄存器A10,然后比较。
如果相等,继续。否则,跳转步骤180。
16c: 0209a2 l8ui a10, a9, 2
16f: 020182 l8ui a8, a1, 2
172: 0a9a87 bne a10, a8, 180
将参考字符串的第2个字符从栈地址-46 = A1 + 2读入寄存器A8,将目标字符串的第2个字符读入寄存器A10,然后比较。
如果相等,继续。否则,跳转步骤180。
175: 030992 l8ui a9, a9, 3
178: 030182 l8ui a8, a1, 3
17b: 020c movi.n a2, 0
17d: 011987 beq a9, a8, 182
180: 120c movi.n a2, 1
182: f01d retw.n
清空寄存器A2,表示相等。
将参考字符串的第3个字符从栈地址-45 = A1 + 3读入寄存器A8,将目标字符串的第3个字符读入寄存器A9,然后比较。
如果相等,继续执行步骤182以返回寄存器A2的值(为0)。否则,设置寄存器A2为1并返回。同时,复原窗口。
当目标字符串在外部时,没有使用内存等待memw,也没有扩展标字符串的字符extui a_target, a_target, 0, 8。
memcmp()性能
通过前面的示例,可见当字符串对齐或CPU支持非对齐读取时,GCC倾向于使用整体比较来实现memcpy()函数,以达到最好的性能。如果没有对齐,或是CPU不支持非对齐读取,memcpy()函数将必须逐字读取并比较。
另外,与(*((multibyte_t*)a1) == *((multibyte_t*)((char[]){'0', '1', '2', '3'})))这种写法相比,使用memcpy()会造成更高的内存占用(分配栈),即使栈内的数据并未被使用。
| 在第N个字符发现不同 | 0(操作) | 1(操作) | 2(操作) | 3(操作) | Equal(操作) | Average(操作) | 代码体积(字节) | 栈使用(字节) |
|---|---|---|---|---|---|---|---|---|
| X86 - 对齐 | 6 | 6 | 6 | 6 | 6 | 6 | 25 | 4 |
| X86 - 未对齐 | 7 | 7 | 7 | 7 | 7 | 7 | 25 | 4 |
| X86 - 外部 | 4 | 4 | 4 | 4 | 4 | 4 | 16 | 0 |
| ARM - 对齐 | 11 | 11 | 11 | 11 | 11 | 11 | 22 | 8 |
| ARM - 未对齐 | 16 | 21 | 26 | 32 | 32 | 25.4 | 66 | 16 |
| ARM - 外部 | 16 | 21 | 26 | 32 | 32 | 25.4 | 66 | 16 |
| Xtensa - 对齐 | 12 | 12 | 12 | 12 | 12 | 12 | 30 | +16 |
| Xtensa - 未对齐 | 12 | 17 | 22 | 28 | 27 | 21.2 | 80 | +16 |
| Xtensa - 外部 | 10 | 13 | 16 | 20 | 19 | 15.6 | 56 | +16 |
总的来说,当使用memcpy()时GCC生成的机器码:
-
消耗更多的数据内存(X86和ARM上为栈,Xtensa上为窗口)。参考字符串必须入栈(ARM上还会入两次),即使有时栈内副本并未被使用。
-
消耗更多的程序内存(文本体积)。
-
让人费解且低效的机器码。
但是,使用memcpy()也有好处:
-
数据长度不限。
-
源码更易读。