X86、ARM与Xtensa上微优化字符串比较

这篇文章讨论在X86、ARM与Xtensa上,当字符串对齐与未对齐时,对于逐字比较、整体比较与调用memcmp()函数比较字符串时GCC生成的机器码的不同。文章将会分析不同情况下GCC生成的机器码的体积、操作量和内存占用以探讨性能。

--by Captdam @ Sep 27, 2026

Index

我在嵌入式系统上开发一个轻量级网站服务器时想到了这个问题:怎样更快处理文字?

比如说,以下代码展示了服务器收到TCP包后检测HTTP请求方法放入例子:


if (http_request_header[0:3] == "GET ") {
	get_request();
} else if (http_request_header[0:3] == "POST") {
	post_request();
} else {
	bad_request();
}
	

其中:

我同意很多人都觉得微优化有点矫枉过正,应该把更多精力放在影响更大的项目上。因为我的背景是电子与计算机工程,我对CPU的工作方式和编译器的生成结果更感兴趣。因此,我决定做一些微优化的实验,并把我的发现放在这。

在这篇文章中,我将测试在以下三种平台上比较字符串:

我将使用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比特无符号整数:

  1. (char[]){'0', '1', '2', '3'} - 指向内联字符串的指针:4字符,各8比特。

  2. (uint32_t*)((char[]){'0', '1', '2', '3'}) - 该指针转换为32比特无符号整数的指针。

  3. *((uint32_t*)((char[]){'0', '1', '2', '3'})) - 以32比特无符号整数的方式从该指针取值。

这个写法将参考字符串:

  1. 变成一个只需一次比较的单一数据单位,而不是一个需要sizeof(array) / sizeof(array[0])次比较的数组。

  2. 嵌入程序文本内存中,免除分配空间并复制到数据内存中的需求。

在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必须逐字读取,然后再通过位移和比特或来构建数据单位,之后才能比较。

性能总结

操作数与代码体积 - 4字节数据
在第N个字符发现不同 0(操作)1(操作)2(操作)3(操作)相等(操作)平均(操作) 代码体积(字节)
X86 - 逐字 - 对齐 5811151510.8 64
X86 - 整体 - 对齐 44444 (+73%)4 (+63%) 16 (+75%)
X86 - 逐字 - 未对齐 5811151510.8 64
X86 - 整体 - 未对齐 44444 (+73%)4 (+63%) 16 (+75%)
ARM - 逐字 - 对齐 6912171712.2 34
ARM - 整体 - 对齐 88888 (+53%)8 (+34%) 16 (+53%)
ARM - 逐字 - 未对齐 6912171712.2 34
ARM - 整体 - 未对齐 1818181818 (-6%)18 (-48%) 36 (-6%)
Xtensa - 逐字 - 对齐 91419252518.4 70
Xtensa - 整体 - 对齐 99999 (+64%)9 (+51%) 22 (+68%)
Xtensa - 逐字 - 未对齐 91419252518.4 70
Xtensa - 整体 - 未对齐 1818181818 (+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;
}
	

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()函数总是整体比较字符串。

对于栈使用:

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()会造成更高的内存占用(分配栈),即使栈内的数据并未被使用。

操作数与代码体积与栈使用 - 4字节数据
在第N个字符发现不同 0(操作)1(操作)2(操作)3(操作)Equal(操作)Average(操作) 代码体积(字节)栈使用(字节)
X86 - 对齐 666666 254
X86 - 未对齐 777777 254
X86 - 外部 444444 160
ARM - 对齐 111111111111 228
ARM - 未对齐 162126323225.4 6616
ARM - 外部 162126323225.4 6616
Xtensa - 对齐 121212121212 30+16
Xtensa - 未对齐 121722282721.2 80+16
Xtensa - 外部 101316201915.6 56+16

总的来说,当使用memcpy()时GCC生成的机器码:

但是,使用memcpy()也有好处: