Skip to content

str::starts_with('x') (literal char) is slower than str::starts_with("x") (literal string). #41993

Closed
@kennytm

Description

@kennytm

(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

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-enhancementCategory: An issue proposing an enhancement or a PR with one.I-slowIssue: Problems and improvements with respect to performance of generated code.T-libs-apiRelevant to the library API team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions