使用带有矢量块的crossbeam范围线程

时间:2016-01-07 21:15:12

标签: parallel-processing rust

我有一个元组向量,我将其拆分为块,然后将每个块移到自己的线程进行处理,并在最后重新组合它们。 我一直在使用以下使用std::thread的代码,但它运行良好,但需要进行大量克隆并重新构建我想要消除的代码。

// convert_bng is just an expensive function
let orig: Vec<(&f32, &f32)>
// orig gets populated here
let mut guards: Vec<JoinHandle<Vec<(i32, i32)>>> = vec![];
    // split into slices
    let mut size = orig.len() / NUMTHREADS;
    if orig.len() % NUMTHREADS > 0 {
        size += 1;
    }
    // if orig.len() == 0, we need another adjustment
    size = std::cmp::max(1, size);
    for chunk in orig.chunks(size) {
        let chunk = chunk.to_owned();
        let g = thread::spawn(move || {
            chunk.into_iter()
                 .map(|elem| convert_bng(elem.0, elem.1))
                 .collect()
        });
        guards.push(g);
    }
    let mut result: Vec<IntTuple> = Vec::with_capacity(orig.len());
    for g in guards {
        result.extend(g.join()
                       .unwrap()
                       .into_iter()
                       .map(|ints| {
                           IntTuple {
                               a: ints.0 as u32,
                               b: ints.1 as u32,
                           }
                       }));
    }

是否可以使用crossbeam或scoped_threadpool简化此操作?类似的东西:

let mut size = orig.len() / NUMTHREADS;
if orig.len() % NUMTHREADS > 0 {
    size += 1;
}
size = std::cmp::max(1, size);
    crossbeam::scope(|scope| {
        for chunk in orig.chunks_mut(size) {
            scope.spawn(move || chunk.iter().map(|elem| convert_bng(elem.0, elem.1)).collect());
        }
    });
    let mut result = orig.into_iter()
                     .map(|ints| {
                         IntTuple {
                             a: ints.0 as u32,
                             b: ints.1 as u32,
                         }
                     })
                     .collect();
}

(使用shepmaster链接的问题代码编辑)

但是,这在let result …中给出了一个错误:
casting &f32 as u32 is invalid,表示scope.spawn()内的地图调用未将其结果返回到块中,或者由于类型不匹配而被丢弃(函数返回(i32, i32),但是orig持有(&f32, &f32))。如果我用result替换有效的虚拟向量,我会得到与spawn相关的完全不同的错误:

error: unable to infer enough type information about `_`; type annotations or generic parameter binding required [E0282]
scope.spawn(move || chunk.iter().map(|elem| convert_bng(elem.0, elem.1)).collect());

2 个答案:

答案 0 :(得分:2)

  

表示scope.spawn()内的地图调用未将其结果返回到块中

这是真的,因为那不是那种方法的运作方式。一般的想法是你建立一个新的范围,然后你可以产生保证在该范围之前结束的线程。

crossbeam::scope确定了范围,is defined as

pub fn scope<'a, F, R>(f: F) -> R 
    where F: FnOnce(&Scope<'a>) -> R

也就是说,你给scope一个闭包。封闭将被引用Scope。无论闭包返回什么都将从`scope。

返回

在该闭包中,您可以使用Scope::spawn生成线程:

fn spawn<F, T>(&self, f: F) -> ScopedJoinHandle<T> 
    where F: FnOnce() -> T + Send + 'a,
          T: Send + 'a

spawn采用不带参数的闭包并返回某种类型。该类型将是spawn方法的结果(模块化一些错误处理)。

任何横梁都不会固有地修改任何数据,这取决于您的代码。那么让我们来看看你在线程中做了什么:

chunk.iter().map(|elem| convert_bng(elem.0, elem.1)).collect()

你拿走&mut [T]并迭代它,通过将每个元素传递给convert_bng来转换它们。您没有convert_bng的定义,所以我们假设它返回bool。然后你collect这个bool的迭代器let foo: ConcreteCollection = iter.collect()。但是,您可以定位许多可能的集合,因此我们需要知道所需的具体集合。在大多数情况下,这可以通过类似collect的方式完成。

由于此spawn是最后一个表达式,因此它也是iterator.collect();的返回值,因此我们可以查看如何使用该返回值。原来它不是,这相当于只说chunks_mut,它没有足够的信息可以编译。

除此之外,您需要决定是否尝试就地修改集合。您执行convert_bng这一事实似乎表明您希望每个线程在没有任何额外分配的情况下对向量执行工作。但是,您忽略它是可变的并返回新分配的集合(不确定类型)。由于我不知道map返回什么,因此很难说它是否可能。此外, void goShowResource(final Form previousForm) { previous = previousForm; final Toolbar bar = new Toolbar(); final Form rd = new Form("resource details"); final Resource thisResource = this; rd.setToolbar(bar); bar.addCommandToSideMenu(new Command("command 1") { @Override public void actionPerformed(ActionEvent evt) { AddResources ar = new AddResources(settings, thisResource); ar.goAddResources(rd); } }); bar.addCommandToSideMenu(new Command("command 2") { @Override public void actionPerformed(ActionEvent evt){ UpdateResource ur = new UpdateResource(settings); ur.goUpdateResource(rd, thisResource); } }); rd.setLayout(new BoxLayout(BoxLayout.Y_AXIS)); showDetails(rd); rd.show(); } 仅在将类型转换为新类型时使用,而不是在适当位置改变值。你不能在原地改变切片以在其中放置不同的类型,因为它不再是切片!

答案 1 :(得分:0)

我目前采用的方法是将convert_bng重写为以下签名:
fn convert_bng(longitude: &f32, latitude: &f32) -> (i32, i32) { … }

然后允许我使用crossbeam如下:

pub extern "C" fn convert_to_bng(longitudes: Array, latitudes: Array) -> Array {
    let orig: Vec<(&f32, &f32)> = // orig is populated here
    let mut result = vec![(1, 1); orig.len()];
    let mut size = orig.len() / NUMTHREADS;
    if orig.len() % NUMTHREADS > 0 {
        size += 1;
    }
    size = std::cmp::max(1, size);
    crossbeam::scope(|scope| {
        for (res_chunk, orig_chunk) in result.chunks_mut(size).zip(orig.chunks(size)) {
            scope.spawn(move || {
                for (res_elem, orig_elem) in res_chunk.iter_mut().zip(orig_chunk.iter()) {
                    *res_elem = convert_bng(orig_elem.0, orig_elem.1);
                }
            });
        }
    });
    Array::from_vec(result)
}

此代码更加清晰简单,并且以与原始代码基本相同的速度运行(平均而言,其速度<1%)。

使用预先填充的结果向量对我来说似乎并不理想,但尝试在原位变异orig的方法似乎并不富有成效:convert_bng&#39; s返回类型与orig不可避免地有所不同,并且目前试图解决这个问题超出了我的范围。