[Help]: Why is my concurrent code slower than the single-threaded version?
Hello, I've recently re-started learning rust and have been aiming to write an image stacking program as my first project which can target web-assembly and help me quickly prototype final results from images while I'm on the field photographing. This is also a good project in my opinion that would touch all parts of the language and hence helping me understand everything better.
I have two versions of the same code. The single threaded version takes about 5-6s to execute (for 2 images). The concurrent version takes about 30s to execute for the same two images. I am not able to understand why that's happening. Is it because I'm copying large objects instead of using them by reference (which introduces the complexity of defining explicit lifetimes which I'm not too familiar with)? Is it something else?
Any help/suggestions would be appreciated. Thank you.
Sorry if it should be obvious, this is my first time writing Rust or any systems programming language for that matter 😅
Edit: Removed the hastebin link and here's the code instead:
// concurrent version
use std::path::PathBuf;
use std::sync::Mutex;
use crossbeam::channel::bounded;
use crossbeam::sync::WaitGroup;
use image::{DynamicImage, EncodableLayout};
use prisma::channel::{ChannelCast, NormalBoundedChannel};
use prisma::{Rgb, Rgba, FromTuple};
pub fn stack_image(image_paths: Vec<PathBuf>) -> Result<(), ()> {
let first_image_path = image_paths.first().expect("At least 2 images should be provided");
let image_paths = &image_paths[1..];
let base_image = image::open(first_image_path).expect("Failed to open image");
match base_image {
DynamicImage::ImageRgb16(data) => {
let final_image = Mutex::new(data.clone());
let wait_group = WaitGroup::new();
for (index, path) in image_paths.into_iter().enumerate() {
let image = image::open(path).expect("Unable to open image");
type Message = ((u32, u32), image::Rgb<u16>, image::Rgb<u16>, WaitGroup);
let (s, r) = bounded::<Message>(0);
println!("Processing {}th image", index);
crossbeam::scope(|scope| {
scope.spawn(|_| {
let locked_image = final_image.lock().unwrap();
let clone = locked_image.clone();
drop(locked_image);
for ((pixel_x, pixel_y, background), foreground) in clone.enumerate_pixels().zip(image.to_rgb16().pixels()) {
let wg = wait_group.clone();
s.send(((pixel_x, pixel_y), background.to_owned(), foreground.to_owned(), wg)).expect("Unable to send");
}
drop(s);
});
for _ in 0..num_cpus::get() {
scope.spawn(|_| {
for ((x, y), background, foreground, wg) in r.iter() {
let [r, g, b] = background.0;
let color1: Rgb<f32> = Rgb::new(r, g, b).color_cast();
let [r, g, b] = foreground.0;
let color2: Rgb<f32> = Rgb::new(r, g, b).color_cast();
let equation = PixelMath3DEquation::new(&|x, y| {
x.max(y)
});
let final_color: Rgb<u16> = stack_pixels(&color1, &color2, &equation).color_cast();
let final_pixel = [
final_color.red(),
final_color.green(),
final_color.blue(),
];
let mut final_image = final_image.lock().unwrap();
final_image.put_pixel(x, y, image::Rgb::from(final_pixel));
drop(final_image);
drop(wg);
};
});
}
}).expect("Failed to start parallel scope");
}
wait_group.wait();
let final_image = final_image.lock().unwrap();
image::save_buffer(
"./final.tiff",
&final_image.as_bytes(),
final_image.width(),
final_image.height(),
image::ColorType::Rgb16
).unwrap();
drop(final_image);
()
}
_ => {
println!("Unsupported image encoding encountered: {}", base_image.color().bits_per_pixel())
}
}
Ok(())
}
// Single-threaded version
use std::path::PathBuf;
use image::{DynamicImage, EncodableLayout};
use prisma::channel::{ChannelCast, NormalBoundedChannel};
use prisma::{Rgb, Rgba, FromTuple};
pub fn stack_image(image_paths: Vec<PathBuf>) -> Result<(), ()> {
let first_image_path = image_paths.first().expect("At least 2 images should be provided");
let image_paths = &image_paths[1..];
let base_image = image::open(first_image_path).expect("Failed to open image");
let equation = PixelMath3DEquation::new(&|x, y| {
x.max(y)
});
match base_image {
DynamicImage::ImageRgb16(data) => {
let mut final_image = data.clone();
println!("rgb16");
for (index, path) in image_paths.into_iter().enumerate() {
let image = image::open(path).expect("Unable to open image");
for ((pixel_x, pixel_y, background), foreground) in final_image.clone().enumerate_pixels().zip(image.to_rgb16().pixels()) {
let [r, g, b] = background.0;
let color1: Rgb<f32> = Rgb::new(r, g, b).color_cast();
let [r, g, b] = foreground.0;
let color2: Rgb<f32> = Rgb::new(r, g, b).color_cast();
let final_color: Rgb<u16> = stack_pixels(&color1, &color2, &equation).color_cast();
let final_pixel = [
final_color.red(),
final_color.green(),
final_color.blue(),
];
final_image.put_pixel(pixel_x, pixel_y, image::Rgb::from(final_pixel));
}
println!("Progress: {} / {}", index, image_paths.len());
}
image::save_buffer("./final.tiff", &final_image.as_bytes(), final_image.width(), final_image.height(), image::ColorType::Rgb16).unwrap();
()
}
_ => {
println!("Unsupported image encoding encountered: {}", base_image.color().bits_per_pixel())
}
}
Ok(())
}