为什么在包含范围上的迭代会在Rust中生成更长的程序集? [英] Why does iteration over an inclusive range generate longer assembly in Rust?

查看:32
本文介绍了为什么在包含范围上的迭代会在Rust中生成更长的程序集?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这两个循环在C++和Rust中是等效的:

#include <cstdint>
uint64_t sum1(uint64_t n) {  
    uint64_t sum = 0;
    for (uint64_t j = 0; j <= n; ++j) {
        sum += 1;
    }
    return sum;
}
pub fn sum1(num: u64) -> u64 {
    let mut sum: u64 = 0;
    for j in 0u64..=num {
        sum += 1;
    }
    return sum;
}

但是,C++版本会生成一个非常简洁的程序集:

sum1(unsigned long):                               # @sum1(unsigned long)
        xor     eax, eax
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        add     rax, 1
        cmp     rax, rdi
        jbe     .LBB0_1
        ret

虽然Rust的版本很长,循环中有两个检查,而不是一个:

example::sum1:
        xor     eax, eax
        xor     ecx, ecx
.LBB0_1:
        mov     rdx, rcx
        cmp     rcx, rdi
        adc     rcx, 0
        add     rax, 1
        cmp     rdx, rdi
        jae     .LBB0_3
        cmp     rcx, rdi
        jbe     .LBB0_1
.LBB0_3:
        ret

始祖:https://godbolt.org/z/xYW94qxjK

Rust本质上试图阻止C++无忧无虑的是什么?

推荐答案

迭代器状态中溢出。

当给定足够大的输入时,C++版本将永远循环:

#include <cstdint>

[[gnu::noinline]]
std::uint64_t sum1(std::uint64_t n) {  
    std::uint64_t sum = 0;
    for (std::uint64_t j = 0; j <= n; ++j) {
        sum += 1;
    }
    return sum;
}

#include <iostream>

int main() {
    std::cout << sum1(UINT64_C(0xffffffff'ffffffff)) << std::endl;

    return 0;
}

这是因为当循环计数器j最终达到0xffffffff'ffffffff时,递增到0,这意味着循环不变量j <= n始终满足,循环永远不会退出。

严格地说,使用0xffffffff'ffffffffinfamously调用sum1会触发未定义的行为,虽然不是因为溢出本身,而是因为没有外部可见副作用的无限循环是UB([intro.progress]/1)。这就是我对函数应用[[gnu::noinline]]属性的原因,以防止编译器在优化过程中利用该属性。

另一方面,Rust版本不仅定义完美,而且迭代次数与范围的基数完全相同:

use std::num::Wrapping;

fn sum1(num: u64) -> u64 {
    let mut sum = Wrapping(0);
    for _ in 0..=num {
        sum += Wrapping(1);
    }
    return sum.0;
}

fn main() {
    println!("{}", sum1(0xffffffff_ffffffff));
}

上述程序(稍作修改以避免陷入与总和相关的调试和发布模式差异中)将在恰好18446744073709551616次迭代后终止并打印0;Rust版本小心地维护迭代器状态,以便迭代器中不会发生溢出。

这篇关于为什么在包含范围上的迭代会在Rust中生成更长的程序集?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆