rust-lang / rust-lang/rust

`Path` comparison is obscenely slow

Open
#154,521 12 comments 0 reactions 1 assignee View on GitHub

@rustbot is already working on this.

Since Jul 7, 2026.

C-bug I-slow T-libs
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.