这两个回路在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
C无忧无虑地想阻止什么?
锁销:https://godbolt.org/z/xYW94qxjK
@user3840170正确识别了差异:在Rust中进行溢出检查,而在C中没有。
然而,问题仍然是为什么在Rust版本中每个循环有2个检查而不是1个,答案是LLVM不够智能和/或Range包容性设计不适合LLVM1.
短循环的最佳代码生成是分割循环,转换:
for j in 0u64..=num {
sum += 1;
}
进入:
for j in 0u64..num { // equivalent to for (auto j = 0; j < num; ++j)
sum += 1;
}
if 0 <= num {
sum += 1;
}
这将导致主循环中只有一个分支,并允许LLVM将其切换到闭合公式2。
循环拆分优化适用于RangeInclusive
和大多数其他Chain
迭代器,因为实际上RangeInclusive
可以被视为一次迭代器和半排他范围迭代器(按任意顺序)的链。这并不总是一个胜利:与内联一样,它意味着复制循环体的代码,如果较大,可能会导致代码大小的显著开销。
不幸的是,LLVM无法分割循环。我不确定这是因为优化完全缺失了,还是因为某种原因无法在这里应用它。我期待着rustc_codegen_gcc
后端,因为gcc7向gcc添加了循环拆分,它可能能够在那里生成更好的代码。
1查看我对RangeInclusive
性能问题留下的评论。2019年,我花了大量时间研究这个问题,我非常希望RangeInclusive
(以及所有范围)不是Iterator
;这是一个代价高昂的错误。
2链迭代器通常使用执行得更好。对于每个(…)
,特别是因为存在(手动)拆分循环。有关
(0..=num)的信息,请参见操场。对于每个(| | sum=1)
减少到num 1
。
这两个回路在C和Rust中是等效的
你的两个被剪断的代码不具有相同的行为<代码>用于(uint64_t j=0;j
防锈等效代码可以是:
pub fn sum1(num: u64) -> u64 {
let mut sum: u64 = 0;
let mut j = 0;
while j <= num {
sum = sum.wrapping_add(1);
j = j.wrapping_add(1);
}
sum
}
目前生产以下asm锁紧螺栓:
example::sum1:
xor eax, eax
.LBB0_1:
add rax, 1
cmp rax, rdi
jbe .LBB0_1
ret
迭代器状态下的溢出。
当给定足够大的输入时,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'ffffffffff
时,将其递增到0,这意味着循环不变量j
严格来说,使用0xffffffff'ffffffff
调用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 1.0之前的版本,在语法上不是有效的Rust 1.0代码。此代码的更新版本会产生不同的错误,但答案仍然包含有价值的信息。 我遇到了下面的例子,说明了如何使用Rust生成一个随机数,但它似乎不起作用。这个例子没有显示它适用于哪个版本的Rust,所以可能它已经过时了,或者可能我搞错了什么。 当我试图编译此文件时,会出现以下错误: 在同一页(上面)上还有另一个例子(如下所
问题内容: Swift 2.2可以实现以下功能: 使用3.0,我们会收到以下错误: 类型“范围”(又名“范围”)不符合协议“序列” 我正在尝试对字符串进行快速的非常简单的操作-只需遍历字符串的前半部分(或更常见的问题:遍历字符串的范围)。 我可以执行以下操作: 但是这里我并没有真正遍历字符串。所以问题是:如何遍历给定字符串的范围。喜欢: 问题答案: 您可以使用以下属性来遍历字符串: 从“ 字符串和
我正在尝试用maven-shade插件2.1构建一个uber-jar。我希望它包括我的jar和依赖jar中的所有类。但我发现它不包括依赖jar中的类。我可能做错了什么?以下是我在pom.xml中对maven-shade插件的用法。可能是因为finalName与project.artifactid相同吗?
我一直在尝试在listview生成器的末尾添加一个按钮。我试着做这个问题中建议的事情:Flutter:如何在ListView的末尾添加一个按钮小部件。包含其他类型小部件的生成器?。但如果我这样做,我会得到:“RangeError(index):无效值:不在包含范围0..49:50中 我试图寻找问题,也有这个问题,但我找不到一个答案,解决它。
问题内容: 我试图找出一种方法来更改每个打印的步骤值 我尝试过的 问题答案: 我建议为此使用循环实现自己的生成器。范例- 然后,您可以将其用作- 我们这样做是可以进行任何迭代的。 演示-
问题内容: 我看过很多文章,解释了这个问题,但是他们都使用整数值,老实说,我并没有完全理解它,所以这个问题: 我正在尝试在Java中生成从(-1554900.101)到(52952058699.3098)范围的随机数,我想知道是否有人可以向我解释这一点,因为我真的很想理解它。 我的想法:这是正确的方法吗?1)生成一个在我指定范围内的随机整数2)将生成的数除以pi以得到float / double随