为什么基于 clap::Parser 读取的大量数字进行的计算比硬编码该数字时慢?

pid*_*pid 4 rust

我有一个非常简单的程序,它从命令行获取整数参数并执行以下任务:

use clap::Parser ;

#[derive(Parser,Default)]
struct Args {
    #[arg(short)]
    number: u128
}

fn sum(n: u128) -> u128 {
    let mut result: u128 = 0;
    for i in 1..n { result += n/i; }
    result 
}

fn main() {
    let args = Args::parse() ;
    println!("{}", sum(args.number)) ;
}
Run Code Online (Sandbox Code Playgroud)

相当简单,对吧?好吧,当对“大”数字执行它时,例如 999999999,在我的机器上几乎需要十秒钟才能得到结果。

$ time ./target/release/main -n 999999999
20877697533

real    0m9.442s
user    0m9.370s
sys     0m0.030s
Run Code Online (Sandbox Code Playgroud)

但是,如果我禁止使用任何拍手并对值进行硬编码

$ time ./target/release/main -n 999999999
20877697533

real    0m9.442s
user    0m9.370s
sys     0m0.030s
Run Code Online (Sandbox Code Playgroud)

执行时间下降到两秒左右

$ time ./target/release/main
20877697533

real    0m2.398s
user    0m2.236s
sys     0m0.013s
Run Code Online (Sandbox Code Playgroud)

那么,是什么让 clap 版本这么慢呢?

Seb*_*edl 5

999999999 适合 32 位整数。通过硬编码值,编译器会注意到这一点并将所有操作降级为 32 位操作,这比没有本机编译器支持的 128 位例程执行速度要快得多

比较非硬编码循环的程序集(但删除了 clap,如 Schwern 的版本):

    xorl    %esi, %esi
    cmpq    $2, %r15
    movq    %rdx, %rax
    sbbq    $0, %rax
    movl    $0, %r13d
    jb  .LBB5_9
    movq    %r15, %rsi
    addq    $-1, %rsi
    movq    %rdx, %rdi
    adcq    $-1, %rdi
    movq    %r15, %rax
    addq    $-2, %rax
    movq    %rdx, %rcx
    adcq    $-1, %rcx
    movq    %rsi, 32(%rsp)
    andl    $3, %esi
    movq    %rsi, 40(%rsp)
    cmpq    $3, %rax
    sbbq    $0, %rcx
    movq    %rdx, 8(%rsp)
    jae .LBB5_4
    xorl    %ebp, %ebp
    movl    $1, %edx
    xorl    %r13d, %r13d
    xorl    %ecx, %ecx
    jmp .LBB5_6

.LBB5_4:
    andq    $-4, 32(%rsp)
    movl    $1, %edx
    xorl    %ebp, %ebp
    xorl    %r13d, %r13d
    xorl    %ecx, %ecx
    xorl    %esi, %esi
    xorl    %ebx, %ebx
    movq    %rdi, 64(%rsp)

.LBB5_5:
    movq    %rbx, 88(%rsp)
    movq    %rsi, 96(%rsp)
    movq    %rcx, 16(%rsp)
    movq    %rdx, (%rsp)
    addq    $1, %rdx
    movq    %rdx, 24(%rsp)
    movq    %rcx, %rbx
    adcq    $0, %rbx
    movq    %r15, %rdi
    movq    8(%rsp), %rsi
    movq    (%rsp), %rdx
    movq    16(%rsp), %rcx
    movq    __udivti3@GOTPCREL(%rip), %r14
    callq   *%r14
    movq    %r14, %r8
    movq    %rax, %r14
    movq    %rdx, %r12
    addq    %rbp, %r14
    adcq    %r13, %r12
    movq    (%rsp), %rax
    addq    $2, %rax
    movq    %rax, 80(%rsp)
    movq    16(%rsp), %rax
    adcq    $0, %rax
    movq    %rax, 72(%rsp)
    movq    %r15, %rdi
    movq    8(%rsp), %rsi
    movq    24(%rsp), %rdx
    movq    %rbx, %rcx
    movq    %r8, %rbx
    callq   *%r8
    movq    %rbx, %r8
    movq    %rax, %rbx
    movq    %r15, %r13
    movq    %rdx, %rbp
    addq    %r14, %rbx
    adcq    %r12, %rbp
    movq    (%rsp), %rax
    addq    $3, %rax
    movq    %rax, 24(%rsp)
    movq    16(%rsp), %r15
    adcq    $0, %r15
    movq    %r13, %rdi
    movq    8(%rsp), %rsi
    movq    80(%rsp), %rdx
    movq    72(%rsp), %rcx
    movq    %r8, %r14
    callq   *%r8
    movq    %r14, %r8
    movq    %rax, %r12
    movq    %rdx, %r14
    addq    %rbx, %r12
    adcq    %rbp, %r14
    addq    $4, (%rsp)
    adcq    $0, 16(%rsp)
    movq    %r13, %rdi
    movq    8(%rsp), %rsi
    movq    24(%rsp), %rdx
    movq    %r15, %rcx
    movq    %r13, %r15
    callq   *%r8
    movq    88(%rsp), %rbx
    movq    96(%rsp), %rsi
    movq    64(%rsp), %rdi
    movq    %rax, %rbp
    movq    %rdx, %r13
    movq    (%rsp), %rdx
    addq    %r12, %rbp
    adcq    %r14, %r13
    addq    $4, %rsi
    adcq    $0, %rbx
    movq    %rsi, %rax
    xorq    32(%rsp), %rax
    movq    %rbx, %rcx
    xorq    %rdi, %rcx
    orq %rax, %rcx
    movq    16(%rsp), %rcx
    jne .LBB5_5

.LBB5_6:
    cmpq    $0, 40(%rsp)
    movq    %rbp, %rsi
    je  .LBB5_9
    xorl    %r12d, %r12d
    xorl    %ebp, %ebp
    movq    %rdx, %rbx
    movq    %rcx, %r14

.LBB5_8:
    movq    %rsi, (%rsp)
    addq    $1, %rbx
    adcq    $0, %r14
    movq    %r15, %rdi
    movq    8(%rsp), %rsi
    callq   *__udivti3@GOTPCREL(%rip)
    movq    (%rsp), %rsi
    addq    %rax, %rsi
    adcq    %rdx, %r13
    addq    $1, %r12
    adcq    $0, %rbp
    movq    %r12, %rax
    xorq    40(%rsp), %rax
    orq %rbp, %rax
    movq    %rbx, %rdx
    movq    %r14, %rcx
    jne .LBB5_8
Run Code Online (Sandbox Code Playgroud)

到硬编码循环:

.LBB5_1:
    movl    $999999999, %eax
    xorl    %edx, %edx
    divl    %r8d
    movl    %eax, %r9d
    addq    %rcx, %r9
    adcq    $0, %rdi
    addq    $2, %r10
    adcq    $0, %r11
    leal    1(%r8), %ecx
    movl    $999999999, %eax
    xorl    %edx, %edx
    divl    %ecx
    movl    %eax, %ecx
    addq    %r9, %rcx
    adcq    $0, %rdi
    cmpq    $999999997, %r8
    sbbq    $0, %rsi
    movq    %r10, %r8
    movq    %r11, %rsi
    jb  .LBB5_1
    subq    $88, %rsp
    movq    %rcx, 24(%rsp)
    movq    %rdi, 32(%rsp)
    leaq    24(%rsp), %rax
    movq    %rax, 8(%rsp)
    movq    core::fmt::num::<impl core::fmt::Display for u128>::fmt@GOTPCREL(%rip), %rax
    movq    %rax, 16(%rsp)
    leaq    .L__unnamed_2(%rip), %rax
    movq    %rax, 56(%rsp)
    movq    $2, 64(%rsp)
    movq    $0, 40(%rsp)
    leaq    8(%rsp), %rax
    movq    %rax, 72(%rsp)
    movq    $1, 80(%rsp)
    leaq    40(%rsp), %rdi
    callq   *std::io::stdio::_print@GOTPCREL(%rip)
    addq    $88, %rsp
    retq
Run Code Online (Sandbox Code Playgroud)

请特别注意此版本中完全没有对 的调用__udivti3