Home > Mobile >  Is there some way to improve the performance of passing closures in Rust?
Is there some way to improve the performance of passing closures in Rust?

Time:10-06

I have to basically go through a picture and apply different filters to it. The basic code I use for that looks like this and runs for my example in ~0.37 seconds:

    let mut changed = true;
    while changed {
        changed = false;
        // I need to clone here so I dont use the changed data
        let past = self.grid.clone();
        // My data is in a vector, this gives me all x/y combinations
        for (x, y) in GridIndexIter::from(0, self.width as i32, 0, self.height as i32) {
            // This gives me the index of the current piece
            let i = self.ind(x, y);
            let root = past[i];
            // Here I apply my filter (if no value, set it to a neighboring value)
            if root.elevation == None {
                let surround: Option<Tile> = GridIndexIter::from(-1, 2, -1, 2)
                    .map(|(x_add, y_add)| past[self.ind(x   x_add, y   y_add)])
                    .filter(|tile| tile.elevation != None)
                    .nth(0);
                if let Some(tile) = surround {
                    self.grid[i].elevation = tile.elevation;
                    changed = true;
                }
            }
        }
    }

But because I want to run multiple filters, all of which are applied the same way but differ in the actual calculation (I might want to smooth the values and so on), I tried to split it into the basic logic of applying a filter and the logic of that filter, which I would set as a closure, but now it takes ~4,5 seconds for my example:

fn apply_filter(&mut self, condition: fn(Tile) -> bool, filter: fn(Vec<Tile>) -> Option<Tile>) {
    let mut changed = true;
    while changed {
        changed = false;
        let past = self.grid.clone();
        for (x, y) in GridIndexIter::from(0, self.width as i32, 0, self.height as i32) {
            let i = self.ind(x, y);
            let root = past[i];
            if condition(root) {
                if let Some(tile) = filter(GridIndexIter::from(-1, 2, -1, 2)
                        .map(|(x_add, y_add)| past[self.ind(x   x_add, y   y_add)])
                        .collect()) {
                    self.grid[i].elevation = tile.elevation;
                    changed = true;
                }
            }
        }
    }
}

self.apply_filter(|tile| tile.elevation == None,
        |past| {
            if let Some(tile) = past.iter().filter(|tile| tile.elevation != None).nth(0) {
                return Some(Tile {
                    elevation: tile.elevation,
                    ..past[4]
                });
            } None
        }
    );

Did I make a mistake? Is there some way to make closure more efficient? Is there another way to achieve the same?

CodePudding user response:

Since it's very hard to reproduce your code, I just wanted to suggest a version that is a bit more clear, and maybe easier to debug. Obviously this is all untested, probably doesn't compile

loop {
    let past = self.grid.clone();
    let updates_count = GridIndexIter::from(0, self.width as i32, 0, self.height as i32)
        .enumerate()
        .filter(|(i,_)| &past[i].elevation.is_none()) // captures &past
        .filter_map(|(i,(x,y))| 
            GridIndexIter::from(-1, 2, -1, 2) // captures &self, &past
                .map(|(x_add, y_add)| &past[self.ind(x   x_add, y   y_add)])
                .filter(|tile| tile.elevation.is_some())
                .nth(0)
                .map(|tile| (i,tile))) // either Some((i,tile)) or None
        .map(|(i,tile)| self.grid[i].elevation = tile.elevation)
        .count();
    if updates_count == 0 {
        break;
    }
}

The version with closures could look like that (notice how filter is defined, it accepts iterators, not vectors)

fn apply_filter(&mut self, condition: fn(Tile) -> bool, filter: fn(impl Iterator<Item=Tile>) -> Option<Tile>) {
    loop {
        let past = self.grid.clone();
        let updates_count = GridIndexIter::from(0, self.width as i32, 0, self.height as i32)
            .enumerate()
            .filter(|(i,_)| condition(&past[i])) // captures &past
            .filter_map(|(i,(x,y))| 
                filter(GridIndexIter::from(-1, 2, -1, 2) // captures &self, &past
                    .map(|(x_add, y_add)| &past[self.ind(x   x_add, y   y_add)]))
                .map(|tile| (i,tile))) // either Some((i,tile)) or None
            .map(|(i,tile)| self.grid[i].elevation = tile.elevation)
            .count();
        if updates_count == 0 {
            break;
        }
    }
}

-- EDIT: The following, is a simplified version of how would an iterator could be passed to a capture: Playground of the following code

use std::ops::Fn;

#[derive(PartialEq, Debug)]
struct Tile(i32);

type TilesIter<'a> = &'a mut dyn Iterator<Item=Tile>;

fn apply_function<'a>(func: &'a dyn Fn(TilesIter) -> Option<Tile>) -> Option<Tile> {
    let tiles = vec![Tile(0), Tile(1), Tile(2), Tile(3)];
    func(&mut tiles.into_iter())
}


fn main() {
    let result_tile = apply_function(&|iter: TilesIter| iter.last());
    println!("{:?}", result_tile); // Some(Tile(3))
    assert_eq!(result_tile, Some(Tile(3)));
}
  • Related