Converting a Bitv to uint - rust

I'm trying to convert a Bitv to uint.
use std::collections::Bitv;
use std::num::Float;
fn main() {
let mut bv = Bitv::with_capacity(3,false);
bv.set(2,true); // Set bit 3
let deci = Vec::from_fn(bv.len(),|i| if bv.get(i) {
(i as f32).exp2()
} else { 0f32 }).iter()
.fold(0u, |acc, &n| acc+n as uint);
println!["{}",deci] // 4
}
That works. Though I wanna know if there is any library function that I'm unaware of or is there any other better way to do.

Some transformations of your code I made.
Don't use floats
Rust 1.0
let vec: Vec<_> = (0..bv.len()).map(|i| {
if bv[i] {
1 << i
} else {
0
}}).collect();
let deci = vec.iter().fold(0, |acc, &n| acc + n);
Original
let vec = Vec::from_fn(bv.len(), |i| {
if bv.get(i) {
1u << i
} else {
0u
}});
let deci = vec.iter().fold(0u, |acc, &n| acc + n);
Don't make an array, just use a tuple
Rust 1.0
let deci = bv.iter()
.fold((0, 0), |(mut acc, nth), bit| {
if bit { acc += 1 << nth };
(acc, nth + 1)
}).0;
Original
let deci = bv.iter()
.fold((0u, 0u), |(mut acc, nth), bit| {
if bit { acc += 1 << nth };
(acc, nth + 1)
}).0;
A bit more usage of iterators
Rust 1.0
let deci = bv.iter()
.enumerate()
.filter_map(|(nth, bit)| if bit { Some(1 << nth) } else { None })
.fold(0, |acc, val| acc + val);
Original
let deci = bv.iter()
.enumerate()
.filter_map(|(nth, bit)| if bit { Some(1 << nth) } else { None })
.fold(0u, |acc, val| acc + val);
Ideally, you could reorganize your code to make use of to_bytes, but the order of bits is different from your example.

Bitv doesn't provide a way to return its value as a uint, because the Bitv might contain more bits than a uint does. BTW, the size of uint is architecture-dependant (32 bits on 32-bit systems, 64 bits on 64-bit systems), so you should prefer using u32 or u64 unless you really need a uint.
Bitv provides the to_bytes and to_bools methods, which return a Vec<u8> and a Vec<bool>, respectively. A Vec<u8> is more compact than a Vec<bool>, so to_bytes should be preferred when the Bitv is known to be large (but if it's known to be large, why would you try converting it to a uint?).
We can also iterate on the bits directly by using iter.
use std::collections::Bitv;
use std::mem;
fn main() {
let mut bv = Bitv::with_capacity(3, false);
bv.set(2, true); // Set bit 3
let deci = bv.iter().enumerate().fold(
0u64,
|accum, (bit_pos, bit)| {
if bit {
assert!(bit_pos < mem::size_of_val(&accum) * 8);
accum + (1 << bit_pos)
} else {
accum
}
});
println!("{}", deci); // 4
}

Related

Accessing Vector elements gives me an error Rust

I trying to write a program that will find the median of any given list.
Eventually, In the FINAL FINAL stretch, an error was shot into my face.
I tried to access elements of a Vector through a variable.
Take a look at the calc_med() function.
use std::io;
use std::sync::Mutex;
#[macro_use]
extern crate lazy_static;
lazy_static! {
static ref num_list: Mutex<Vec<f64>> = Mutex::new(Vec::new());
}
fn main() {
loop {
println!("Enter: ");
let mut inp: String = String::new();
io::stdin().read_line(&mut inp).expect("Failure");
let upd_inp: f64 = match inp.trim().parse() {
Ok(num) => num,
Err(_) => {
if inp.trim() == String::from("q") {
break;
} else if inp.trim() == String::from("d") {
break {
println!("Done!");
calc_med();
};
} else {
continue;
}
}
};
num_list.lock().unwrap().push(upd_inp);
num_list
.lock()
.unwrap()
.sort_by(|a, b| a.partial_cmp(b).unwrap());
println!("{:?}", num_list.lock().unwrap());
}
}
fn calc_med() {
// FOR THE ATTENTION OF STACKOVERFLOW
let n: f64 = ((num_list.lock().unwrap().len()) as f64 + 1.0) / 2.0;
if n.fract() == 0.0 {
let n2: usize = n as usize;
} else {
let n3: u64 = n.round() as u64;
let n4: usize = n3 as usize;
let median: f64 = (num_list[n4] + num_list[n4 - 1]) / 2;
println!("{}", median);
}
}
The error is as following:
Compiling FindTheMedian v0.1.0 (/home/isaak/Documents/Code/Rusty/FindTheMedian)
error[E0608]: cannot index into a value of type `num_list`
--> src/main.rs:50:28
|
50 | let median: f64 = (num_list[n4] + num_list[n4 - 1]) / 2;
| ^^^^^^^^^^^^
error[E0608]: cannot index into a value of type `num_list`
--> src/main.rs:50:43
|
50 | let median: f64 = (num_list[n4] + num_list[n4 - 1]) / 2;
| ^^^^^^^^^^^^^^^^
The current code is trying to index a variable of type Mutex<Vec<f64>>, which is not valid. The way you access the underlying data in a mutex is by calling .lock() on it, which will in turn return a structure that resembles Result<Vec<f64>, Error>.
So, fixing only the line would look like this:
let num_list_vec = num_list.lock().unwrap();
let median: f64 = (num_list_vec[n4] + num_list_vec[n4 - 1]) / 2;
However, since you already locked at the start of the function this will not work, since the mutex is already locked. The best way then is to do the locking + unwraping at the start of the function and use the underlying value in all places:
fn calc_med() {
let num_list_vec = num_list.lock().unwrap();
let n: f64 = ((num_list_vec.len()) as f64 + 1.0) / 2.0;
if n.fract() == 0.0 {
let n2: usize = n as usize;
} else {
let n3: u64 = n.round() as u64;
let n4: usize = n3 as usize;
let median: f64 = (num_list_vec[n4] + num_list_vec[n4 - 1]) / 2;
println!("{}", median);
}
}
Edit: Checking your main, I see you are also lock().unwrap()ing in sequence a lot, which is not the way Mutex should be used. Mutex is mainly used whenever you have a need for multi-threaded programming, so that different threads cannot access the same variable twice. It also incurs a performance hit, so you shouldn't really use it in single-threaded scenarios most of the time.
Unless there's a bigger picture we're missing, you should just define your Vec in main and pass it to calc_med as an argument. If the reason you did what you did was to get it as a global, there are other ways to do that in Rust without performance hits, but due to safe design of Rust these ways are not encouraged and should only be used if you know 100% what you want.
Your error is the num_list is not an vector, it's a mutex with an vector inside of it. To access the value inside of a mutex, you must lock it, and then unwrap the result. You do this correctly in main.
To avoid continually unlocking and locking, it is generally best practice to lock the mutex once, at the start of the function. Rust will automatically drop the lock when the reference goes out of scope. See the updated example:
fn calc_med() { // FOR THE ATTENTION OF STACKOVERFLOW
let nums = num_list.lock().unwrap();
let n: f64 = (nums.len() as f64 + 1.0) / 2.0;
if n.fract() == 0.0 {
let n2: usize = n as usize;
} else {
let n3: u64 = n.round() as u64;
let n4: usize = n3 as usize;
let median: f64 = (nums[n4] + nums[n4 - 1]) / 2;
println!("{}", median);
}
}

How to safely convert float to int in Rust

How can I safely convert a floating point type (e.g. f64) to and integer type (e.g. u64)? In other words I want to convert the number but only if it actually can be represented by the target type.
I found a couple of questions that don't cover this and are not duplicates:
Convert float to integer in Rust
How do I convert between numeric types safely and idiomatically?
The solution is not to use as - that performs a saturating cast. Also u64::try_from(f64) is not implemented.
The closest seems to be f64::to_int_unchecked() but unfortunately it's unsafe. You can easily check the first two safety requirements (not NaN or infinity), but the third is a bit tedious: Be representable in the return type Int, after truncating off its fractional part.
The best I can come up with is to use as to convert it back to f64 and check equality, i.e.
fn convert(x: f64) -> Option<u64> {
let y = x as u64;
if y as f64 == x {
Some(y)
} else {
None
}
}
Is that the best option? Is it implemented anywhere?
For fun, I made an implementation based on the raw f64 bits:
const F64_BITS: u64 = 64;
const F64_EXPONENT_BITS: u64 = 11;
const F64_EXPONENT_MAX: u64 = (1 << F64_EXPONENT_BITS) - 1;
const F64_EXPONENT_BIAS: u64 = 1023;
const F64_FRACTION_BITS: u64 = 52;
pub fn f64_to_u64(f: f64) -> Option<u64> {
let bits = f.to_bits();
let sign = bits & (1 << (F64_EXPONENT_BITS + F64_FRACTION_BITS)) != 0;
let exponent = (bits >> F64_FRACTION_BITS) & ((1 << F64_EXPONENT_BITS) - 1);
let fraction = bits & ((1 << F64_FRACTION_BITS) - 1);
eprintln!("Input: {f}, bits: {bits:b}, sign: {sign}, exponent: {exponent}, fraction: {fraction}");
match (sign, exponent, fraction) {
(_, 0, 0) => {
debug_assert!(f == 0.0);
Some(0)
},
(true, _, _) => {
debug_assert!(f < 0.0);
None
},
(_, F64_EXPONENT_MAX, 0) => {
debug_assert!(f.is_infinite());
None
},
(_, F64_EXPONENT_MAX, _) => {
debug_assert!(f.is_nan());
None
},
(_, 0, _) => {
debug_assert!(f.is_subnormal());
None
},
_ => {
if exponent < F64_EXPONENT_BIAS {
debug_assert!(f < 1.0);
None
} else {
let mantissa = fraction | (1 << F64_FRACTION_BITS);
let left_shift = exponent as i64 - (F64_EXPONENT_BIAS + F64_FRACTION_BITS) as i64;
if left_shift < 0 {
let right_shift = (-left_shift) as u64;
if mantissa & (1 << right_shift - 1) != 0 {
debug_assert!(f.fract() != 0.0);
None
} else {
Some(mantissa >> right_shift)
}
} else {
if left_shift > (F64_BITS - F64_FRACTION_BITS - 1) as i64 {
debug_assert!(f > 2.0f64.powi(63));
None
} else {
Some(mantissa << left_shift)
}
}
}
},
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero() {
assert_eq!(f64_to_u64(0.0), Some(0));
assert_eq!(f64_to_u64(-0.0), Some(0));
}
#[test]
fn positive() {
assert_eq!(f64_to_u64(1.0), Some(1));
assert_eq!(f64_to_u64(2.0), Some(2));
assert_eq!(f64_to_u64(3.0), Some(3));
assert_eq!(f64_to_u64(2.0f64.powi(52)), Some(1 << 52));
assert_eq!(f64_to_u64(2.0f64.powi(53)), Some(1 << 53));
assert_eq!(f64_to_u64(2.0f64.powi(63)), Some(1 << 63));
assert_eq!(f64_to_u64(1.5 * 2.0f64.powi(63)), Some(11 << 62));
assert_eq!(f64_to_u64(1.75 * 2.0f64.powi(63)), Some(111 << 61));
}
#[test]
fn too_big() {
assert_eq!(f64_to_u64(2.0f64.powi(64)), None);
}
#[test]
fn fractional() {
assert_eq!(f64_to_u64(0.5), None);
assert_eq!(f64_to_u64(1.5), None);
assert_eq!(f64_to_u64(2.5), None);
}
#[test]
fn negative() {
assert_eq!(f64_to_u64(-1.0), None);
assert_eq!(f64_to_u64(-2.0), None);
assert_eq!(f64_to_u64(-3.0), None);
assert_eq!(f64_to_u64(-(2.0f64.powi(f64::MANTISSA_DIGITS as i32))), None);
}
#[test]
fn infinity() {
assert_eq!(f64_to_u64(f64::INFINITY), None);
assert_eq!(f64_to_u64(-f64::INFINITY), None);
}
#[test]
fn nan() {
assert_eq!(f64_to_u64(f64::NAN), None);
}
}
Not sure whether this is useful. It's, ahem, slightly more complex than the solutions proposed so far. It may be faster on some hardware, but I doubt it, and haven't bothered to write a benchmark.
Generally, I would defer what is best practice to the relevant lints used by Clippy. Clippy does a good job outlining the possible pitfalls of using x as y and offers possible solutions. These are all of the relevant lints I could find on the subject:
https://rust-lang.github.io/rust-clippy/master/#cast_possible_truncation
https://rust-lang.github.io/rust-clippy/master/#cast_lossless
https://rust-lang.github.io/rust-clippy/master/#cast_possible_wrap
https://rust-lang.github.io/rust-clippy/master/#cast_precision_loss
https://rust-lang.github.io/rust-clippy/master/#cast_sign_loss
However if all you want is to find an answer of mapping f64 onto u64 without any precision loss, there are two conditions you will want to check:
x is an integer
x is within the bounds of the target type
pub fn strict_f64_to_u64(x: f64) -> Option<u64> {
// Check if fractional component is 0 and that it can map to an integer in the f64
// Using fract() is equivalent to using `as u64 as f64` and checking it matches
if x.fract() == 0.0 && x >= u64::MIN as f64 && x <= u64::MAX as f64 {
return Some(x.trunc())
}
None
}

Is there a way to update a string in place in rust?

You can also consider this as, is it possible to URLify a string in place in rust?
For example,
Problem statement: Replace whitespace with %20
Assumption: String will have enough capacity left to accommodate new characters.
Input: Hello how are you
Output: Hello%20how%20are%20you
I know there are ways to do this if we don't have to do this "in place". I am solving a problem that explicitly states that you have to update in place.
If there isn't any safe way to do this, is there any particular reason behind that?
[Edit]
I was able to solve this using unsafe approach, but would appreciate a better approach than this. More idiomatic approach if there is.
fn space_20(sentence: &mut String) {
if !sentence.is_ascii() {
panic!("Invalid string");
}
let chars: Vec<usize> = sentence.char_indices().filter(|(_, ch)| ch.is_whitespace()).map(|(idx, _)| idx ).collect();
let char_count = chars.len();
if char_count == 0 {
return;
}
let sentence_len = sentence.len();
sentence.push_str(&"*".repeat(char_count*2)); // filling string with * so that bytes array becomes of required size.
unsafe {
let bytes = sentence.as_bytes_mut();
let mut final_idx = sentence_len + (char_count * 2) - 1;
let mut i = sentence_len - 1;
let mut char_ptr = char_count - 1;
loop {
if i != chars[char_ptr] {
bytes[final_idx] = bytes[i];
if final_idx == 0 {
// all elements are filled.
println!("all elements are filled.");
break;
}
final_idx -= 1;
} else {
bytes[final_idx] = '0' as u8;
bytes[final_idx - 1] = '2' as u8;
bytes[final_idx - 2] = '%' as u8;
// final_idx is of type usize cannot be less than 0.
if final_idx < 3 {
println!("all elements are filled at start.");
break;
}
final_idx -= 3;
// char_ptr is of type usize cannot be less than 0.
if char_ptr > 0 {
char_ptr -= 1;
}
}
if i == 0 {
// all elements are parsed.
println!("all elements are parsed.");
break;
}
i -= 1;
}
}
}
fn main() {
let mut sentence = String::with_capacity(1000);
sentence.push_str(" hello, how are you?");
// sentence.push_str("hello, how are you?");
// sentence.push_str(" hello, how are you? ");
// sentence.push_str(" ");
// sentence.push_str("abcd");
space_20(&mut sentence);
println!("{}", sentence);
}
An O(n) solution that neither uses unsafe nor allocates (provided that the string has enough capacity), using std::mem::take:
fn urlify_spaces(text: &mut String) {
const SPACE_REPLACEMENT: &[u8] = b"%20";
// operating on bytes for simplicity
let mut buffer = std::mem::take(text).into_bytes();
let old_len = buffer.len();
let space_count = buffer.iter().filter(|&&byte| byte == b' ').count();
let new_len = buffer.len() + (SPACE_REPLACEMENT.len() - 1) * space_count;
buffer.resize(new_len, b'\0');
let mut write_pos = new_len;
for read_pos in (0..old_len).rev() {
let byte = buffer[read_pos];
if byte == b' ' {
write_pos -= SPACE_REPLACEMENT.len();
buffer[write_pos..write_pos + SPACE_REPLACEMENT.len()]
.copy_from_slice(SPACE_REPLACEMENT);
} else {
write_pos -= 1;
buffer[write_pos] = byte;
}
}
*text = String::from_utf8(buffer).expect("invalid UTF-8 during URL-ification");
}
(playground)
Basically, it calculates the final length of the string, sets up a reading pointer and a writing pointer, and translates the string from right to left. Since "%20" has more characters than " ", the writing pointer never catches up with the reading pointer.
Is it possible to do this without unsafe?
Yes like this:
fn main() {
let mut my_string = String::from("Hello how are you");
let mut insert_positions = Vec::new();
let mut char_counter = 0;
for c in my_string.chars() {
if c == ' ' {
insert_positions.push(char_counter);
char_counter += 2; // Because we will insert two extra chars here later.
}
char_counter += 1;
}
for p in insert_positions.iter() {
my_string.remove(*p);
my_string.insert(*p, '0');
my_string.insert(*p, '2');
my_string.insert(*p, '%');
}
println!("{}", my_string);
}
Here is the Playground.
But should you do it?
As discussed for example here on Reddit this is almost always not the recommended way of doing this, because both remove and insert are O(n) operations as noted in the documentation.
Edit
A slightly better version:
fn main() {
let mut my_string = String::from("Hello how are you");
let mut insert_positions = Vec::new();
let mut char_counter = 0;
for c in my_string.chars() {
if c == ' ' {
insert_positions.push(char_counter);
char_counter += 2; // Because we will insert two extra chars here later.
}
char_counter += 1;
}
for p in insert_positions.iter() {
my_string.remove(*p);
my_string.insert_str(*p, "%20");
}
println!("{}", my_string);
}
and the corresponding Playground.

Binary search a vector in chunks

I have a file of ipv4 addresses, which as we know are 4 bytes each. I wish to do a binary search over the file contents to find a given IP address. Rust has a built-in binary search but it doesn't let you pass a len and it instead reads it from the vector.
I have tried to adapt the built-in rust binary search but am a bit lost. This is where i am so far. Maybe there is a way to use the built in method?
fn binary_search(s: &Vec<&u8>, x: &u32) -> Result<usize, usize> {
let f = |p: &[u8]| p.cmp(x); // need to compare byte slices somehow
let mut size = s.len() / 4;
if size == 0 {
return Err(0);
}
let mut base = 0usize;
while size > 1 {
let half = size / 2;
let mid = base + half;
let cmp = f(s[mid..mid+4]);
base = if cmp == Greater { base } else { mid };
size -= half;
}
let cmp = f(s[base..base+4]);
if cmp == Equal {
Ok(base)
} else {
Err(base + (cmp == Less) as usize)
}
}
It’d be better to have a slice with one element per address, either of 4-byte arrays ([u8; 4]), some equivalent struct (hey, Ipv4Addr), or just u32. Unfortunately, I don’t think it’s possible to reinterpret a &[u8] with a length divisible by 4 as &[[u8; 4]] yet (and the other options would need alignment). You could do this conversion while reading the file in chunks, though.
So first, in an equivalent example program:
use std::net::Ipv4Addr;
fn main() {
let vec: Vec<Ipv4Addr> = vec![
[10, 0, 0, 0].into(),
[20, 0, 0, 0].into(),
[30, 0, 0, 0].into(),
];
println!("vec {:?}", vec);
let found = vec.binary_search(&Ipv4Addr::from_str("20.0.0.0").unwrap());
println!("found {:?}", found);
}
(playground)
Then reading from a file would look something like:
let mut vec: Vec<Ipv4Addr> = vec![];
loop {
let mut address = [0; 4];
match f.read_exact(&mut address) {
Ok(()) => {},
Err(err) if err.kind() == ErrorKind::UnexpectedEof => break,
err => err?,
}
vec.push(address.into());
}
(although this one is slightly lax in that it ignores any trailing bytes that don’t form a multiple of 4)
where f is a BufReader around a file.
I think i have a working solution now, but i'm not a master at rust so please critique it harshly.
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6e3102ea622f1ae0d66465f4007ccb03
use std::cmp::Ordering::{self, Equal, Greater, Less};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use std::str::FromStr;
fn binary_search(s: Vec<u8>, x: Vec<u8>) -> Result<usize, usize> {
let f = |p: &[u8]| p.cmp(&x);
let mut size = s.len() / 4;
if size == 0 {
return Err(0);
}
let mut base = 0usize;
while size > 1 {
let half = size / 2;
let mid = base + half;
// mid is always in [0, size), that means mid is >= 0 and < size.
// mid >= 0: by definition
// mid < size: mid = size / 2 + size / 4 + size / 8 ...
let cmp = f(s[mid*4..(mid+1)*4].to_vec());
base = if cmp == Greater { base } else { mid };
size -= half;
}
// base is always in [0, size) because base <= mid.
let cmp = f(s[base*4..(base+1)*4].to_vec());
if cmp == Equal {
Ok(base*4)
} else {
Err(base*4 + ((cmp == Less) as usize) * 4)
}
}
fn main() {
let vec: Vec<u8> = vec![10, 0, 0, 0, 20, 0, 0, 0, 30, 0, 0, 0];
println!("vec {:?}", vec);
let found = binary_search(vec, Ipv4Addr::from_str("20.0.0.0").unwrap().octets().to_vec());
println!("found {:?}", found);
}

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