如何在Rust中的自定义单线程迭代器上并行`map(...)`?

时间:2017-02-27 01:23:14

标签: multithreading parallel-processing rust

我有MyReader实施Iterator并生成Buffer s Buffer : SendMyReader很快产生了很多Buffer,但是我有一个CPU密集型工作要在每个Buffer.map(|buf| ...))上执行,这是我的瓶颈,然后收集结果(订购)。我希望将CPU密集型工作并行化 - 希望用于N个线程,这将使用工作窃取来执行它们的速度与核心数量允许的速度一样快。

编辑:更准确。我正在研究rdedupMyStructChunker,它读取io::Read(通常是stdio),查找数据的部分(块)并生成它们。然后,对于每个块,假设map()计算sha256摘要,压缩,加密,保存并返回摘要作为map(...)的结果。已保存数据的摘要用于构建数据的indexmap(...)处理的块之间的顺序无关紧要,但每个map(...)返回的摘要需要按照找到块的顺序收集。实际的save到文件步骤被卸载到另一个线程(写入线程)。 actual code of PR in question

我希望我可以使用rayon,但是rayon期望一个已经可以并行化的迭代器 - 例如。一个Vec<...>或类似的东西。我发现无法从par_iter获得MyReader - 我的读者本质上是单线程的。

simple_parallel,但文档说它不建议用于一般用途。我想确保一切都能正常运作。

我可以采用spmc队列实现和自定义thread_pool,但我正在寻找经过优化和测试的现有解决方案。

还有pipeliner,但还没有支持有序地图。

1 个答案:

答案 0 :(得分:5)

一般而言,就并行化而言,保留顺序是一项非常艰难的要求。

您可以尝试使用典型的扇出/扇入设置进行手工制作:

  • 单个生产者,用顺序单调递增的ID标记输入,
  • 从该生产者消费的线程池,然后将结果发送给最终消费者,
  • 缓冲和重新排序结果的消费者,以便按顺序处理它们。

或者你可以提高抽象水平。

特别感兴趣:Future

Future表示计算的结果,可能已经或可能没有。接收Future的有序列表的消费者可以简单地等待每个消息,并让缓冲在队列中自然发生。

对于奖励积分,如果您使用固定大小的队列,您将自动对消费者施加压力。

因此我建议建立一些CpuPool

设置将是:

use std::sync::mpsc::{Receiver, Sender};

fn produce(sender: Sender<...>) {
    let pool = CpuPool::new_num_cpus();

    for chunk in reader {
        let future = pool.spawn_fn(|| /* do work */);
        sender.send(future);
    }

    // Dropping the sender signals there's no more work to consumer
}

fn consume(receiver: Receiver<...>) {
    while let Ok(future) = receiver.recv() {
        let item = future.wait().expect("Computation Error?");

        /* do something with item */
    }
}

fn main() {
    let (sender, receiver) = std::sync::mpsc::channel();

    std::thread::spawn(move || consume(receiver));

    produce(sender);
}