String Comparison Micro-optimization on X86, ARM and Xtensa

This article discusses the difference in GCC generated machine code between comparing a string character by character, as a whole multi-byte data unit, and using memcmp() function on X86, ARM and Xtensa for both aligned and misaligned strings. It analyzes the machine code generated by GCC, shows the code size, operation count and memory usage in different senorials.

--by Captdam @ Sep 27, 2026

Index

This question came into my head when I was developing a light weight web server on an embedded system: how to process text fast?

For example, the following code shows checking the HTTP request method of a TCP packet received by the server:


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

where:

Some may think micro-optimization is over engineering. I do agree with that, I should put more effort into other optimizations that bring significant outcomes. My background from electrical and computer engineering makes me interested in how the CPU works and how the compiler generates the instructions. Therefore, I decided to do some experiments about these micro-optimizations and share my findings here.

In this article, I will test string comparison on three differnet platforms:

I will use GCC to compile C language code, optimization level set to -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)
	

Aligned String Comparison

Compare Aligned String Character by Character

As we know, a character is 8 bits wide, which can be represented by a byte (char). 4 characters are 32 bits wide, and can be represented by 4 bytes, or a 32-bit word. If we compare the target string character by character, there will be 4 comparisons. Consider the following C language code:


#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
}
	

Let's compile this code for X86, ARM and Xtensa, then disassemble:


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 Compares Aligned String Character by Character

Following shows orginal machine code:

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
		

Preset return value in register A to 1 which is for not-equal.


   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
		

Load the 0th character from the target string into register D, then compare with the 0th character '0'(0x30) in the reference string (hardcoded in instruction).

If equal, continue to step 18; otherwise, return with value 1 preset in register A.


  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
		

Load the 1st character from the target string into register D, then compare with the 1st character '1'(0x31) in the reference string (hardcoded in instruction).

If equal, continue; otherwise, go to step 11 to return with value 1 preset in register A.


  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
		

Load the 2nd character from the target string into register D, then compare with the 2nd character '2'(0x32) in the reference string (hardcoded in instruction).

If equal, continue; otherwise, go to step 11 to return with value 1 preset in register A.


  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  
		

Load the 3rd character from the target string into register A, then compare with the 3rd character '3'(0x33) in the reference string (hardcoded in instruction).

If equal, clear register A to 0; otherwise, set register A to 1. Extend register A and return.

ARM Compares Aligned String Character by Character

Following shows orginal machine code:

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
		

Preset return value in register R0 to 1 which is for not-equal.


   0:	4b08      	ldr	r3, [pc, #32]	; 24
   24:	0000      	R_ARM_ABS32	target
		

Load the address of the target string into register R3.


   4:	781a      	ldrb	r2, [r3, #0]
   6:	2a30      	cmp	r2, #48	; 0x30
   8:	d000      	beq.n	c
   a:	4770      	bx	lr
		

Load the 0th character from the target string into register R2, then compare with the 0th character '0'(0x30) in the reference string (hardcoded in instruction).

If equal, continue to step c; otherwise, return with value 1 preset in register R0.


   c:	785a      	ldrb	r2, [r3, #1]
   e:	2a31      	cmp	r2, #49	; 0x31
  10:	d1fb      	bne.n	a
		

Load the 1st character from the target string into register R2, then compare with the 1st character '1'(0x31) in the reference string (hardcoded in instruction).

If equal, continue; otherwise, go to step a to return with value 1 preset in register R0.


  12:	789a      	ldrb	r2, [r3, #2]
  14:	2a32      	cmp	r2, #50	; 0x32
  16:	d1f8      	bne.n	a
		

Load the 2nd character from the target string into register R2, then compare with the 2nd character '2'(0x32) in the reference string (hardcoded in instruction).

If equal, continue; otherwise, go to step a to return with value 1 preset in register R0.


  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
		

Load the 3rd character from the target string into register R0, then compare with the 3rd character '3'(0x33) in the reference string (hardcoded in instruction).

If equal:


  1a:	r0 = '3' - 0x33 = 0
  1c:	r3 = 0 - 1 = -1, carry = 1
  1e:	r0 = 0 - (-1) - 1 = 0
			

If not equal:


  1a:	r0 = any(!'3') - 0x33 = some
  1c:	r3 = some - 1, carry = 0 ; For X - 1 to set carry, X must be 1.
  1e:	r0 = some - (some - 1) - 0 = 1
			

Return with value in register R0.

Xtensa Compares Aligned String Character by Character

Following shows orginal machine code:

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
		

Move the window for 32 bytes to preserve parent registers. This is equivalent to allocate 32 bytes of stack and push registers into it.


   e:	120c      	movi.n	a2, 1
		

Preset return value in register A2 to 1 which is for not-equal.


   3:	000091        	l32r	a9, fffc0004
			3: R_XTENSA_SLOT0_OP	.literal
00000000 <.literal>:
			0: R_XTENSA_32	target
		

Load the address of the target string into register 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
		

Load and extend the 0th character from the target string into register A8, load the 0th character from the reference string (u32_t)'0'(48) (hardcoded in instruction) into register A10, then compare.

If equal, continue; otherwise, go to step 44 to return with value 1 preset in register A2.


  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
		

Load and extend the 1st character from the target string into register A8, load the 1st character from the reference string (u32_t)'1'(49) (hardcoded in instruction) into register A10, then compare.

If equal, continue; otherwise, go to step 44 to return with value 1 preset in register A2.


  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
		

Load and extend the 2nd character from the target string into register A8, load the 2nd character from the reference string (u32_t)'2'(50) (hardcoded in instruction) into register A10, then compare.

If equal, continue; otherwise, go to step 44 to return with value 1 preset in register A2.


  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
		

Load and extend the 3rd character from the target string into register A8, then add with the negative of the 3rd character from the reference string (u32_t)'3'(51) (hardcoded in instruction).

If equal (add yields zero in register A8), set register A2 to register A9 (value is 0); otherwise, register A2 unchanged (was 1). Return with value in register A2, restore the window.

As we can see, on all three platforms, the machine code generated by GCC follows the same algorithm:

Compare Aligned String as a Whole Data Unit

If we compare the target string as a whole 32-bit word (which is 1 data unit for 32-bit CPU), only 1 comparison is required. Consider the following C language code:


int compare_u32_aligned() {
	return (*((uint32_t*)target) == *((uint32_t*)((char[]){'0', '1', '2', '3'}))) ? 0 : 1;
}
	

In this example, we cast the 4-byte long reference string into a single 32-bit unsigned integer:

  1. (char[]){'0', '1', '2', '3'} - Pointer to an inline character array: 4 characters, each 8-bit wide.

  2. (uint32_t*)((char[]){'0', '1', '2', '3'}) - Cast it into a pointer for 32-bit unsigned integer.

  3. *((uint32_t*)((char[]){'0', '1', '2', '3'})) - Get the value this pointer as a 32-bit unsigned integer.

This shortcut embeds the reference string:

  1. Into a single data unit that requires only a comparison, instead of an array that requires sizeof(array) / sizeof(array[0]) comparisons.

  2. Into the program text memory, no need to allocate and copy it into the data memory.

On CPU hardware-level, there is no data type. Casting the pointer type in C language does not change the pointed value.

X86 Compares Aligned String as a Whole Data Unit

Following shows orginal machine code:

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
		

Clear return register A.


  82:	81 3d 00 00 00 00 30 31 32 33 	cmpl   $0x33323130,0x0(%rip)
			84: R_X86_64_PC32	target-0x8
		

Compare the 0th word (4 characters) in the target string with the reference string "3210"(0x33323130). Note: first character in LSB.


  8c:	0f 95 c0             	setne  %al
  8f:	c3                   	retq  
		

If equal, clear register A to 0; otherwise, set register A to 1. Then, return with value in register A.

ARM Compares Aligned String as a Whole Data Unit

Following shows orginal machine code:

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
		

Load the address of the target string into register R3. Then, load its 0th word (4 characters) into register 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
		

Load the address of the reference string into register R3. Then, load its 0th word (4 characters) into register R3.


  58:	1ac0      	subs	r0, r0, r3
  5a:	1e43      	subs	r3, r0, #1
  5c:	4198      	sbcs	r0, r3
  5e:	4770      	bx	lr
		

Compare the 0th word (4 characters) between the target string and the reference string by subtract them.

If equal:


  58:	r0 = "3210" - 0x33323130 = 0
  5a:	r3 = 0 - 1 = -1, carry = 1
  5c:	r0 = 0 - (-1) - 1 = 0
			

If not equal:


  58:	r0 = any(!"3210") - 0x33323130 = some
  5a:	r3 = some - 1, carry = 0 ; For X - 1 to set carry, X must be 1.
  5c:	r0 = some - (some - 1) - 0 = 1
			

Return with value in register R0.

Xtensa Compares Aligned String as a Whole Data Unit

Following shows orginal machine code:

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
		

Move the window for 32 bytes to preserve parent registers.


  9b:	120c      	movi.n	a2, 1
		

Preset return value in register A2 to 1 which is for not-equal.


  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 	
		

Load the address of the target string into register A8. Then, load its 0th word (4 characters) into register A8.

Load the negative of the 0th word (4 characters) of the reference string into register A9.


  9d:	889a      	add.n	a8, a8, a9
  9f:	090c      	movi.n	a9, 0
  a1:	832980        	moveqz	a2, a9, a8
  a4:	f01d      	retw.n
		

Compare by add with the negative of the reference string.

If equal (add yields zero in register A8), set register A2 to register A9 (value is 0); otherwise, register A2 unchanged (was 1). Return with value in register A2, restore the window.

As we can see, on all three platforms, the machine code generated by GCC performs one read and one comparison only. Definitely, this is faster than the previous method. Furthermore, it is clear to say that comparing all 4 characters as a whole data unit has a smaller code size.

Misaligned String Comparison

In the previous example, the CPU reads 32-bit data from memory at a 32-bit aligned address. What if the read address is not 32-bit aligned?

To simulate a misaligned target string, we will add 1 to the address of the aligned target string, like target[1] or target + 1.

Compare Misaligned String Character by Character

Consider the following C language code:


int compare_char_misaligned() {
	return (target[1] == '0' && target[2] == '1' && target[3] == '2' && target[4] == '3') ? 0: 1;
}
	

On all three platforms, misalignment does not make any difference than comparing aligned memory when the comparison is performed character by character. All three platforms support byte read at any address.

Following shows how X86 compare misaligned string character by character:

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   
		
Following shows how ARM compare misaligned string character by character:

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
		
Following shows how Xtensa compare misaligned string character by character:

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
		

Compare Misaligned String as a Whole Data Unit

Consider the following C language code:


int compare_u32_misaligned() {
	return (*((uint32_t*)(target+1)) == *((uint32_t*)((char[]){'0', '1', '2', '3'}))) ? 0 : 1;
}
	

X86 Compares Misaligned String as a Whole Data Unit

Following shows orginal machine code:

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
		

Clear return register A.


  92:	81 3d 00 00 00 00 30 31 32 33 	cmpl   $0x33323130,0x0(%rip)
			94: R_X86_64_PC32	target-0x7
		

Compare the 0th word (4 characters) in the target string with the reference string "3210"(0x33323130).


  9c:	0f 95 c0             	setne  %al
  9f:	c3                   	retq  
		

If equal, clear register A to 0; otherwise, set register A to 1. Then, return with value in register A.

As we can see, X86 is capable of reading multibyte data from misaligned address. Therefore, misalignment does not make any difference in the machine code than comparing aligned memory when the comparison is performed as a whole data unit on X86. However, modern X86 CPUs can internally rewrite the given machine code into different micro code. That means, internally, the CPU may have to perform multiple aligned reads.

ARM Compares Misaligned String as a Whole Data Unit

Following shows orginal machine code:

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
		

Load the address of the aligned target string into register R3. Then, add 1 to simulate the address of misaligned target string, save it in register R2.

In other words, register R2 is the target string address, register R3 is the target string address - 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
		

Load the target string byte by byte, construct the 0th word (4 characters) in register R0:


  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
		

Load the address of the reference string into register R3. Then, load its 0th word (4 characters) into register R3.


  84:	1ac0      	subs	r0, r0, r3
  86:	1e43      	subs	r3, r0, #1
  88:	4198      	sbcs	r0, r3
  8a:	4770      	bx	lr
		

Compare the 0th word (4 characters) between the target string and the reference using the same algorithm as previous ARM example for aligned string as a whole data unit.

Return with value in register R0.

Misalignment causes substantial performance loss when comparing the string as a whole data unit on ARM. The CPU must read each byte individually, then construct them into one data unit by shifting and bitwise ORing, before it can be used for comparison.

Xtensa Compares Misaligned String as a Whole Data Unit

Following shows orginal machine code:

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
		

Move the window for 32 bytes to preserve parent registers.


  ae:	120c      	movi.n	a2, 1
		

Preset return value in register A2 to 1 which is for not-equal.


  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
		

Load the address of the aligned target string into register A8. This simulate the address of misaligned target string - 1.

Byte by byte, load the 1st to 4th character of the aligned target string, which is equivalent to the 0th to 3rd character of the misaligned target string. Construct the 0th word word (4 characters) in register A8:


  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
		

Load the negative of the 0th word (4 characters) of the reference string into register A9. Then, compare by add with the negative of the reference string.

If equal (add yields zero in register A8), set register A2 to register A9 (value is 0); otherwise, register A2 unchanged (was 1). Return with value in register A2, restore the window.

Similar to ARM, misalignment causes substantial performance loss when comparing the string as a whole data unit on Xtensa. The CPU must read each byte individually, then construct them into one data unit by shifting and bitwise ORing, before it can be used for comparison.

Performance Analysis

Operation Count and Code Size - 4-byte Data
Difference Found on Nth Character 0 (ops)1 (ops)2 (ops)3 (ops)Equal (ops)Average (ops) Code Size (bytes)
X86 - Character by Character - Aligned 5811151510.8 64
X86 - Whole Data Unit - Aligned 44444 (+73%)4 (+63%) 16 (+75%)
X86 - Character by Character - Misaligned 5811151510.8 64
X86 - Whole Data Unit - Misaligned 44444 (+73%)4 (+63%) 16 (+75%)
ARM - Character by Character - Aligned 6912171712.2 34
ARM - Whole Data Unit - Aligned 88888 (+53%)8 (+34%) 16 (+53%)
ARM - Character by Character - Misaligned 6912171712.2 34
ARM - Whole Data Unit - Misaligned 1818181818 (-6%)18 (-48%) 36 (-6%)
Xtensa - Character by Character - Aligned 91419252518.4 70
Xtensa - Whole Data Unit - Aligned 99999 (+64%)9 (+51%) 22 (+68%)
Xtensa - Character by Character - Misaligned 91419252518.4 70
Xtensa - Whole Data Unit - Misaligned 1818181818 (+28%)18 (+2%) 50 (+29%)

Operation Count

o

The function may exit at the middle if it found the difference before the last character in the string when comparing character by character.

For a long string (longer than one word), it is more likely that words in the first portion of the string are equal; then at a specific point, the difference is found at the Nth character at a specific word. In that case, we should consider the operation count for "Equal" for the first few words, and the operation count for "Average" for the last word. For example, if the Xth character is found different in a long string, the operation count should be:


(int)(X / sizeof(word)) * executedInstructionCount_equal + executedInstructionCount_average
	
t

When comparing as a whole data unit, the CPU reads all characters before compare. Therefore, all instructions in the function will be executed. Hence, the operation count (and time to run) will be the same whether the string is equal or not.

When comparing character by character, the operation count on "Avearge" is about 3/4 of the operation count on "Equal" on all three platforms for both aligned and misaligned string.

Because X86 supports misaligned multi-byte reads; hence, the operation count when comparing as a whole data uint is always less than comparing character by character whether the data is aligned or not. 73% faster for "Equal", 63% faster for "Average".

Alignment matters if the CPU cannot read misaligned multi-byte data. On ARM, comparing as a whole data uint is 53% / 34% (Equal / Average) faster than comparing character by character on avearge if data aligned; but 6% / 48% (Equal / Average) slower if misaligned. On Xtensa, comparing as a whole data uint is 64% / 51% (Equal / Average) faster than comparing character by character on avearge if data aligned; but only 28% / 2% (Equal / Average) faster if misaligned.

Assume run time is proportional to operation count (ARM and Xtensa are RISC, X86 is CISC). Actual performance varies due to memory bus stall, pipeline stall, branch prediction, and other optimizations.

Code Size

On the other hand, smaller code is favored not only because it saves storage size, but also reduces instruction cache misses. Hence, improve the performance.

Because X86 supports misaligned multi-byte reads; hence, code size when comparing as a whole data uint improves 75% than comparing character by character whether the data is aligned or not.

Alignment matters if the CPU cannot read misaligned multi-byte data. When aligned, code size improves 53% on ARM and 68% on Xtensa; when misaligned, there is negative 6% improvement on ARM, and only 29% improvement on Xtensa.

In conclusion, if data is aligned, comparing string as a whole data unit gives smaller code size and less instruction count than comparing character by character.

Modern desktop CPUs are capable of instruction reordering, parallel execution, and can rewrite the compiled machine code into different micro code at hardware-level. They tend to internally optimize the instruction generated by the compiler at run-time. Therefore, the actual speed may vary.

Low-end, embedded CPUs generally does not have these hardware-level optimizations (to reduce cost, to save energy and to minimize error). Analyzing the machine code generated by the compiler can give us pretty much the actual performance.

Use memcmp() Function from <string.h>

The C language provides string (including binary string, in other words, memory) manipulation functions in its standard library <string.h>; one of which is memory compare: memcmp(const void* target, const void* reference, size_t size).

In this section, we would like to see if the memcmp() function can compare multiple characters as a whole data unit.

Use memcmp() if the Comparison Size is Unknown

If the comparison size is unknown, GCC will link and use the library implementation. Consider the following C language code:


int compare_str_prototype(size_t size) {
	return (memcmp((void*)target, (char[]){'0', '1', '2', '3'}, size) == 0) ? 0 : 1;
}
	
which on X86 platform compiles to:

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   
		
on ARM platform compiles to:

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
		
and on Xtensa platform compiles to:

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
		

that, on all three platforms, an external memcmp() function will be called, its address will be resolved at link stage.

Use memcmp() on X86 for Known Size

On the other hand, if the size is known, GCC will inline the comparison function. Consider the following C language code:


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 Compares Aligned and Misaligned String Using memcmp()

Following shows orginal machine code:

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)
		

Because X86 is capable of misaligned multi-byte read, the machine code is same for both aligned and misaligned string:


  a6:	c7 44 24 fc 30 31 32 33 	movl   $0x33323130,-0x4(%rsp)
		

Copy the reference string (hardcoded in instruction) into the stack at address -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)
		

Load the the 0th word (4 characters) of target string into register A, then compare with the reference string in stack.


  b2:	0f 95 c0             	setne  %al
  b5:	0f b6 c0             	movzbl %al,%eax
  b8:	c3                   	retq 
		

If equal, clear register A to 0; otherwise, set register A to 1. Then, extend register A and return with value in register A.

X86 Compares External (Alignment Unknown) String Using memcmp()

Following shows orginal machine code:

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
		

Clear return register 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
		

Compare the 0th word (4 characters) in the target string with the reference string "3210"(0x33323130).


  ec:	0f 95 c0             	setne  %al
  ef:	c3                   	retq    
		

If equal, clear register A to 0; otherwise, set register A to 1. Then, return with value in register A.

On X86, the inlined memcmp() function always compares the string as a whole data unit.

For stack usage:

Use memcmp() on ARM for Known Size

ARM Compares Aligned String Using memcmp()

Following shows orginal machine code:

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
		

Move the stack pointer SP down for 8 bytes.


  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
		

Load the reference string into register R0. Then, store it into the stack at address -4 = SP + 4 (relative to the parent function's stack).

However, the reference string saved in the stack is not used at all. Instead, the orginal copy saved in register R0 is used for the comparison.

I have no idea why create the stack if not used. Maybe the memcmp() function prototype forces the input buffer to be placed into the data memory. Data memory means in stack or in heap, instead of in text.


  9a:	4b05      	ldr	r3, [pc, #20]	; b0
  9e:	681b      	ldr	r3, [r3, #0]
			b0: R_ARM_ABS32	target
		

Load the target string as a whole 32-bit data unit into register 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
		

Compare the 0th word (4 characters) between the target string and the reference string by subtract them.

If equal:


  a0:	r0 = "3210" - 0x33323130 = 0
  a2:	r3 = 0 - 1 = -1, carry = 1
  a4:	r0 = 0 - (-1) - 1 = 0
			

If not equal:


  a0:	r0 = any(!"3210") - 0x33323130 = some
  a2:	r3 = some - 1, carry = 0 ; For X - 1 to set carry, X must be 1.
  a4:	r0 = some - (some - 1) - 0 = 1
			

Restore the stack. Then, return with value in register R0.

ARM Compares Misaligned String Using memcmp()

Following shows orginal machine code:

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
		

Move the stack pointer SP down for 16 bytes.


  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
		

Load the reference string into register R3. Then, store it into the stack at address -4 = SP + 12 and -12 = SP + 4.

I have no idea why create two copies of the reference string in the stack.


  c0:	4b0e      	ldr	r3, [pc, #56]	; fc
  fc:	0001      	R_ARM_ABS32	target
		

Load the address of the misaligned target string into register R3. Note the offset 0001 from aligned target string 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
		

Create a frame pointer R2 pointed to -8 = SP + 8.

Load the 0th character from the target string into register R1, load the 0th character from the reference string into register R2 from stack address -4 = R2 + 4. Then, compare.

If equal, continue to step d4.

Why create frame pointer R2. We can use 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
		

Return 0 for equal or 1 for not-equal:

If equal, enter at step cc, register R0 was preset to 0 at step ee:


  cc:	r3 = 0 - 1 = -1, carry = 1
  ce:	r0 = 0 - (-1) - 1 = 0
			

If not-equal, enter at step ca:


  ca:	r0 = 1
  cc:	r3 = 1 - 1 = 0, carry = 0
  ce:	r0 = 1 - 0 - 0 = 1
			

Restore stack pointer and return with value in register R0.

Why not return directly?


  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
		

Create a frame pointer R2 pointed to -8 = SP + 8.

Load the 1st character from the target string into register R1, load the 1st character from the reference string into register R2 from stack address -3 = R2 + 5. Then, compare.

If equal, continue; otherwise, go to step 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
		

Create a frame pointer R2 pointed to -8 = SP + 8.

Load the 2nd character from the target string into register R1, load the 2nd character from the reference string into register R2 from stack address -2 = R2 + 6. Then, compare.

If equal, continue; otherwise, go to step 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
		

Create a frame pointer R3 pointed to -8 = SP + 8.

Load the 3rd character from the target string into register R2, load the 3rd character from the reference string into register R3 from stack address -1 = R3 + 7. Then, compare.

If equal, clear register R0 to 0 and go to step cc; otherwise, go to step ca.

ARM Compares External (Alignment Unknown) String Using memcmp()

Following shows orginal machine code:

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
		

Move the stack pointer SP down for 16 bytes.


 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
		

Load the reference string into register R3. Then, store it into the stack at address -4 = SP + 12 and -12 = SP + 4.

Again. Two copies in stack.


 106:	4a10      	ldr	r2, [pc, #64]	; 148
			148: R_ARM_ABS32	target_ext
		

Load the address of the external target string into register 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
		

Create a frame pointer R3 pointed to -16 = SP + 0.

Load the 0th character from the target string into register R1, load the 0th character from the reference string into register R3 from stack address -12 = R3 + 4. Then, compare.

If equal, continue to step 120.

Again. Why create frame pointer R3. We can use 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
		

Return 0 for equal or 1 for not-equal, using the same algorithm as previous ARM example.

Restore stack pointer and return with value in register R0.

Again, why not return directly?


 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
		

Create a frame pointer R3 pointed to -8 = SP + 8.

Load the 1st character from the target string into register R1, load the 1st character from the reference string into register R3 from stack address -3 = R3 + 5. Then, compare.

If equal, continue; otherwise, go to step 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
		

Create a frame pointer R3 pointed to -8 = SP + 8.

Load the 2nd character from the target string into register R1, load the 2nd character from the reference string into register R3 from stack address -2 = R3 + 6. Then, compare.

If equal, continue; otherwise, go to step 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
		

Create a frame pointer R3 pointed to -8 = SP + 8.

Load the 3rd character from the target string into register R2, load the 3rd character from the reference string into register R3 from stack address -1 = R3 + 7. Then, compare.

If equal, set register R0 to 0 and go to step 118; otherwise, go to step 116.

On ARM, the machine code generated by GCC is very similar for misaligned staring and external string when using memcpy(). Same algorithm, same code size, same flow, and same issue.

Use memcmp() on Xtensa for Known Size

Xtensa Compares Aligned String Using memcmp()

Following shows orginal machine code:

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
		

Move the window for 48 bytes to preserve parent registers and to allocate stack space.


  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
		

Load the reference string into register A9. Then, store it into the stack at address -48 = A1 + 0 (relative to the parent function's stack).

However, the reference string saved in the stack is not used at all. Instead, the orginal copy saved in A9 is used for the comparison.

I have no idea why create the stack if not used. Maybe the memcmp() function prototype forces the input buffer to be placed into the data memory. Data memory means in stack or in heap, instead of in text.


  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
		

Load the target string as a whole 32-bit data unit into register 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
		

Compare the 0th word (4 characters) between the target string and the reference string by subtract them.

If equal (zero in register A8), set register A2 to register A10 (value is 0); otherwise, register A2 unchanged (was 1). Return with value in register A2, restore the window.

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 Compares Misaligned String Using memcmp()

Following shows orginal machine code:

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
		

Move the window for 48 bytes to preserve parent registers and to allocate stack space.


  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
		

Load the address of the misaligned target string into register A9. Note the address of the misaligned target string is the address of the aligned target string + 1.

Load reference string into register A10. Then, store it into the stack at address -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
		

Load the 0th character from the reference string from stack address -48 = A1 + 0 into register A10, load and extend the 0th character from the target string into register A8, then compare.

If equal, continue; otherwise, go to step 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
		

Load the 1st character from the reference string from stack address -47 = A1 + 1 into register A10, load and extend the 1st character from the target string into register A8, then compare.

If equal, continue; otherwise, go to step 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
		

Load the 2nd character from the reference string from stack address -46 = A1 + 2 into register A10, load and extend the 2nd character from the target string into register A8, then compare.

If equal, continue; otherwise, go to step 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
		

Clear register A2, which is for equal.

Load the 3rd character from the reference string from stack address -45 = A1 + 3 into register A9, load and extend the 3rd character from the target string into register A8, then compare.

If equal, go to step 14a to return with value in register A2 (was 0); otherwise, set register A2 to 1 and return. Restore the window while return.

The machine code on Xtensa that determining the return value after comparison is more straight forward than ARM.

Xtensa Compares External (Alignment Unknown) String Using memcmp()

Following shows orginal machine code:

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
		

Move the window for 48 bytes to preserve parent registers and to allocate stack space.


 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
		

Load the address of the external target string into register A9.

Load reference string into register A8. Then, store it into the stack at address -48 = A1 + 0.


 157:	0009a2        	l8ui	a10, a9, 0
 15d:	748080        	extui	a8, a8, 0, 8
 160:	1c9a87        	bne	a10, a8, 180
		

Extend the 0th character low 8 bits in the reference string in register A8 to keep the 0th character only, load the 0th character from the target string into register A10, then compare.

If equal, continue; otherwise, go to step 180.


 163:	0109a2        	l8ui	a10, a9, 1
 166:	010182        	l8ui	a8, a1, 1
 169:	139a87        	bne	a10, a8, 180
		

Load the 1st character from the reference string from stack address -47 = A1 + 1 into register A8, load the 1st character from the target string into register A10, then compare.

If equal, continue; otherwise, go to step 180.


 16c:	0209a2        	l8ui	a10, a9, 2
 16f:	020182        	l8ui	a8, a1, 2
 172:	0a9a87        	bne	a10, a8, 180
		

Load the 2nd character from the reference string from stack address -46 = A1 + 2 into register A8, load the 2nd character from the target string into register A10, then compare.

If equal, continue; otherwise, go to step 148.


 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
		

Clear register A2, which is for equal.

Load the 3rd character from the reference string from stack address -45 = A1 + 3 into register A8, load the 3rd character from the target string into register A9, then compare.

If equal, go to step 182 to return with value in register A2 (was 0); otherwise, set register A2 to 1 and return. Restore the window while return.

No memory wait memw and no target string character extend extui a_target, a_target, 0, 8 used when the target string is external.

Performance of memcmp()

As we can see in the previous examples, GCC tends to compare the string as a whole data unit for memcpy() function when the string is aligned or when the CPU supports misaligned reads; hence, provide the best performance. If the string is misaligned and the CPU can not perform a misaligned read, the memcpy() function will perform read-and-compare operation character by character.

On the other hand, compare to notation like (*((multibyte_t*)a1) == *((multibyte_t*)((char[]){'0', '1', '2', '3'}))), using memcpy() has higher memory usage (allocating stack), even the data is stack is not used.

Operation Count and Code Size and Stack Usage - 4-byte Data
Difference Found on Nth Character 0 (ops)1 (ops)2 (ops)3 (ops)Equal (ops)Average (ops) Code Size (bytes)Stack Usage (bytes)
X86 - Alignment 666666 254
X86 - Misaligned 777777 254
X86 - External 444444 160
ARM - Alignment 111111111111 228
ARM - Misaligned 162126323225.4 6616
ARM - External 162126323225.4 6616
Xtensa - Alignment 121212121212 30+16
Xtensa - Misaligned 121722282721.2 80+16
Xtensa - External 101316201915.6 56+16

In conclusion, using memcpy(), machine code generated by GCC is:

; however, using memcpy() is good for: