Functional programming 函数式编程是零成本的吗?

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 =

我对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 = 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)
    });
}