Build tree from formatted input - rust

I have been having a problem trying to come up with a solution to read a binary tree from a formatted input and build said tree in Rust. The borrow checker has been driving crazy and thus decided to take to the community.
Basically, the input looks like this:
1 2 3 4 5 6 N N 7 8
and it represents a tree that looks like this:
1
/ \
2 3
/ \ / \
4 5 6 N
/ \ /
N 7 8
with N meaning NULL.
To read this, in CPP I would usually read this tree by level doing kind of a Breadth first building of the tree using a queue to do so.
I was attempting the same approach in Rust but that is where hell broke lose on me. I am a beginner in Rust and of course I am being scolded by the borrow checker.
I am using the following TreeNode structure:
#[derive(Debug, PartialEq, Eq)]
pub struct TreeNode {
pub val: i32,
pub left: Option<Rc<RefCell<TreeNode>>>,
pub right: Option<Rc<RefCell<TreeNode>>>,
}
impl TreeNode {
#[inline]
pub fn new(val: i32) -> Self {
TreeNode {
val,
left: None,
right: None
}
}
}
And here is the piece of code that is doing the reading of the tree from STDIN:
fn read_b_tree<T: io::BufRead>(scan: &mut Scanner<T>, size: usize)
-> Result<Option<Rc<RefCell<TreeNode>>>, Box<dyn Error>> {
if size == 0 {
Ok(None)
} else {
let r = scan.token::<String>();
if r == "N" {
Ok(None)
} else {
let mut q = VecDeque::new();
let root = Rc::new(RefCell::new(TreeNode::new(r.parse::<i32>()?)));
q.push_back(&root);
let mut cnt: usize = 1;
while cnt < size && !q.is_empty() {
let node = match q.pop_front() {
Some(node) => Ok(node),
_ => Err("Queue should not be empty"),
}?;
let v = Rc::clone(node);
let left = scan.token::<String>();
let right = scan.token::<String>();
if left != "N" {
let left_n = Rc::new(RefCell::new(TreeNode::new(left.parse::<i32>()?)));
v.borrow_mut().left = Some(Rc::clone(&left_n));
q.push_back(&left_n);
}
cnt += 1;
if right != "N" {
let right_n = Rc::new(RefCell::new(TreeNode::new(right.parse::<i32>()?)));
v.borrow_mut().right = Some(Rc::clone(&right_n));
q.push_back(&right_n);
}
cnt += 1;
}
Ok(Some(root))
}
}
}
As you can imagine, I ran into lifetime issues with this approach, such as:
error[E0597]: `right_n` does not live long enough
--> src/main.rs:146:33
|
125 | while cnt < size && !q.is_empty() {
| - borrow later used here
...
146 | q.push_back(&right_n);
| ^^^^^^^^ borrowed value does not live long enough
147 | }
| - `right_n` dropped here while still borrowed
I would be highly thankful to anyone who could give some pointers as to how I work out of this situation.
Sincerely,

The following code is shorter and demonstrates your problem:
fn read_b_tree() -> Result<Option<Rc<RefCell<TreeNode>>>, Box<dyn Error>> {
let r = String::new();
let mut q = VecDeque::new();
let root = Rc::new(RefCell::new(TreeNode::new(r.parse::<i32>()?)));
q.push_back(&root);
while !q.is_empty() {
if r != "N" {
let left_n = Rc::new(RefCell::new(TreeNode::new(r.parse::<i32>()?)));
q.push_back(&left_n);
}
}
Ok(Some(root))
}
The inferred type of q is VecDeque<&Rc<RefCell<TreeNode>>>, yet there is no reason why you choose this instead of VecDeque<Rc<RefCell<TreeNode>>> (Rc instead of &Rc) - Rc is already a reference so there is no need to do it a second time.
I think you inserted the & because without & there was an error use of moved value .... This error is correct: You use root after moving it to q. But that is not a problem, because you wanted to move a Rc to q and you can easily get one new by just cloning it root.clone() or Rc::clone(&root).
The fixed example is:
fn read_b_tree() -> Result<Option<Rc<RefCell<TreeNode>>>, Box<dyn Error>> {
let r = String::new();
let mut q = VecDeque::new();
let root = Rc::new(RefCell::new(TreeNode::new(r.parse::<i32>()?)));
q.push_back(root.clone());
while !q.is_empty() {
if r != "N" {
let left_n = Rc::new(RefCell::new(TreeNode::new(r.parse::<i32>()?)));
q.push_back(left_n); //this works in the example, but you will have to clone here too
}
}
Ok(Some(root))
}

Related

Borrow inside a loop

I'm trying to learn Rust after many years of C++. I have a situation where the compiler is complaining about a borrow, and it doesn't seem to matter whether it is mutable or immutable. I don't seem to be able to use self as a parameter inside a loop that start with: for item in self.func.drain(..).I've tried calling appropriate() as a function:
Self::appropriate(&self,&item,index)
and I have tried it as a method:
self.appropriate(&item,index)
but I get the same message in either case:
The function or method appropriate() is intended imply examine the relationship among its parameters and return a bool without modifying anything. How can I call either a function or method on self without violating borrowing rules?This program is a learning exercise from exercism.org and doesn't include a main() so it won't run but should almost compile except for the error in question. Here's the code I have:
use std::collections::HashMap;
pub type Value = i32;
pub type Result = std::result::Result<(), Error>;
pub struct Forth {
v: Vec<Value>,
f: HashMap<String,usize>,
s: Vec<Vec<String>>,
func: Vec<String>
}
#[derive(Debug, PartialEq)]
pub enum Error {
DivisionByZero,
StackUnderflow,
UnknownWord,
InvalidWord,
}
impl Forth {
pub fn new() -> Forth {
let mut temp: Vec<Vec<String>> = Vec::new();
temp.push(Vec::new());
Forth{v: Vec::<Value>::new(), f: HashMap::new(), s: temp, func: Vec::new()}
}
pub fn stack(&self) -> &[Value] {
&self.v
}
pub fn eval(&mut self, input: &str) -> Result {
self.v.clear();
self.s[0].clear();
let mut count = 0;
{
let temp: Vec<&str> = input.split(' ').collect();
let n = temp.len() as i32;
for x in 0..n as usize {
self.s[0].push(String::from(temp[x]));
}
}
let mut collecting = false;
let mut xlist: Vec<(usize,usize)> = Vec::new();
let mut sx: usize = 0;
let mut z: i32 = -1;
let mut x: usize;
let mut n: usize = self.s[0].len();
loop {
count += 1;
if count > 20 {break;}
z += 1;
x = z as usize;
if x >= n {break;}
z = x as i32;
let word = &self.s[sx][x];
if word == ";" {
if collecting {
collecting = false;
let index: usize = self.s.len();
self.s.push(Vec::<String>::new());
for item in self.func.drain(..) {
if self.s[index].len() > 0 &&
Self::appropriate(&self,&item,index)
{
let sx = *self.f.get(&self.s[index][0]).unwrap();
let n = self.s[sx].len();
for x in 1..n as usize {
let symbol = self.s[sx][x].clone();
self.s[index].push(symbol);
}
}
else {
self.s[index].push(item);
}
}
self.f.insert(self.s[index][0].clone(), index);
self.func.clear();
continue;
}
if 0 < xlist.len() {
(x, n) = xlist.pop().unwrap();
continue;
}
return Err(Error::InvalidWord);
}
if collecting {
self.func.push(String::from(word));
continue;
}
if Self::is_op(word) {
if self.v.len() < 2 {
return Err(Error::StackUnderflow);
}
let b = self.v.pop().unwrap();
let a = self.v.pop().unwrap();
let c = match word.as_str() {
"+" => a + b,
"-" => a - b,
"*" => a * b,
"/" => {if b == 0 {return Err(Error::DivisionByZero);} a / b},
_ => 0
};
self.v.push(c);
continue;
}
match word.parse::<Value>() {
Ok(value) => { self.v.push(value); continue;},
_ => {}
}
if word == ":" {
collecting = true;
self.func.clear();
continue;
}
if word == "drop" {
if self.v.len() < 1 {
return Err(Error::StackUnderflow);
}
self.v.pop();
continue;
}
if word == "dup" {
if self.v.len() < 1 {
return Err(Error::StackUnderflow);
}
let temp = self.v[self.v.len() - 1];
self.v.push(temp);
continue;
}
if !self.f.contains_key(word) {
return Err(Error::UnknownWord);
}
xlist.push((sx,n));
sx = *self.f.get(word).unwrap();
n = self.s[sx].len();
z = 0;
}
Ok(())
}
fn is_op(input: &str) -> bool {
match input {"+"|"-"|"*"|"/" => true, _ => false}
}
fn appropriate(&self, item:&str, index:usize) -> bool
{
false
}
fn prev_def_is_short(&self, index: usize) -> bool {
if index >= self.s.len() {
false
}
else {
if let Some(&sx) = self.f.get(&self.func[0]) {
self.s[sx].len() == 2
}
else {
false
}
}
}
}
The error message relates to the call to appropriate(). I haven't even written the body of that function yet; I'd like to get the parameters right first. The compiler's complaint is:
As a subroutine call
error[E0502]: cannot borrow `self` as immutable because it is also borrowed as mutable
--> src/lib.rs:85:47
|
81 | for item in self.func.drain(..) {
| -------------------
| |
| mutable borrow occurs here
| mutable borrow later used here
...
85 | Self::appropriate(&self,&item,index)
| ^^^^^ immutable borrow occurs here
For more information about this error, try `rustc --explain E0502`.
as a method call
error[E0502]: cannot borrow `*self` as immutable because it is also borrowed as mutable
--> src/lib.rs:85:29
|
81 | for item in self.func.drain(..) {
| -------------------
| |
| mutable borrow occurs here
| mutable borrow later used here
...
85 | self.appropriate(&item,index)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ immutable borrow occurs here
For more information about this error, try `rustc --explain E0502`.
Is there any canonical way to deal with this situation?
The problem is that self.func.drain() will consume the elements contained in self.func, thus an exclusive (&mut) access is needed on self.func for the entire for loop.
If during the iteration you need to pass a reference to self globally, then its func member is potentially accessible while the loop holds an exclusive access to it: Rust forbids that.
Since you use drain() in order to consume all the elements inside self.func, I suggest you swap this vector with an empty one just before the loop, then iterate on this other vector that is not anymore part of self.
No copy of the content of the vector is involved here; swap() only deals with pointers.
Here is an over-simplified version of your code, adapted consequently.
struct Forth {
func: Vec<String>,
}
impl Forth {
fn eval(&mut self) {
/*
for item in self.func.drain(..) {
self.appropriate(&self);
}
*/
let mut func = Vec::new();
std::mem::swap(&mut self.func, &mut func);
for item in func.drain(..) {
let b = self.appropriate();
println!("{:?} {:?}", item, b);
}
}
fn appropriate(&self) -> bool {
false
}
}
fn main() {
let mut f = Forth {
func: vec!["aaa".into(), "bbb".into()],
};
f.eval();
}

how to correctly return a reference to a mutated linked list in rust?

I am solving a leetcode problem in Rust, it's a linked list problem.
The part that I am stuck at is that I have a working algorithm, but I wasn't able to return from the function, below is my solution
pub fn remove_nth_from_end(head: Option<Box<ListNode>>, n: i32) -> Option<Box<ListNode>> {
let mut cursor = head.clone().unwrap();
let mut count: i32 = 0;
while cursor.next != None {
count += 1;
cursor = cursor.next.unwrap();
}
let mut n = count - n;
let mut new_cursor = head.unwrap();
while n != 0 {
n -= 1;
new_cursor = new_cursor.next.unwrap();
}
new_cursor.next = new_cursor.next.unwrap().next;
head // <- error: used of moved value
}
I first clone the head so that I can iterate through the linked list to get its total number of nodes.
Then, I will have to remove one node from the list, hence I'm not cloning the head, instead I use it directly, in this case the variable is moved. So after I am done removing the node, I would like to return the head, so that I can return the whole linked list.
However, because of the ownership system in rust, I wasn't able to return a moved value. The problem is I couldn't clone the value as well because if I were to clone, then the head is no longer pointing to the linked list where I removed one node from it.
How would one solve this kind of issue in Rust? I am fairly new to Rust, just picked up the language recently.
One way is to use &mut over the nodes and then use Option::take to take ownership of the nodes while leaving None behind. Use those combinations to mutate the list:
impl Solution {
pub fn remove_nth_from_end(mut head: Option<Box<ListNode>>, mut n: i32) -> Option<Box<ListNode>> {
match n {
0 => head.and_then(|node| node.next),
_ => {
let mut new_head = &mut head;
while n > 0 {
new_head = if let Some(next) = new_head {
&mut next.next
} else {
return head;
};
n -= 1;
}
let to_skip = new_head.as_mut().unwrap().next.take();
new_head.as_mut().map(|node| {
node.next = if let Some(mut other_node) = to_skip {
other_node.next.take()
} else {
None
};
});
head
}
}
}
}
Playground
Disclaimer: This do not implement it working from the end of the list but from the beginning of it. Didn't realize that part, but that should be the problem itself.

Casting &i32 as usize

I have a function that is meant to make a move in connect 4, this function takes 3 parameters. The main issue is that the square variable is out of scope in the for loop so it must be borrowed however I cannot cast an &i32 to usize.
fn make_move<'playing>(board: &'playing Vec<&str>, column: i32, turn: &'playing i32) -> &'playing Vec<&'playing str> {
let mut square = column;
let new_board = board;
for i in 0..6 {
if new_board[(&square) as usize] == " " {
square = &square + 7;
} else {
if turn % 2 == 0 {
new_board[(&square - 7) as usize] = "●";
} else {
new_board[(&square - 7) as usize] = "○";
}
}
}
return new_board;
}
error[E0606]: casting `&i32` as `usize` is invalid
--> src/lib.rs:6:22
|
6 | if new_board[(&square) as usize] == " " {
| ---------^^^^^^^^^
| |
| cannot cast `&i32` as `usize`
| help: dereference the expression: `*(&square)`
I also just started using Rust so I am sure my logic is flawed and maybe this is a dumb mistake but help would be much appreciated.
In this case, you don't need the ampersands at all before square. square is an i32, which is an integer type, and you want to turn it into another integer type, so you don't need to borrow it at all. Since i32 is Copy, you can make cheap copies of square without a problem.
Additionally, new_board is making a copy of your reference to board, not copying the actual board. Vec is not Copy, since copies are not cheap, so you'll need to use the clone method to create a new board and then return the actual Vec, not a reference. With the above change and this one, the result would look like this:
fn make_move<'playing>(
board: &'playing Vec<&str>,
column: i32,
turn: &'playing i32,
) -> Vec<&'playing str> {
let mut square = column;
let mut new_board = board.clone();
for i in 0..6 {
if new_board[(square) as usize] == " " {
square = square + 7;
} else {
if turn % 2 == 0 {
new_board[(square - 7) as usize] = "●";
} else {
new_board[(square - 7) as usize] = "○";
}
}
}
return new_board;
}
Alternatively, you can make board a mutable reference, in which case you will modify both the old and new board, but you can continue to return a reference. That would look like this:
fn make_move<'playing>(
board: &'playing mut Vec<&str>,
column: i32,
turn: &'playing i32,
) -> &'playing Vec<&'playing str> {
let mut square = column;
let new_board = board;
for i in 0..6 {
if new_board[(square) as usize] == " " {
square = square + 7;
} else {
if turn % 2 == 0 {
new_board[(square - 7) as usize] = "●";
} else {
new_board[(square - 7) as usize] = "○";
}
}
}
return new_board;
}

How can you modify edge weights for a filtered Petgraph graph?

I am using an edge filter for my graph and want to update an edge weight:
use petgraph::prelude::*;
use petgraph::graph;
use petgraph::visit::{Dfs, EdgeFiltered, IntoEdges};
fn filter_edges(edge: graph::EdgeReference<u32>) -> bool {
match edge.weight() {
0 => true,
_ => false,
}
}
fn main() {
let mut graph: graph::Graph<u32, u32> = graph::Graph::new();
let a = graph.add_node(1);
let b = graph.add_node(2);
let e = graph.add_edge(a, b, 0);
let mut filtered_graph = EdgeFiltered::from_fn(&graph, filter_edges);
let mut dfs = Dfs::new(&filtered_graph, a);
while let Some(node_index) = dfs.next(&filtered_graph) {
for edge in filtered_graph.edges(node_index) {
filtered_graph.update_edge(edge.source(), edge.target(), 1);
//graph.update_edge(edge.source(), edge.target(), 1);
}
}
}
But this errors because EdgeFiltered doesn't have an update_edge function:
error[E0599]: no method named `update_edge` found for struct `EdgeFiltered<&Graph<u32, u32>, for<'r> fn(petgraph::graph::EdgeReference<'r, u32>) -> bool {filter_edges}>` in the current scope
--> src/main.rs:22:28
|
22 | filtered_graph.update_edge(edge.source(), edge.target(), 1);
| ^^^^^^^^^^^ method not found in `EdgeFiltered<&Graph<u32, u32>, for<'r> fn(petgraph::graph::EdgeReference<'r, u32>) -> bool {filter_edges}>`
If I switch to refer to the original graph instead, it has a borrow checker error (unlike Dfs, unfortunately EdgeFiltered isn't designed to let you access the original graph):
error[E0502]: cannot borrow `graph` as mutable because it is also borrowed as immutable
--> src/main.rs:21:13
|
17 | let mut filtered_graph = EdgeFiltered::from_fn(&graph, filter_edges);
| ------ immutable borrow occurs here
18 | let mut dfs = Dfs::new(&filtered_graph, a);
19 | while let Some(node_index) = dfs.next(&filtered_graph) {
| --------------- immutable borrow later used here
20 | for edge in filtered_graph.edges(node_index) {
21 | graph.update_edge(edge.source(), edge.target(), 1);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
Playground link for the above
Edgefiltered is pretty minimal, and doesn't really seem to have anything intended for mutable graph manipulation. Is there any way to do this with what Petgraph comes with, or will I have to write my own version of update_edge showhow?
FilteredGraph borrows the Graph, therefore you cannot get a mutable reference to the Graph as long as FilteredGraph exists.
You can re-create a FilteredGraph on each dfs.next() call to work around this, e.g. this works:
use petgraph::graph;
use petgraph::visit::{Dfs, EdgeFiltered};
fn filter_edges(edge: graph::EdgeReference<u32>) -> bool {
match edge.weight() {
0 => true,
_ => false,
}
}
fn main() {
let mut graph: graph::Graph<u32, u32> = graph::Graph::new();
let a = graph.add_node(1);
let b = graph.add_node(2);
let e = graph.add_edge(a, b, 0);
let filtered_graph = EdgeFiltered::from_fn(&graph, filter_edges);
let mut dfs = Dfs::new(&filtered_graph, a);
while let Some(node_index) = dfs.next(&EdgeFiltered::from_fn(&graph, filter_edges)) {
let mut neighbors = graph.neighbors(node_index).detach();
while let Some((edge_idx, _)) = neighbors.next(&graph) {
graph[edge_idx] = 1;
}
}
}
Note: This will take the neighbors of the given node based on the edges present in graph, not by those present in filtered_graph.
You can solve that by ditching EdgeFiltered and manually handling it in the traversal, e.g. like this:
fn main() {
let mut graph: graph::Graph<u32, u32> = graph::Graph::new();
let a = graph.add_node(1);
let b = graph.add_node(2);
let e = graph.add_edge(a, b, 0);
let mut dfs = Dfs::new(&graph, a);
while let Some(node_index) = dfs.next(&graph) {
let mut neighbors = graph.neighbors(node_index).detach();
while let Some((edge_idx, _)) = neighbors.next(&graph) {
let edge_weight = &mut graph[edge_idx];
if *edge_weight == 0 {
*edge_weight = 1;
}
}
}
}

How to give each CPU core mutable access to a portion of a Vec? [duplicate]

This question already has an answer here:
How do I pass disjoint slices from a vector to different threads?
(1 answer)
Closed 4 years ago.
I've got an embarrassingly parallel bit of graphics rendering code that I would like to run across my CPU cores. I've coded up a test case (the function computed is nonsense) to explore how I might parallelize it. I'd like to code this using std Rust in order to learn about using std::thread. But, I don't understand how to give each thread a portion of the framebuffer. I'll put the full testcase code below, but I'll try to break it down first.
The sequential form is super simple:
let mut buffer0 = vec![vec![0i32; WIDTH]; HEIGHT];
for j in 0..HEIGHT {
for i in 0..WIDTH {
buffer0[j][i] = compute(i as i32,j as i32);
}
}
I thought that it would help to make a buffer that was the same size, but re-arranged to be 3D & indexed by core first. This is the same computation, just a reordering of the data to show the workings.
let mut buffer1 = vec![vec![vec![0i32; WIDTH]; y_per_core]; num_logical_cores];
for c in 0..num_logical_cores {
for y in 0..y_per_core {
let j = y*num_logical_cores + c;
if j >= HEIGHT {
break;
}
for i in 0..WIDTH {
buffer1[c][y][i] = compute(i as i32,j as i32)
}
}
}
But, when I try to put the inner part of the code in a closure & create a thread, I get errors about the buffer & lifetimes. I basically don't understand what to do & could use some guidance. I want per_core_buffer to just temporarily refer to the data in buffer2 that belongs to that core & allow it to be written, synchronize all the threads & then read buffer2 afterwards. Is this possible?
let mut buffer2 = vec![vec![vec![0i32; WIDTH]; y_per_core]; num_logical_cores];
let mut handles = Vec::new();
for c in 0..num_logical_cores {
let per_core_buffer = &mut buffer2[c]; // <<< lifetime error
let handle = thread::spawn(move || {
for y in 0..y_per_core {
let j = y*num_logical_cores + c;
if j >= HEIGHT {
break;
}
for i in 0..WIDTH {
per_core_buffer[y][i] = compute(i as i32,j as i32)
}
}
});
handles.push(handle)
}
for handle in handles {
handle.join().unwrap();
}
The error is this & I don't understand:
error[E0597]: `buffer2` does not live long enough
--> src/main.rs:50:36
|
50 | let per_core_buffer = &mut buffer2[c]; // <<< lifetime error
| ^^^^^^^ borrowed value does not live long enough
...
88 | }
| - borrowed value only lives until here
|
= note: borrowed value must be valid for the static lifetime...
The full testcase is:
extern crate num_cpus;
use std::time::Instant;
use std::thread;
fn compute(x: i32, y: i32) -> i32 {
(x*y) % (x+y+10000)
}
fn main() {
let num_logical_cores = num_cpus::get();
const WIDTH: usize = 40000;
const HEIGHT: usize = 10000;
let y_per_core = HEIGHT/num_logical_cores + 1;
// ------------------------------------------------------------
// Serial Calculation...
let mut buffer0 = vec![vec![0i32; WIDTH]; HEIGHT];
let start0 = Instant::now();
for j in 0..HEIGHT {
for i in 0..WIDTH {
buffer0[j][i] = compute(i as i32,j as i32);
}
}
let dur0 = start0.elapsed();
// ------------------------------------------------------------
// On the way to Parallel Calculation...
// Reorder the data buffer to be 3D with one 2D region per core.
let mut buffer1 = vec![vec![vec![0i32; WIDTH]; y_per_core]; num_logical_cores];
let start1 = Instant::now();
for c in 0..num_logical_cores {
for y in 0..y_per_core {
let j = y*num_logical_cores + c;
if j >= HEIGHT {
break;
}
for i in 0..WIDTH {
buffer1[c][y][i] = compute(i as i32,j as i32)
}
}
}
let dur1 = start1.elapsed();
// ------------------------------------------------------------
// Actual Parallel Calculation...
let mut buffer2 = vec![vec![vec![0i32; WIDTH]; y_per_core]; num_logical_cores];
let mut handles = Vec::new();
let start2 = Instant::now();
for c in 0..num_logical_cores {
let per_core_buffer = &mut buffer2[c]; // <<< lifetime error
let handle = thread::spawn(move || {
for y in 0..y_per_core {
let j = y*num_logical_cores + c;
if j >= HEIGHT {
break;
}
for i in 0..WIDTH {
per_core_buffer[y][i] = compute(i as i32,j as i32)
}
}
});
handles.push(handle)
}
for handle in handles {
handle.join().unwrap();
}
let dur2 = start2.elapsed();
println!("Runtime: Serial={0:.3}ms, AlmostParallel={1:.3}ms, Parallel={2:.3}ms",
1000.*dur0.as_secs() as f64 + 1e-6*(dur0.subsec_nanos() as f64),
1000.*dur1.as_secs() as f64 + 1e-6*(dur1.subsec_nanos() as f64),
1000.*dur2.as_secs() as f64 + 1e-6*(dur2.subsec_nanos() as f64));
// Sanity check
for j in 0..HEIGHT {
let c = j % num_logical_cores;
let y = j / num_logical_cores;
for i in 0..WIDTH {
if buffer0[j][i] != buffer1[c][y][i] {
println!("wtf1? {0} {1} {2} {3}",i,j,buffer0[j][i],buffer1[c][y][i])
}
if buffer0[j][i] != buffer2[c][y][i] {
println!("wtf2? {0} {1} {2} {3}",i,j,buffer0[j][i],buffer2[c][y][i])
}
}
}
}
Thanks to #Shepmaster for the pointers and clarification that this is not an easy problem for Rust, and that I needed to consider crates to find a reasonable solution. I'm only just starting out in Rust, so this really wasn't clear to me.
I liked the ability to control the number of threads that scoped_threadpool gives, so I went with that. Translating my code from above directly, I tried to use the 4D buffer with core as the most-significant-index and that ran into troubles because that 3D vector does not implement the Copy trait. The fact that it implements Copy makes me concerned about performance, but I went back to the original problem and implemented it more directly & found a reasonable speedup by making each row a thread. Copying each row will not be a large memory overhead.
The code that works for me is:
let mut buffer2 = vec![vec![0i32; WIDTH]; HEIGHT];
let mut pool = Pool::new(num_logical_cores as u32);
pool.scoped(|scope| {
let mut y = 0;
for e in &mut buffer2 {
scope.execute(move || {
for x in 0..WIDTH {
(*e)[x] = compute(x as i32,y as i32);
}
});
y += 1;
}
});
On a 6 core, 12 thread i7-8700K for 400000x4000 testcase this runs in 3.2 seconds serially & 481ms in parallel--a reasonable speedup.
EDIT: I continued to think about this issue and got a suggestion from Rustlang on twitter that I should consider rayon. I converted my code to rayon and got similar speedup with the following code.
let mut buffer2 = vec![vec![0i32; WIDTH]; HEIGHT];
buffer2
.par_iter_mut()
.enumerate()
.map(|(y,e): (usize, &mut Vec<i32>)| {
for x in 0..WIDTH {
(*e)[x] = compute(x as i32,y as i32);
}
})
.collect::<Vec<_>>();

Resources