为什么在 Go 中交换 []float64 的元素比在 Rust 中交换 Vec<f64> 的元素更快?
Why is swapping elements of a []float64 in Go faster than swapping elements of a Vec<f64> in Rust?
我有两个(等效的?)程序,一个在 Go 中,另一个在 Rust 中。平均执行时间为:
- 开始~169 毫秒
- 生锈~201ms
去
package main
import (
"fmt"
"time"
)
func main() {
work := []float64{0.00, 1.00}
start := time.Now()
for i := 0; i < 100000000; i++ {
work[0], work[1] = work[1], work[0]
}
elapsed := time.Since(start)
fmt.Println("Execution time: ", elapsed)
}
生锈
我用--release
编译
use std::time::Instant;
fn main() {
let mut work: Vec<f64> = Vec::new();
work.push(0.00);
work.push(1.00);
let now = Instant::now();
for _x in 1..100000000 {
work.swap(0, 1);
}
let elapsed = now.elapsed();
println!("Execution time: {:?}", elapsed);
}
在这种情况下,Rust 的性能是否低于 Go? Rust 程序可以用惯用的方式编写,以更快地执行吗?
Could the Rust program be written in an idiomatic way, to execute faster?
是的。要创建包含几个元素的向量,请使用 vec![]
宏:
let mut work: Vec<f64> = vec![0.0, 1.0];
for _x in 1..100000000 {
work.swap(0, 1);
}
那么这段代码是否更快?是的。看看 what assembly is generated:
example::main:
mov eax, 99999999
.LBB0_1:
add eax, -11
jne .LBB0_1
ret
在我的 PC 上,它的运行速度比您的原始代码快 30 倍。
为什么程序集仍然包含这个什么都不做的循环?为什么编译器无法看到两个 push
与 vec![0.0, 1.0]
相同?这两个问题都很好,而且都可能指向 LLVM 或 Rust 编译器中的缺陷。
但是,遗憾的是,您的微基准测试没有太多有用的信息。基准测试很难,真的很难。有太多的陷阱,连专业人士都会掉进去。就您而言,基准测试在几个方面存在缺陷。首先,您以后永远不会观察向量的内容(它从未被使用过)。这就是为什么一个好的编译器可以删除所有甚至触及向量的代码(就像上面的 Rust 编译器所做的那样)。所以这不好。
除此之外,这与任何真正的性能关键代码都不相似。即使稍后会观察到向量,奇数次交换也等于单次交换。所以除非你想看看优化器是否能理解这个交换规则,否则遗憾的是你的基准测试并不是很有用。
(不是答案)但为了补充 Lukas 写的内容,
这是 Go 1.11 generates
对于循环本身:
xorl CX, CX
movsd 8(AX), X0
movsd (AX), X1
movsd X0, (AX)
movsd X1, 8(AX)
incq CX
cmpq CX, 0000000
jlt 68
(由 https://godbolt.org 提供)
无论哪种情况,请注意,您测量的时间很可能主要由进程的启动和初始化决定,因此您实际上并未测量循环执行的速度。 IOW 你的方法不正确。
我有两个(等效的?)程序,一个在 Go 中,另一个在 Rust 中。平均执行时间为:
- 开始~169 毫秒
- 生锈~201ms
去
package main
import (
"fmt"
"time"
)
func main() {
work := []float64{0.00, 1.00}
start := time.Now()
for i := 0; i < 100000000; i++ {
work[0], work[1] = work[1], work[0]
}
elapsed := time.Since(start)
fmt.Println("Execution time: ", elapsed)
}
生锈
我用--release
use std::time::Instant;
fn main() {
let mut work: Vec<f64> = Vec::new();
work.push(0.00);
work.push(1.00);
let now = Instant::now();
for _x in 1..100000000 {
work.swap(0, 1);
}
let elapsed = now.elapsed();
println!("Execution time: {:?}", elapsed);
}
在这种情况下,Rust 的性能是否低于 Go? Rust 程序可以用惯用的方式编写,以更快地执行吗?
Could the Rust program be written in an idiomatic way, to execute faster?
是的。要创建包含几个元素的向量,请使用 vec![]
宏:
let mut work: Vec<f64> = vec![0.0, 1.0];
for _x in 1..100000000 {
work.swap(0, 1);
}
那么这段代码是否更快?是的。看看 what assembly is generated:
example::main:
mov eax, 99999999
.LBB0_1:
add eax, -11
jne .LBB0_1
ret
在我的 PC 上,它的运行速度比您的原始代码快 30 倍。
为什么程序集仍然包含这个什么都不做的循环?为什么编译器无法看到两个 push
与 vec![0.0, 1.0]
相同?这两个问题都很好,而且都可能指向 LLVM 或 Rust 编译器中的缺陷。
但是,遗憾的是,您的微基准测试没有太多有用的信息。基准测试很难,真的很难。有太多的陷阱,连专业人士都会掉进去。就您而言,基准测试在几个方面存在缺陷。首先,您以后永远不会观察向量的内容(它从未被使用过)。这就是为什么一个好的编译器可以删除所有甚至触及向量的代码(就像上面的 Rust 编译器所做的那样)。所以这不好。
除此之外,这与任何真正的性能关键代码都不相似。即使稍后会观察到向量,奇数次交换也等于单次交换。所以除非你想看看优化器是否能理解这个交换规则,否则遗憾的是你的基准测试并不是很有用。
(不是答案)但为了补充 Lukas 写的内容, 这是 Go 1.11 generates 对于循环本身:
xorl CX, CX
movsd 8(AX), X0
movsd (AX), X1
movsd X0, (AX)
movsd X1, 8(AX)
incq CX
cmpq CX, 0000000
jlt 68
(由 https://godbolt.org 提供)
无论哪种情况,请注意,您测量的时间很可能主要由进程的启动和初始化决定,因此您实际上并未测量循环执行的速度。 IOW 你的方法不正确。