Как использовать filter_map (), а не filter () в сочетании с map () без снижения производительности? - PullRequest
4 голосов
/ 28 апреля 2020

Я бы хотел использовать filter_map() вместо unwrap() в map() и filter(), но при этом я вижу снижение производительности. Как мне написать код, используя 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, который включает режим разблокировки.

output

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)

Я использую rustc 1.44.0-nightly (6dee5f112 2020-04-06) на 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

1 Ответ

2 голосов
/ 29 апреля 2020

Разница заключается в том, что в ваших 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 также не работает. Глядя на сгенерированную сборку , bencher04 выглядит очень похоже на 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...