Functional programming 函数式编程是零成本的吗?
我对Rust中的函数式编程性能进行了一些测试:Functional programming 函数式编程是零成本的吗?,functional-programming,rust,Functional Programming,Rust,我对Rust中的函数式编程性能进行了一些测试: extern crate rand; // 0.5.5 use rand::Rng; fn time(f: impl FnOnce()) -> std::time::Duration { let s = std::time::Instant::now(); f(); s.elapsed() } fn main() { let iteration = 10000000; let mut rng =
extern crate rand; // 0.5.5
use rand::Rng;
fn time(f: impl FnOnce()) -> std::time::Duration {
let s = std::time::Instant::now();
f();
s.elapsed()
}
fn main() {
let iteration = 10000000;
let mut rng = rand::thread_rng();
println!(
"while: {:?}",
time(|| {
let mut i = 0;
let mut num = 0i64;
while i < iteration {
num += rng.gen::<i64>();
i += 1;
}
})
); // 29.116528ms
println!(
"for: {:?}",
time(|| {
let mut num = 0i64;
for _ in 0..iteration {
num += rng.gen::<i64>();
}
})
); // 26.68407ms
println!(
"fold: {:?}",
time(|| {
rng.gen_iter::<i64>().take(iteration).fold(0, |x, y| x + y);
})
); // 26.065936ms
}
extern板条箱兰德;//0.5.5
使用rand::Rng;
fn time(f:impl FnOnce())->std::time::Duration{
让s=std::time::Instant::now();
f();
s、 过去的()
}
fn main(){
设迭代次数=10000000;
让mut rng=rand::thread_rng();
普林顿(
“while:{:?}”,
时间{
设muti=0;
让mut num=0i64;
而我却在重复{
num+=rng.gen::();
i+=1;
}
})
);//29.116528ms
普林顿(
“for:{:?}”,
时间{
让mut num=0i64;
对于0.迭代中的u{
num+=rng.gen::();
}
})
);//26.68407ms
普林顿(
“折叠:{:?}”,
时间{
国际热核实验堆:取(迭代),折叠(0,| x,y | x+y);
})
);//26.065936ms
}
我已经设置了优化标志来编译它
这三种情况花费的时间几乎相同,这是否意味着Rust中的函数编程是零成本的?通常,fold(reduce)可以编译成等效的高效手工编译代码,从而节省程序员的时间。值得注意的是,折叠中的递归处于尾部位置,因此它只是编写循环的一种更简单的方法
并非所有以函数式编写的程序都是如此。标准性能警告像往常一样,您应该根据自己的情况对代码进行基准测试,并了解权衡。从可理解的代码开始,并在必要时加快速度 下面是一些函数,它们被分解成了永不内联的函数。我还阻止了随机数生成器内联,并使迭代计数更小,以便以后使用:
extern crate rand; // 0.5.5
use rand::{distributions::Standard, Rng, RngCore};
const ITERATION: usize = 10000;
#[inline(never)]
fn add_manual(mut rng: impl Rng) -> i64 {
let mut num = 0;
let mut i = 0;
while i < ITERATION {
num += rng.gen::<i64>();
i += 1;
}
num
}
#[inline(never)]
fn add_range(mut rng: impl Rng) -> i64 {
let mut num = 0;
for _ in 0..ITERATION {
num += rng.gen::<i64>();
}
num
}
#[inline(never)]
fn add_fold(mut rng: impl Rng) -> i64 {
rng.sample_iter::<i64, _>(&Standard)
.take(ITERATION)
.fold(0i64, |x, y| x + y)
}
#[inline(never)]
fn add_sum(mut rng: impl Rng) -> i64 {
rng.sample_iter::<i64, _>(&Standard).take(ITERATION).sum()
}
// Prevent inlining the RNG to create easier-to-inspect LLVM IR
struct NoInlineRng<R: Rng>(R);
impl<R: Rng> RngCore for NoInlineRng<R> {
#[inline(never)]
fn next_u32(&mut self) -> u32 {
self.0.next_u32()
}
#[inline(never)]
fn next_u64(&mut self) -> u64 {
self.0.next_u64()
}
#[inline(never)]
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.0.fill_bytes(dest)
}
#[inline(never)]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> {
self.0.try_fill_bytes(dest)
}
}
fn main() {
let mut rng = NoInlineRng(rand::thread_rng());
let a = add_manual(&mut rng);
let b = add_range(&mut rng);
let c = add_fold(&mut rng);
let d = add_sum(&mut rng);
println!("{}, {}, {}, {}", a, b, c, d);
}
结果是:
测试台\u添加\u手册。。。实验台:28058纳秒/国际热核实验堆(+/-3552)
测试台\u添加\u范围。。。试验台:28349NS/iter(+/-6663)
测试台\u添加\u总和。。。试验台:29807 ns/iter(+/-2016)
这对我来说几乎是一样的。我想说的是,在这种情况下,在这个时间点上,性能没有显著的差异。然而,这并不适用于函数式中所有可能的代码位。什么意义上的零成本?与手动编译的命令式代码等价物相比,零额外成本?这将假定编译器总是可以内联和专门化高阶函数,包括多态类型,这似乎很难保证。所有这一切对我来说都表明,随机数生成器所花费的时间比外观技术之间的任何差异都要长。是的,LLVM优化器可以对迭代器进行优化,以获得正确、快速的命令式代码,是的,Rust和LLVM进行了大量的内联。然而,我并没有资格告诉你到底使用了哪些优化技术。事实上,我不知道如何使这种比较更合理。我不知道如何控制编译器只优化代码的正确部分。欢迎提出任何建议。我的答案非常简短,但希望以后值得编辑,并用示例填写。
#[bench]
fn bench_add_manual(b: &mut Bencher) {
b.iter(|| {
let rng = rand::thread_rng();
add_manual(rng)
});
}
#[bench]
fn bench_add_range(b: &mut Bencher) {
b.iter(|| {
let rng = rand::thread_rng();
add_range(rng)
});
}
#[bench]
fn bench_add_sum(b: &mut Bencher) {
b.iter(|| {
let rng = rand::thread_rng();
add_sum(rng)
});
}