使用多线程后,Conway的生活游戏变慢

时间:2019-12-10 04:00:57

标签: multithreading rust conways-game-of-life

我是Rust的入门者,目前正在撰写平行的Conway的生活游戏。代码本身可以正常工作,但是问题是当使用多个线程时,程序会变慢(我通过计算滑翔器从左上角到右下角的移动时间来测量程序的速度)。我做了一些实验,随着线程数量的增加,它变得越来越慢。我也有使用几乎相同算法的Java版本。它工作正常。我所期望的是,Rust版本在线程数超过一个的情况下至少可以变得更快一点。有人可以指出我做错了什么吗?很抱歉,如果代码看起来不合理,就像我说的我是一个完整的初学者:-)。

main.rs读取命令行参数并进行主板更新。

extern crate clap;
extern crate termion;
extern crate chrono;

use std::thread;
use std::sync::Arc;
use chrono::prelude::*;


mod board;
mod config;

use board::Board;
use config::Config;

fn main() {
    let dt1 = Local::now();

    let matches = clap::App::new("conway")
        .arg(clap::Arg::with_name("length")
            .long("length")
            .value_name("LENGTH")
            .help("Set length of the board")
            .takes_value(true))
        .arg(clap::Arg::with_name("threads")
            .long("threads")
            .value_name("THREADS")
            .help("How many threads to update the board")
            .takes_value(true))
        .arg(clap::Arg::with_name("display")
            .long("display")
            .value_name("DISPLAY")
            .help("Display the board or not")
            .takes_value(true))
        .arg(clap::Arg::with_name("delay")
            .long("delay")
            .value_name("MILLISECONDS")
            .help("Delay between the frames in milliseconds")
            .takes_value(true))
        .get_matches();

    let config = Config::from_matches(matches);
    let mut board = Board::new(config.length);
    let mut start: bool = false;
    let mut end: bool = false;
    let mut start_time: DateTime<Local> = Local::now();
    let mut end_time: DateTime<Local>;

    board.initialize_glider();
    loop {
        if config.display == 1 {
            print!("{}{}", termion::clear::All, termion::cursor::Goto(3, 3));
            board_render(&board);
        }

        if board.board[0][1] == 1 && !start {
            start_time = Local::now();
            start = true;
        }
        if board.board[config.length - 1][config.length - 1] == 1 && !end {
            end_time = Local::now();
            println!("{}", end_time - start_time);
            end = true;
        }

        board = board::Board::update(Arc::new(board), config.threads);
        thread::sleep(config.delay);
    }
}

fn board_render(board: &Board) {
    let mut output = String::with_capacity(board.n * (board.n + 1));
    for i in 0..board.n {
        for j in 0..board.n {
            let ch;
            if board.board[i][j] == 0 {
                ch = '░';
            } else {
                ch = '█';
            }
            output.push(ch);
        }
        output.push_str("\n  ");
    }
    print!("{}", output);
}

board.rs是用于更新具有多个线程的电路板的算法

use std::sync::{Mutex, Arc};
use std::thread;

pub struct Board {
    pub n: usize,
    pub board: Vec<Vec<i32>>,
}

impl Board {
    pub fn new(n: usize) -> Board {
        let board = vec![vec![0; n]; n];
        Board {
            n,
            board,
        }
    }

    pub fn update(Board: Arc<Self>, t_num: usize) -> Board {
        let new_board = Arc::new(Mutex::new(Board::new(Board.n)));
        let mut workers = Vec::with_capacity(t_num);

        let block_size = Board.n / t_num;
        let mut start = 0;

        for t in 0..t_num {
            let old_board = Board.clone();

            let new_board = Arc::clone(&new_board);
            let mut end = start + block_size;

            if t == t_num - 1 { end = old_board.n; }

            let worker = thread::spawn(move || {
                let mut board = new_board.lock().unwrap();
                for i in start..end {
                    for j in 0..old_board.n {
                        let im = (i + old_board.n - 1) % old_board.n;
                        let ip = (i + 1) % old_board.n;
                        let jm = (j + old_board.n - 1) % old_board.n;
                        let jp = (j + 1) % old_board.n;
                        let sum = old_board.board[im][jm] + old_board.board[im][j]
                            + old_board.board[im][jp] + old_board.board[i][jm] + old_board.board[i][jp]
                            + old_board.board[ip][jm] + old_board.board[ip][j] + old_board.board[ip][jp];

                        if sum == 2 {
                            board.board[i][j] = old_board.board[i][j];
                        } else if sum == 3 {
                            board.board[i][j] = 1;
                        } else {
                            board.board[i][j] = 0;
                        }
                    }
                }
            });

            workers.push(worker);
            start = start + block_size;
        }

        for worker in workers {
            worker.join().unwrap();
        }


        let result = new_board.lock().unwrap();
        let mut board = Board::new(Board.n);
        board.board = result.board.to_vec();

        board
    }

    pub fn initialize_glider(&mut self) -> &mut Board {
        self.board[0][1] = 1;
        self.board[1][2] = 1;
        self.board[2][0] = 1;
        self.board[2][1] = 1;
        self.board[2][2] = 1;

        self
    }
}

1 个答案:

答案 0 :(得分:3)

每个工作线程在启动时都会尝试立即锁定互斥锁,直到完成后才释放锁。由于一次只有一个线程可以容纳该互斥锁,因此一次只能有一个线程可以工作。

有两种方法可以解决此问题:

  1. 在您确实需要之前,不要锁定互斥锁。在辅助线程中创建一个临时区域,该区域代表您要更新的块。首先填充划痕区域。 然后锁定互斥锁,将暂存区域的内容复制到new_board中,然后返回。

    使用此方法,大多数工作可以同时完成,但是如果您所有的工人都在大致相同的时间完成工作,他们仍然必须轮流将其全部放入new_board

  2. 完全不使用锁定:将self.board的类型更改为Vec<Vec<AtomicI32>>std::sync::atomic::AtomicI32),并在无需获取锁定的情况下自动更新板。 / p>

    此方法可能会或可能不会减慢更新过程,这可能取决于您使用的内存顺序¹,但它消除了锁争用。

自由放养建议

  • 不要调用变量Board。编译器提醒您的约定是为变量赋予蛇形名称,但除此之外,它令人困惑,因为您还有一个名为Board的类型。我建议实际上只是调用它self,这也可以让您使用方法语法来调用update

  • 不要将整个电路板放在Arc中,因此您可以将其传递给update,然后制作一个新的电路板,而必须将其放置在新的{{1}中下一次迭代。让Arc本身返回一个update,或者让它花费Arc并在其中进行所有的self纠缠。

  • 更好的是,根本不要使用Arc。使用提供scoped threads的板条箱通过引用将您的数据传递到辅助线程。

  • 分配器的性能通常会好于一些大分配,而不是许多小分配。将Arc的类型更改为Board.board并使用算术来计算索引(例如,点Vec<i32>位于索引i, j上。)

  • 如果不需要,最好不要创建并丢弃分配。细胞自动机的典型建议是创建两个包含板状态的缓冲区:当前状态和下一个状态。创建完下一个状态后,只需交换缓冲区,以便当前状态变为下一个状态,反之亦然。

  • j*n + i浪费了空间;您可以使用i32i8,也可以使用enum


¹我建议您bool,除非您真的知道自己在做什么。我怀疑SeqCst可能就足够了,但是我真的不知道我在做什么。