`Path` comparison is obscenely slow
@rustbot is already working on this.
Since Jul 7, 2026.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.2k
- PR merge metrics
- PR metrics pending
Description
So, I did a classic thing where I Rewrote It In Rust with an old shell script and it ran a lot slower. And the surprising culprit for the slowdown was std::path::compare_components, which accounted for 64.24% of the runtime of my program according to callgrind.
Looking at the godbolt code, it's pretty bad:
use std::path::Path;
#[inline(never)]
pub fn compare_paths(x: &Path, y: &Path) -> bool {
x == y
}
#[inline(never)]
pub fn compare_strings(x: &str, y: &str) -> bool {
x == y
}
Generated x86 assembly:
example::compare_paths::h5eec614bf73d21a6:
push rbp
push r15
push r14
push r13
push r12
push rbx
sub rsp, 456
mov rbx, rcx
mov r14, rdx
mov rdx, rsi
mov rsi, rdi
mov r15, qword ptr [rip + std::path::Path::components::h97ae5388a318f0a4@GOTPCREL]
mov rdi, rsp
call r15
lea rdi, [rsp + 64]
mov rsi, r14
mov rdx, rbx
call r15
mov r14, qword ptr [rsp]
mov r15, qword ptr [rsp + 8]
mov rbx, qword ptr [rsp + 64]
mov r12, qword ptr [rsp + 72]
cmp r15, r12
jne .LBB0_4
movzx eax, byte ptr [rsp + 56]
cmp al, byte ptr [rsp + 120]
jne .LBB0_4
cmp byte ptr [rsp + 57], 2
jne .LBB0_4
cmp byte ptr [rsp + 121], 2
jne .LBB0_4
mov rdi, r14
mov rsi, rbx
mov rdx, r15
call qword ptr [rip + bcmp@GOTPCREL]
test eax, eax
je .LBB0_18
.LBB0_4:
movzx eax, byte ptr [rsp + 16]
cmp al, 6
je .LBB0_6
mov rcx, qword ptr [rsp + 48]
mov qword ptr [rsp + 399], rcx
movups xmm0, xmmword ptr [rsp + 17]
movups xmm1, xmmword ptr [rsp + 33]
movaps xmmword ptr [rsp + 384], xmm1
movaps xmmword ptr [rsp + 368], xmm0
.LBB0_6:
movzx ecx, byte ptr [rsp + 58]
movzx esi, byte ptr [rsp + 56]
movzx edi, byte ptr [rsp + 57]
movzx edx, byte ptr [rsp + 80]
cmp dl, 6
je .LBB0_8
mov r8, qword ptr [rsp + 112]
mov qword ptr [rsp + 447], r8
movups xmm0, xmmword ptr [rsp + 81]
movups xmm1, xmmword ptr [rsp + 97]
movaps xmmword ptr [rsp + 432], xmm1
movaps xmmword ptr [rsp + 416], xmm0
.LBB0_8:
movzx r8d, byte ptr [rsp + 122]
movzx r9d, word ptr [rsp + 120]
mov qword ptr [rsp + 128], r14
mov qword ptr [rsp + 136], r15
mov byte ptr [rsp + 144], al
movaps xmm0, xmmword ptr [rsp + 368]
movaps xmm1, xmmword ptr [rsp + 384]
movups xmmword ptr [rsp + 145], xmm0
movups xmmword ptr [rsp + 161], xmm1
mov rax, qword ptr [rsp + 399]
mov qword ptr [rsp + 176], rax
mov byte ptr [rsp + 184], sil
mov byte ptr [rsp + 185], dil
mov byte ptr [rsp + 186], cl
mov qword ptr [rsp + 304], rbx
mov qword ptr [rsp + 312], r12
mov byte ptr [rsp + 320], dl
movaps xmm0, xmmword ptr [rsp + 416]
movaps xmm1, xmmword ptr [rsp + 432]
movups xmmword ptr [rsp + 321], xmm0
movups xmmword ptr [rsp + 337], xmm1
mov rax, qword ptr [rsp + 447]
mov qword ptr [rsp + 352], rax
mov word ptr [rsp + 360], r9w
mov byte ptr [rsp + 362], r8b
lea rdi, [rsp + 192]
lea rsi, [rsp + 128]
call qword ptr [rip + <std::path::Components as core::iter::traits::double_ended::DoubleEndedIterator>::next_back::h6927a2b9b4414ad5@GOTPCREL]
movzx r14d, byte ptr [rsp + 192]
cmp r14b, 10
jne .LBB0_9
.LBB0_27:
lea rdi, [rsp + 248]
lea rsi, [rsp + 304]
call qword ptr [rip + <std::path::Components as core::iter::traits::double_ended::DoubleEndedIterator>::next_back::h6927a2b9b4414ad5@GOTPCREL]
cmp byte ptr [rsp + 248], 10
sete al
jmp .LBB0_30
.LBB0_9:
mov rax, qword ptr [rip + <std::path::Components as core::iter::traits::double_ended::DoubleEndedIterator>::next_back::h6927a2b9b4414ad5@GOTPCREL]
jmp .LBB0_10
.LBB0_28:
cmp bl, cl
jne .LBB0_29
.LBB0_26:
lea rdi, [rsp + 192]
lea rsi, [rsp + 128]
mov rbx, qword ptr [rip + <std::path::Components as core::iter::traits::double_ended::DoubleEndedIterator>::next_back::h6927a2b9b4414ad5@GOTPCREL]
call rbx
mov rax, rbx
movzx r14d, byte ptr [rsp + 192]
cmp r14b, 10
je .LBB0_27
.LBB0_10:
movzx ebx, byte ptr [rsp + 193]
mov r12, qword ptr [rsp + 200]
mov r15, qword ptr [rsp + 208]
mov rbp, qword ptr [rsp + 216]
mov r13, qword ptr [rsp + 224]
lea rdi, [rsp + 248]
lea rsi, [rsp + 304]
call rax
movzx ecx, byte ptr [rsp + 248]
cmp rcx, 10
je .LBB0_29
movzx eax, r14b
lea rdi, [rax - 5]
cmp al, 6
mov esi, 0
cmovb rdi, rsi
lea rdx, [rcx - 5]
cmp cl, 6
cmovb rdx, rsi
cmp rdi, rdx
jne .LBB0_29
mov rsi, qword ptr [rsp + 256]
mov rdx, qword ptr [rsp + 264]
cmp rdi, 4
je .LBB0_23
test rdi, rdi
jne .LBB0_26
cmp cl, 5
ja .LBB0_26
cmp r14b, cl
jne .LBB0_29
movzx ecx, byte ptr [rsp + 249]
mov r8, qword ptr [rsp + 272]
mov r14, qword ptr [rsp + 280]
lea rdi, [rip + .LJTI0_0]
movsxd rax, dword ptr [rdi + 4*rax]
add rax, rdi
jmp rax
.LBB0_23:
cmp r15, rdx
jne .LBB0_29
mov rdi, r12
mov rdx, r15
call qword ptr [rip + bcmp@GOTPCREL]
test eax, eax
je .LBB0_26
jmp .LBB0_29
.LBB0_19:
cmp r15, rdx
jne .LBB0_29
mov rdi, r12
mov rdx, r15
mov rbx, r8
call qword ptr [rip + bcmp@GOTPCREL]
test eax, eax
jne .LBB0_29
cmp r13, r14
jne .LBB0_29
mov rsi, rbx
mov rdi, rbp
mov rdx, r13
call qword ptr [rip + bcmp@GOTPCREL]
test eax, eax
je .LBB0_26
.LBB0_29:
xor eax, eax
jmp .LBB0_30
.LBB0_18:
mov al, 1
.LBB0_30:
add rsp, 456
pop rbx
pop r12
pop r13
pop r14
pop r15
pop rbp
ret
.LJTI0_0:
.long .LBB0_23-.LJTI0_0
.long .LBB0_19-.LJTI0_0
.long .LBB0_28-.LJTI0_0
.long .LBB0_23-.LJTI0_0
.long .LBB0_19-.LJTI0_0
.long .LBB0_28-.LJTI0_0
example::compare_strings::hff5ab0c8ddd3840e:
cmp rsi, rcx
jne .LBB1_1
push rax
mov rax, rsi
mov rsi, rdx
mov rdx, rax
call qword ptr [rip + bcmp@GOTPCREL]
test eax, eax
sete al
add rsp, 8
ret
.LBB1_1:
xor eax, eax
ret
Note that the code is so long that it doesn't even inline the calls to std::path::Path::components and std::path::Components::next_back. (Why is comparing backwards?)
I get that there's a lot that goes into actually comparing paths vs. strings, but for equality at least, I would not expect it to be this bad. Effectively 65% of the entire runtime of my program can be cut off by using string comparisons instead of Path comparisons, and it feels like we could do way better than that.
This issue has been assigned to @asder8215 via this comment.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.