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.
C language, Assembly language, GCC, Optimization, X86, ARM, Xtensa, memcmp, stack
--by Captdam @ Sep 27, 2026Index
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:
-
http_request_header- Target string, the string to compare. -
"GET "- Reference string, the string to compare against.
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:
-
X86 - A powerful desktop PC CPU.
-
ARM-M0+ - The CPU used in Raspberry Pi RP2040, an ARM CPU designed for embedded systems.
-
Xtensa - The CPU used in ESP32, a chip designed for WiFi communication-capable embedded device.
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:
-
The string is compared character by character.
-
Each character of the reference string is stored in the instruction (immediate addressing)
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:
-
(char[]){'0', '1', '2', '3'}- Pointer to an inline character array: 4 characters, each 8-bit wide. -
(uint32_t*)((char[]){'0', '1', '2', '3'})- Cast it into a pointer for 32-bit unsigned integer. -
*((uint32_t*)((char[]){'0', '1', '2', '3'}))- Get the value this pointer as a 32-bit unsigned integer.
This shortcut embeds the reference string:
-
Into a single data unit that requires only a comparison, instead of an array that requires
sizeof(array) / sizeof(array[0])comparisons. -
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
| 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 | 5 | 8 | 11 | 15 | 15 | 10.8 | 64 |
| X86 - Whole Data Unit - Aligned | 4 | 4 | 4 | 4 | 4 (+73%) | 4 (+63%) | 16 (+75%) |
| X86 - Character by Character - Misaligned | 5 | 8 | 11 | 15 | 15 | 10.8 | 64 |
| X86 - Whole Data Unit - Misaligned | 4 | 4 | 4 | 4 | 4 (+73%) | 4 (+63%) | 16 (+75%) |
| ARM - Character by Character - Aligned | 6 | 9 | 12 | 17 | 17 | 12.2 | 34 |
| ARM - Whole Data Unit - Aligned | 8 | 8 | 8 | 8 | 8 (+53%) | 8 (+34%) | 16 (+53%) |
| ARM - Character by Character - Misaligned | 6 | 9 | 12 | 17 | 17 | 12.2 | 34 |
| ARM - Whole Data Unit - Misaligned | 18 | 18 | 18 | 18 | 18 (-6%) | 18 (-48%) | 36 (-6%) |
| Xtensa - Character by Character - Aligned | 9 | 14 | 19 | 25 | 25 | 18.4 | 70 |
| Xtensa - Whole Data Unit - Aligned | 9 | 9 | 9 | 9 | 9 (+64%) | 9 (+51%) | 22 (+68%) |
| Xtensa - Character by Character - Misaligned | 9 | 14 | 19 | 25 | 25 | 18.4 | 70 |
| Xtensa - Whole Data Unit - Misaligned | 18 | 18 | 18 | 18 | 18 (+28%) | 18 (+2%) | 50 (+29%) |
Operation Count
oThe 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;
}
-
compare_str_aligned()- Target string is aligned. -
compare_str_misaligned()- Target string is misaligned. -
compare_str_unknown()- Address of the external target string to be resolved at link stage; hence, alignment unknown. In other words, this function works for both aligned and misaligned string.
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:
-
When the alignment of the target string is known, whether aligned or misaligned, 4 bytes of stack space is allocated.
-
For external target string where the address is not known, no stack used.
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.
| 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 | 6 | 6 | 6 | 6 | 6 | 6 | 25 | 4 |
| X86 - Misaligned | 7 | 7 | 7 | 7 | 7 | 7 | 25 | 4 |
| X86 - External | 4 | 4 | 4 | 4 | 4 | 4 | 16 | 0 |
| ARM - Alignment | 11 | 11 | 11 | 11 | 11 | 11 | 22 | 8 |
| ARM - Misaligned | 16 | 21 | 26 | 32 | 32 | 25.4 | 66 | 16 |
| ARM - External | 16 | 21 | 26 | 32 | 32 | 25.4 | 66 | 16 |
| Xtensa - Alignment | 12 | 12 | 12 | 12 | 12 | 12 | 30 | +16 |
| Xtensa - Misaligned | 12 | 17 | 22 | 28 | 27 | 21.2 | 80 | +16 |
| Xtensa - External | 10 | 13 | 16 | 20 | 19 | 15.6 | 56 | +16 |
In conclusion, using memcpy(), machine code generated by GCC is:
-
Consuming more data memory (stack size on X86 and ARM, window size on Xtensa). The reference string must be copied into stack (and twice on ARM), even the copy in stack is not used in some time.
-
Consuming more instruction memory (text size).
-
Is more incomprehensibly and less efficient machine code.
; however, using memcpy() is good for:
-
Working for any data size.
-
Easy-to-read source code.