Description
(Discovered when reviewing #41957)
This piece of code:
pub fn check(a: &str) -> bool {
a.starts_with('/')
}
generates an extremely complicated assembly. The generated function will try to decode the input string a
, obtain the first code point, and then compare with '/'
(47).
Compare with
pub fn check(a: &str) -> bool {
a.starts_with("/")
}
which basically just checks if the first byte of the string is '/'
(after an unnecessary (?) is_char_boundary
check introduced in #26771 and another unnecessary pointer-equality check)
Most of the time the character as a pattern is a compile-time constant, so it should be more efficient by encoding the char into UTF-8, and then memcmp
with the input string.
(Rust uses the "decoding" approach so that char
share the same implementation with &[char]
and FnMut(char) -> bool
via the CharEq
trait.)
This problem is particularly bad for Clippy users, since there is a single_char_pattern
lint which suggested changing all .starts_with("x")
by .starts_with('x')
with the assumption that the latter is faster, which as of Rust 1.19 it is the reverse.
$ rustc -vV
rustc 1.19.0-nightly (826d8f385 2017-05-13)
binary: rustc
commit-hash: 826d8f3850b37a23481dfcf4a899b5dfc82d22e3
commit-date: 2017-05-13
host: x86_64-apple-darwin
release: 1.19.0-nightly
LLVM version: 4.0
x86_64 ASM
Build with:
$ rustc --crate-type=dylib -Copt-level=3 --emit=asm 2.rs
$ cat 2.s
First function (.starts_with('/')
):
.section __TEXT,__text,regular,pure_instructions
.globl __ZN2_25check17hfe0a879e8d0ee56bE
.p2align 4, 0x90
__ZN2_25check17hfe0a879e8d0ee56bE:
.cfi_startproc
pushq %rbp
Lcfi0:
.cfi_def_cfa_offset 16
Lcfi1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Lcfi2:
.cfi_def_cfa_register %rbp
testq %rsi, %rsi
je LBB0_1
movzbl (%rdi), %edx
testb %dl, %dl
jns LBB0_17
leaq (%rdi,%rsi), %r8
xorl %eax, %eax
cmpq $1, %rsi
movq %r8, %rsi
je LBB0_5
movzbl 1(%rdi), %eax
addq $2, %rdi
andl $63, %eax
movq %rdi, %rsi
LBB0_5:
movl %edx, %ecx
andl $31, %ecx
cmpb $-32, %dl
jb LBB0_6
cmpq %r8, %rsi
je LBB0_8
movzbl (%rsi), %edi
incq %rsi
andl $63, %edi
jmp LBB0_10
LBB0_1:
xorl %eax, %eax
jmp LBB0_18
LBB0_6:
shll $6, %ecx
jmp LBB0_16
LBB0_8:
xorl %edi, %edi
movq %r8, %rsi
LBB0_10:
shll $6, %eax
orl %edi, %eax
cmpb $-16, %dl
jb LBB0_11
cmpq %r8, %rsi
je LBB0_13
movzbl (%rsi), %edx
andl $63, %edx
jmp LBB0_15
LBB0_11:
shll $12, %ecx
jmp LBB0_16
LBB0_13:
xorl %edx, %edx
LBB0_15:
andl $7, %ecx
shll $18, %ecx
shll $6, %eax
orl %edx, %eax
LBB0_16:
orl %ecx, %eax
movl %eax, %edx
LBB0_17:
cmpl $47, %edx
sete %al
LBB0_18:
popq %rbp
retq
.cfi_endproc
.subsections_via_symbols
Second function (.starts_with("/")
):
.section __TEXT,__text,regular,pure_instructions
.globl __ZN2_25check17hfe0a879e8d0ee56bE
.p2align 4, 0x90
__ZN2_25check17hfe0a879e8d0ee56bE:
.cfi_startproc
pushq %rbp
Lcfi0:
.cfi_def_cfa_offset 16
Lcfi1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Lcfi2:
.cfi_def_cfa_register %rbp
testq %rsi, %rsi
je LBB0_3
cmpq $1, %rsi
je LBB0_4
cmpb $-65, 1(%rdi)
jle LBB0_3
LBB0_4:
movb $1, %al
leaq _str.0(%rip), %rcx
cmpq %rcx, %rdi
je LBB0_6
cmpb $47, (%rdi)
je LBB0_6
LBB0_3:
xorl %eax, %eax
LBB0_6:
popq %rbp
retq
.cfi_endproc
.section __TEXT,__const
_str.0:
.byte 47
.subsections_via_symbols