【问题标题】:How do I use filter_map() rather then filter() combined with map() without performance decreasing?如何在不降低性能的情况下使用 filter_map() 而不是 filter() 与 map() 结合使用?
【发布时间】:2020-08-11 18:44:56
【问题描述】:

我想在map()filter() 中使用filter_map() 而不是unwrap(),但是这样做会降低性能。如何在不损失性能的情况下使用filter_map() 编写代码?为什么首先会出现性能损失?

src/lib.rs

use std::collections::HashMap;

pub enum Kind {
    Square(Square),
    Circle(Circle),
}

#[derive(Default, Copy, Clone)]
pub struct Circle {
    a: u32,
    b: u32,
    c: u32,
    d: u32,
}

#[derive(Default)]
pub struct Square {
    a: u32,
    b: Option<u32>,
    c: Option<u32>,
    d: Option<u32>,
    e: Option<u32>,
}

impl Kind {
    pub fn get_circle(&self) -> Option<&Circle> {
        if let Kind::Circle(b) = self {
            return Some(b);
        }
        None
    }
}

benches/test.rs

#![feature(test)]
extern crate test;

#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use std::net::{IpAddr, Ipv4Addr, SocketAddr};
    use test::Bencher;
    use testing::Circle;
    use testing::Kind;
    use testing::Square;

    fn get_bencher() -> HashMap<SocketAddr, Kind> {
        let mut question = HashMap::new();
        let square: Square = Default::default();
        question.insert(
            SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 0),
            Kind::Square(square),
        );

        let circle: Circle = Default::default();
        for n in 1..=10000 {
            let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), n);
            question.insert(socket, Kind::Circle(circle));
        }
        question
    }

    #[bench]
    fn bencher01(b: &mut Bencher) {
        let question = get_bencher();

        b.iter(|| {
            question
                .iter()
                .map(|a| (a.0, a.1.get_circle()))
                .filter_map(|(&a, b)| Some((a, b?)))
                .collect::<Vec<_>>()
        })
    }

    #[bench]
    fn bencher02(b: &mut Bencher) {
        let question = get_bencher();

        b.iter(|| {
            question
                .iter()
                .map(|a| (a.0, a.1.get_circle()))
                .filter(|c| c.1.is_some())
                .map(|d| (*d.0, d.1.unwrap()))
                .collect::<Vec<_>>()
        })
    }

    #[bench]
    fn bencher03(b: &mut Bencher) {
        let question = get_bencher();

        b.iter(|| {
            question
                .iter()
                .filter_map(|a| Some((*a.0, a.1.get_circle()?)))
                .collect::<Vec<_>>()
        })
    }
}

使用 Rust nightly 和 cargo bench 运行这些测试,这会强制发布模式。

输出

running 3 tests
test tests::bencher01 ... bench:     201,978 ns/iter (+/- 12,787)
test tests::bencher02 ... bench:      89,004 ns/iter (+/- 6,204)
test tests::bencher03 ... bench:     238,569 ns/iter (+/- 6,004)

我在Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz Linux #### 5.6.4-arch1-1 #1 SMP PREEMPT Mon, 13 Apr 2020 12:21:19 +0000 x86_64 GNU/Linux 上使用rustc 1.44.0-nightly (6dee5f112 2020-04-06)

【问题讨论】:

  • 你的分析器说什么是慢的部分?

标签: rust hashmap iterator


【解决方案1】:

不同之处在于,在您的flat_map 实现中,您在检查形状是否为圆形之前复制了SocketAddr,因此当形状不是圆形时,您会浪费时间复制和丢弃它。见:

#[bench]
fn bencher04(b: &mut Bencher) {
    let question = get_bencher();

    b.iter(|| {
        question
            .iter()
            .filter_map(|a| {
                let c = a.1.get_circle()?;
                Some((*a.0, c))
            })
            .collect::<Vec<_>>()
    })
}

这给了我:

running 4 tests
test tests::bencher01 ... bench:     339,720 ns/iter (+/- 23,464)
test tests::bencher02 ... bench:     329,727 ns/iter (+/- 12,212)
test tests::bencher03 ... bench:     335,785 ns/iter (+/- 16,195)
test tests::bencher04 ... bench:     327,622 ns/iter (+/- 20,807)

注意:对我来说差异比你小,因为我在 32 位平台上运行,在该平台上复制 SocketAddr 的速度要快得多。


在 64 位平台上,bencher04 的性能不佳。查看generated assemblybencher04 看起来与bencher02 非常相似,但由于某种原因它确实移动了更多数据。

但是:

#[bench]
fn bencher05(b: &mut Bencher) {
    let question = get_bencher();

    b.iter(|| {
        question
            .iter()
            .flat_map(|a| {
                a.1.get_circle().map (|c| (*a.0, c))
            })
            .collect::<Vec<_>>()
        })
    }

性能更接近:

running 5 tests
test tests::bencher01 ... bench:     219,381 ns/iter (+/- 10,186)
test tests::bencher02 ... bench:     148,273 ns/iter (+/- 3,068)
test tests::bencher03 ... bench:     244,614 ns/iter (+/- 3,057)
test tests::bencher04 ... bench:     209,905 ns/iter (+/- 7,509)
test tests::bencher05 ... bench:     167,143 ns/iter (+/- 5,029)

【讨论】:

  • 感谢您的回答,但是我运行了您在我的系统上发布的代码,但我仍然看到性能下降:运行 4 个测试 test tests::bencher01 ... bench: 208,874 ns/ iter (+/- 35,134) 测试测试::bencher02 ... bench: 94,463 ns/iter (+/- 39,286) 测试测试::bencher03 ... bench: 259,229 ns/iter (+/- 52,471) 测试测试: :bencher04 ... bench: 213,174 ns/iter (+/- 46,104)
  • 您的编辑创建的性能与 unwrap() 相同,已被接受。非常感谢。
  • 你的 05 比你的 04 慢得多(90,690 ns/iter (+/- 8,639) vs 169,073 ns/iter (+/- 32,725))。事实上,05 是所有这些实现中最慢的,几乎慢了 50%。
  • 我得到了以下结果: test tests::bencher01 ... bench: 198,970 ns/iter (+/- 8,671) test tests::bencher02 ... bench: 87,531 ns/iter (+ /- 2,857) 测试测试::bencher03 ... bench: 237,512 ns/iter (+/- 9,739) 测试测试::bencher04 ... bench: 198,910 ns/iter (+/- 6,194) 测试测试::bencher05 。 .. 工作台:146,388 ns/iter (+/- 13,196)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-08
  • 2013-10-03
  • 1970-01-01
  • 2020-10-31
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多