compare sum from chunk from array to the previous chunk in rust - rust

I have a let mut arr = vec![100,200,300,400,500,600]; and want to compare chunk of sum
chunkA: 100+200+300 = 600
chunkB: 200+300+400 = 900
compare chunkA and chunkB

To iterate over overlapping chunks in a slice, use windows(). To process pairs of chunks, you can use itertools' tuple_windows():
use itertools::Itertools;
for (prev, current) in v.windows(3).tuple_windows() {
// ...
}
If you want some accumulated result, I'd recommend going with Iterator::fold() (or Iterator::reduce()).

Something like this should do it:
fn main(){
let arr = vec![100,200,300,400,500,600];
println!("{}", arr.windows (3).fold ((None, 0), |(prev, count), w| {
let s = w.iter().sum::<i32>();
(Some (s),
if let Some (prev) = prev {
if s > prev { count + 1 } else { count }
} else {
0
})
}).1);
}
Playground
It uses slice::windows to create the "chunks", then Iterator::fold to process them.
Or equivalently:
fn main() {
let arr = vec![100, 200, 300, 400, 500, 600];
let mut chunks = arr.windows (3);
println!(
"{}",
chunks
.next()
.map(
|first| chunks.fold ((first.iter().sum::<i32>(), 0), |(prev, count), w| {
let s = w.iter().sum::<i32>();
(s, if s > prev { count + 1 } else { count })
})
)
.unwrap_or ((0, 0))
.1
);
}
Playground

Related

Short-circuit iterator once condition is met

I am trying to write an iterator which conditionally uses elements in a separate iterator. In my example, the separate iterator should increment the sum variable. Once another condition is met *n == 4, the iterator should stop checking the condition and assume rest of elements are increments for the sum variable. I have the following working example:
fn conditional(n: &i64) -> bool {
// a lot of code here which is omitted for brevity
n % 2 == 0
}
fn main() {
let buf = vec![1,2,3,4,5,6];
let mut sum = 0;
let mut iter = buf.iter();
while let Some(n) = iter.next() {
if conditional(n) {
sum += n;
}
if *n == 4 {
// end of file - assume rest of elements are `conditional`
break;
}
};
// rest of elements [5,6]
for n in iter {
sum += n;
}
println!("sum (2+4+5+6): {:?}", sum);
}
output:
sum (2+4+5+6): 17
playground link
I would rather write the same thing with a single iterator using something like flat_map:
fn conditional(n: &i64) -> bool {
// a lot of code here which is omitted for brevity
n % 2 == 0
}
fn main() {
let buf = vec![1,2,3,4,5,6];
let mut sum = 0;
let mut terminate = false;
buf.iter().flat_map(|n| {
if *n == 4 {
// hard terminate here - return Some(n) for rest of iterator [5,6]
terminate = true;
return Some(n);
}
if terminate {
return Some(n);
}
if conditional(n) {
return Some(n);
}
None // odd
})
.for_each(|n| {
sum += n;
});
println!("sum (2+4+5+6): {:?}", sum);
}
output:
sum (2+4+5+6): 17
playground link
Is there a way to write this in a more concise manner? I want to short-circuit the iterator once the *n == 4 condition is reached.
There are many ways to solve this.
Here are a couple:
fn conditional(n: &i64) -> bool {
// a lot of code here which is omitted for brevity
n % 2 == 0
}
fn main() {
let buf = vec![1, 2, 3, 4, 5, 6];
let sum = buf
.iter()
.fold((0, false), |(mut sum, mut terminate), value| {
if *value == 4 {
terminate = true;
}
if terminate || conditional(value) {
sum += *value;
}
(sum, terminate)
})
.0;
println!("sum (2+4+5+6): {:?}", sum);
}
sum (2+4+5+6): 17
Or using filter and a stateful closure:
fn conditional(n: &i64) -> bool {
// a lot of code here which is omitted for brevity
n % 2 == 0
}
fn main() {
let buf = vec![1, 2, 3, 4, 5, 6];
let sum: i64 = buf
.iter()
.filter({
let mut terminate = false;
move |&value| {
terminate || {
if *value == 4 {
terminate = true;
}
conditional(value)
}
}
})
.sum();
println!("sum (2+4+5+6): {:?}", sum);
}
sum (2+4+5+6): 17
You can use filter():
buf.iter().filter(|n| {
if **n == 4 {
terminate = true;
}
terminate || conditional(n)
})
And sum() instead of for_each():
let sum = buf
.iter()
.filter(|n| {
if **n == 4 {
terminate = true;
}
terminate || conditional(n)
})
.sum::<i64>();

Tic Tac Toe - Minimax

I'm trying to build a tic-tac-toe game using minimax algorithm with rust. And I'm stuck. I tried to write a rust code based on the psudeo code on the wikipedia page. https://en.wikipedia.org/wiki/Minimax. However, it didn't work. Ai always makes the first possible move. I would be glad if you could help me.
In main.rs
fn main() {
let mut g = Game::new();
while g.game_state() == Game_State::Continuous {
g.print();
println!("{}", minimax(&g));
if g.turn == Player::Cross {
g.take_input();
}
else {
g = best_move(&g);
}
}
g.print();
if let Game_State::Win(Player::None) = g.game_state() {
println!("Draw");
}
else {
g.print_winner();
}
}
In ai.rs
pub fn child_nodes(game: &Game) -> Vec<Game> {
let mut children: Vec<Game> = Vec::new();
for r in 0..3 {
for c in 0..3 {
if game.grid[r][c] == Player::None {
let mut child = game.clone();
child.grid[r][c] = game.turn;
child.turn = reverse_player(child.turn);
children.push(child);
}
}
}
return children;
}
pub fn minimax(game: &Game) -> isize {
match game.game_state() {
Game_State::Win(winner) => to_scor(winner),
Game_State::Continuous => {
use std::cmp::{min, max};
let children_vec = child_nodes(&game);
let mut score: isize;
if game.turn == Player::Cross {
score = -2;
for i in &children_vec {
score = max(score, minimax(i));
}
}
else {
score = 2;
for i in &children_vec {
score = min(score, minimax(i));
}
}
return score;
}
}
}
pub fn best_move(game: &Game) -> Game {
let children = child_nodes(game);
let mut values: Vec<isize> = Vec::new();
for i in 0..children.len() {
values.push(minimax(&children[i]));
}
let mut index: usize = 0;
let iter = values.iter().enumerate();
if game.turn == Player::Cross {
if let Option::Some(t) = iter.max() {
index = t.0;
}
}
else if game.turn == Player::Circle {
if let Option::Some(t) = iter.min() {
index = t.0;
}
}
let best_pos = children[index];
best_pos
}
pub fn to_scor(x: Player) -> isize {
match x {
Player::Cross => 1,
Player::Circle => -1,
Player::None => 0
}
}
.enumerate() returns an iterator over tuples, and .max() and .min() on an iterator of tuples will compare the tuples - that is, (1, x) is always considered to be less than (2, y) for any values of x and y. This can be demonstrated with this snippet:
fn main() {
let v = vec![3, 1, 2, 5, 3, 6, 7, 2];
println!("{:?}", v.iter().enumerate().min());
println!("{:?}", v.iter().enumerate().max());
}
which prints:
Some((0, 3))
Some((7, 2))
which are just the first and last elements of the list (and not the minimum or maximum elements).
However, as shown here, you can use max_by to use your own function to compare the tuples.

traversing a grid up or down without having to write the same loop twice

I'm working on moving in a vector that acts as a representation of a 2d grid, and I want to be able to move straight up or down, but don't want to rewrite the for loop twice. I came up with this solution that fails due to the borrow checker when indexing into the grid.
// fill the vec later
let grid: Vec<u8> = Vec::with_capacity(WIDTH*HEIGHT);
let mut current_position = Point::new(somewhere);
let end = Point::new(somewhere_else);
let moving: &mut usize;
let to: u32;
if moving_horizontal {
moving = &mut current_position.x;
to = end.x;
}
else {
moving = &mut current_position.y;
to = end.y;
}
for _ in *moving..=to {
// do stuff with this
grid[current_position.x+current_position.y*HEIGHT];
*moving += 1;
}
Is there any neat solution to this or do I have to just write the same for loop twice in each block of my conditional statements?
Please don't too harsh on me. I've just started a few days ago to learn Rust. This is just an idea I had and I'm posting it to learn if it works in this situation or not.
let mut current_position = Point::new(somewhere);
let end = Point::new(somewhere_else);
let moving: &mut usize;
let not_moving: &usize;
let to: u32;
if moving_horizontal {
moving = &mut current_position.x;
not_moving = &current_position.y;
to = end.x
} else {
moving = &mut current_position.y;
not_moving = &current_position.x;
to = end.y
}
for _ in *moving..=to {
// do stuff with this
if moving_horizontal {
grid[*moving + *not_moving * HEIGHT];
} else {
grid[*not_moving + *moving * HEGHT];
}
*moving += 1;
}
If we didn't have to use moving in the for declaration, it could be written in a much better way, but I'm not sure about the details of the algorithm.
Anyway, even if this passes the borrow checker, it is very difficult to read and a double for loop would be much more maintainable.
I think you can generate all the move steps first and then change the current point with the steps one by one in one single loop.
There are four kinds of steps -- (-1, 0), (1, 0), (0, 1) and (0, -1), corresponding to moving left, right, up and down. You can calculate how many steps are needed and which direction needs to be taken first based on the relative positions of the curr and end points and the moving_horizontal flag.
use std::iter::repeat;
struct Point {
x: usize,
y: usize,
}
fn main() {
const HEIGHT: usize = 25;
const WIDTH: usize = 80;
const LEFT: (i8, i8) = (-1, 0);
const RIGHT: (i8, i8) = (1, 0);
const UP: (i8, i8) = (0, 1);
const DOWN: (i8, i8) = (0, -1);
let grid: Vec<u8> = vec![0; WIDTH * HEIGHT];
let mut curr = Point {x: 6, y: 2 };
let end = Point {x: 3, y: 4 };
let (x_step, x_num) = if end.x > curr.x { (RIGHT, end.x - curr.x) }
else { (LEFT, curr.x - end.x) };
let (y_step, y_num) = if end.y > curr.y { (UP, end.y - curr.y) }
else { (DOWN, curr.y - end.y) };
let moving_horizontal = true;
let all_steps = if moving_horizontal {
repeat(x_step).take(x_num).chain(repeat(y_step).take(y_num))
} else {
repeat(y_step).take(y_num).chain(repeat(x_step).take(x_num))
};
println!("Start: (x:{}, y:{})", curr.x, curr.y);
println!("End: (x:{}, y:{})", end.x, end.y);
println!("Steps (Horizontal: {}):", moving_horizontal);
for (i, step) in all_steps.enumerate() {
// do stuff with this
grid[curr.x + curr.y * HEIGHT];
curr.x = match step.0 {
1 => curr.x + 1,
-1 => curr.x - 1,
_ => curr.x
};
curr.y = match step.1 {
1 => curr.y + 1,
-1 => curr.y -1,
_ => curr.y
};
println!("{} => (x:{}, y:{})", i, curr.x, curr.y);
}
}
Playground
The output when moving_horizontal is true:
Start: (x:6, y:2)
End: (x:3, y:4)
Steps (Horizontal: true):
0 => (x:5, y:2)
1 => (x:4, y:2)
2 => (x:3, y:2)
3 => (x:3, y:3)
4 => (x:3, y:4)
The output when moving_horizontal is false:
Start: (x:6, y:2)
End: (x:3, y:4)
Steps (Horizontal: false):
0 => (x:6, y:3)
1 => (x:6, y:4)
2 => (x:5, y:4)
3 => (x:4, y:4)
4 => (x:3, y:4)
If I understood correctly, you want to mutate the current_position so that it moves to the end Point horizontally or vertically.
fn main() {
moving_position(true);
moving_position(false);
}
#[derive(Debug)]
struct Point {
x: usize,
y: usize,
}
impl Point {
fn new(x: usize, y: usize) -> Self {
Self { x, y }
}
}
const WIDTH: usize = 30;
const HEIGHT: usize = 30;
fn moving_position(moving_horizontal: bool) {
let grid: Vec<u8> = Vec::with_capacity(WIDTH * HEIGHT);
let mut current_position = Point::new(8, 5);
let end = Point::new(0, 1);
println!("Starting at: {:?}", current_position);
match moving_horizontal {
true => move_horizontal(&mut current_position, end.x),
false => move_vertical(&mut current_position, end.y),
};
println!("Final position: {:?}",current_position);
}
fn move_horizontal(current_position: &mut Point, to: usize) {
match current_position.x <= to {
true => for i in current_position.x+1..=to {
current_position.x = i;
println!("moving right: {:?}", current_position);
},
false => for i in (to..current_position.x).rev() {
current_position.x = i;
println!("moving left: {:?}", current_position);
},
};
}
fn move_vertical(mut current_position: &mut Point, to: usize) {
match current_position.y <= to {
true => for i in current_position.y+1..=to {
current_position.y = i;
println!("moving up: {:?}", current_position);
},
false => for i in (to..current_position.y).rev() {
current_position.y = i;
println!("moving down: {:?}", current_position);
},
};
}
We check if its moving_horizontal and depending on that we call functions move_horizontal or move_vertical.
These two functions take the current_position as a mutable reference (meaning we will mutate current_position in the function but will return ownership).
Playground

Severe performance degredation over time in multi-threading: what am I missing?

In my application a method runs quickly once started but begins to continuously degrade in performance upon nearing completion, this seems to be even irrelevant of the amount of work (the number of iterations of a function each thread has to perform). Once it reaches near the end it slows to an incredibly slow pace compared to earlier (worth noting this is not just a result of fewer threads remaining incomplete, it seems even each thread slows down).
I cannot figure out why this occurs, so I'm asking. What am I doing wrong?
An overview of CPU usage:
A slideshow of the problem
Worth noting that CPU temperature remains low throughout.
This stage varies with however much work is set, more work produces a better appearance with all threads constantly near 100%. Still, at this moment this appears good.
Here we see the continued performance of earlier,
Here we see it start to degrade. I do not know why this occurs.
After some period of chaos most of the threads have finished their work and the remaining threads continue, at this point although it seems they are at 100% they in actually perform their remaining workload very slowly. I cannot understand why this occurs.
Printing progress
I have written a multi-threaded random_search (documentation link) function for optimization. Most of the complexity in this function comes from printing data passing data between threads, this supports giving outputs showing progress like:
2300
565 (24.57%) 00:00:11 / 00:00:47 [25.600657363049734] { [563.0ns, 561.3ms, 125.0ns, 110.0ns] [2.0µs, 361.8ms, 374.0ns, 405.0ns] [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] }
I have been trying to use this output to figure out whats gone wrong, but I have no idea.
This output describes:
The total number of iterations 2300.
The total number of current iterations 565.
The time running 00:00:11 (mm:ss:ms).
The estimated time remaining 00:00:47 (mm:ss:ms).
The current best value [25.600657363049734].
The most recently measured times between execution positions (effectively time taken for thread to go from some line, to another line (defined specifically with update_execution_position in code below) [563.0ns, 561.3ms, 125.0ns, 110.0ns].
The averages times between execution positions (this is average across entire runtime rather than since last measured) [2.0µs, 361.8ms, 374.0ns, 405.0ns].
The execution positions of threads (0 is when a thread is completed, rest represent a thread having hit some line, which triggered this setting, but yet to hit next line which changes it, effectively being between 2 positions) [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
The random_search code:
Given I have tested implementations with the other methods in my library grid_search and simulated_annealing it would suggest to me the problem does not atleast entirely reside in random_search.rs.
random_search.rs:
pub fn random_search<
A: 'static + Send + Sync,
T: 'static + Copy + Send + Sync + Default + SampleUniform + PartialOrd,
const N: usize,
>(
// Generics
ranges: [Range<T>; N],
f: fn(&[T; N], Option<Arc<A>>) -> f64,
evaluation_data: Option<Arc<A>>,
polling: Option<Polling>,
// Specifics
iterations: u64,
) -> [T; N] {
// Gets cpu data
let cpus = num_cpus::get() as u64;
let search_cpus = cpus - 1; // 1 cpu is used for polling, this one.
let remainder = iterations % search_cpus;
let per = iterations / search_cpus;
let ranges_arc = Arc::new(ranges);
let (best_value, best_params) = search(
// Generics
ranges_arc.clone(),
f,
evaluation_data.clone(),
// Since we are doing this on the same thread, we don't need to use these
Arc::new(AtomicU64::new(Default::default())),
Arc::new(Mutex::new(Default::default())),
Arc::new(AtomicBool::new(false)),
Arc::new(AtomicU8::new(0)),
Arc::new([
Mutex::new((Duration::new(0, 0), 0)),
Mutex::new((Duration::new(0, 0), 0)),
Mutex::new((Duration::new(0, 0), 0)),
Mutex::new((Duration::new(0, 0), 0)),
]),
// Specifics
remainder,
);
let thread_exit = Arc::new(AtomicBool::new(false));
// (handles,(counters,thread_bests))
let (handles, links): (Vec<_>, Vec<_>) = (0..search_cpus)
.map(|_| {
let ranges_clone = ranges_arc.clone();
let counter = Arc::new(AtomicU64::new(0));
let thread_best = Arc::new(Mutex::new(f64::MAX));
let thread_execution_position = Arc::new(AtomicU8::new(0));
let thread_execution_time = Arc::new([
Mutex::new((Duration::new(0, 0), 0)),
Mutex::new((Duration::new(0, 0), 0)),
Mutex::new((Duration::new(0, 0), 0)),
Mutex::new((Duration::new(0, 0), 0)),
]);
let counter_clone = counter.clone();
let thread_best_clone = thread_best.clone();
let thread_exit_clone = thread_exit.clone();
let evaluation_data_clone = evaluation_data.clone();
let thread_execution_position_clone = thread_execution_position.clone();
let thread_execution_time_clone = thread_execution_time.clone();
(
thread::spawn(move || {
search(
// Generics
ranges_clone,
f,
evaluation_data_clone,
counter_clone,
thread_best_clone,
thread_exit_clone,
thread_execution_position_clone,
thread_execution_time_clone,
// Specifics
per,
)
}),
(
counter,
(
thread_best,
(thread_execution_position, thread_execution_time),
),
),
)
})
.unzip();
let (counters, links): (Vec<Arc<AtomicU64>>, Vec<_>) = links.into_iter().unzip();
let (thread_bests, links): (Vec<Arc<Mutex<f64>>>, Vec<_>) = links.into_iter().unzip();
let (thread_execution_positions, thread_execution_times) = links.into_iter().unzip();
if let Some(poll_data) = polling {
poll(
poll_data,
counters,
remainder,
iterations,
thread_bests,
thread_exit,
thread_execution_positions,
thread_execution_times,
);
}
let joins: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
let (_, best_params) = joins
.into_iter()
.fold((best_value, best_params), |(bv, bp), (v, p)| {
if v < bv {
(v, p)
} else {
(bv, bp)
}
});
return best_params;
fn search<
A: 'static + Send + Sync,
T: 'static + Copy + Send + Sync + Default + SampleUniform + PartialOrd,
const N: usize,
>(
// Generics
ranges: Arc<[Range<T>; N]>,
f: fn(&[T; N], Option<Arc<A>>) -> f64,
evaluation_data: Option<Arc<A>>,
counter: Arc<AtomicU64>,
best: Arc<Mutex<f64>>,
thread_exit: Arc<AtomicBool>,
thread_execution_position: Arc<AtomicU8>,
thread_execution_times: Arc<[Mutex<(Duration, u64)>; 4]>,
// Specifics
iterations: u64,
) -> (f64, [T; N]) {
let mut execution_position_timer = Instant::now();
let mut rng = thread_rng();
let mut params = [Default::default(); N];
let mut best_value = f64::MAX;
let mut best_params = [Default::default(); N];
for _ in 0..iterations {
// Gen random values
for (range, param) in ranges.iter().zip(params.iter_mut()) {
*param = rng.gen_range(range.clone());
}
// Update execution position
execution_position_timer = update_execution_position(
1,
execution_position_timer,
&thread_execution_position,
&thread_execution_times,
);
// Run function
let new_value = f(&params, evaluation_data.clone());
// Update execution position
execution_position_timer = update_execution_position(
2,
execution_position_timer,
&thread_execution_position,
&thread_execution_times,
);
// Check best
if new_value < best_value {
best_value = new_value;
best_params = params;
*best.lock().unwrap() = best_value;
}
// Update execution position
execution_position_timer = update_execution_position(
3,
execution_position_timer,
&thread_execution_position,
&thread_execution_times,
);
counter.fetch_add(1, Ordering::SeqCst);
// Update execution position
execution_position_timer = update_execution_position(
4,
execution_position_timer,
&thread_execution_position,
&thread_execution_times,
);
if thread_exit.load(Ordering::SeqCst) {
break;
}
}
// Update execution position
// 0 represents ended state
thread_execution_position.store(0, Ordering::SeqCst);
return (best_value, best_params);
}
}
util.rs:
pub fn update_execution_position<const N: usize>(
i: usize,
execution_position_timer: Instant,
thread_execution_position: &Arc<AtomicU8>,
thread_execution_times: &Arc<[Mutex<(Duration, u64)>; N]>,
) -> Instant {
{
let mut data = thread_execution_times[i - 1].lock().unwrap();
data.0 += execution_position_timer.elapsed();
data.1 += 1;
}
thread_execution_position.store(i as u8, Ordering::SeqCst);
Instant::now()
}
pub struct Polling {
pub poll_rate: u64,
pub printing: bool,
pub early_exit_minimum: Option<f64>,
pub thread_execution_reporting: bool,
}
impl Polling {
const DEFAULT_POLL_RATE: u64 = 10;
pub fn new(printing: bool, early_exit_minimum: Option<f64>) -> Self {
Self {
poll_rate: Polling::DEFAULT_POLL_RATE,
printing,
early_exit_minimum,
thread_execution_reporting: false,
}
}
}
pub fn poll<const N: usize>(
data: Polling,
// Current count of each thread.
counters: Vec<Arc<AtomicU64>>,
offset: u64,
// Final total iterations.
iterations: u64,
// Best values of each thread.
thread_bests: Vec<Arc<Mutex<f64>>>,
// Early exit switch.
thread_exit: Arc<AtomicBool>,
// Current positions of execution of each thread.
thread_execution_positions: Vec<Arc<AtomicU8>>,
// Current average times between execution positions for each thread
thread_execution_times: Vec<Arc<[Mutex<(Duration, u64)>; N]>>,
) {
let start = Instant::now();
let mut stdout = stdout();
let mut count = offset
+ counters
.iter()
.map(|c| c.load(Ordering::SeqCst))
.sum::<u64>();
if data.printing {
println!("{:20}", iterations);
}
let mut poll_time = Instant::now();
let mut held_best: f64 = f64::MAX;
let mut held_average_execution_times: [(Duration, u64); N] =
vec![(Duration::new(0, 0), 0); N].try_into().unwrap();
let mut held_recent_execution_times: [Duration; N] =
vec![Duration::new(0, 0); N].try_into().unwrap();
while count < iterations {
if data.printing {
// loop {
let percent = count as f32 / iterations as f32;
// If count == 0, give 00... for remaining time as placeholder
let remaining_time_estimate = if count == 0 {
Duration::new(0, 0)
} else {
start.elapsed().div_f32(percent)
};
print!(
"\r{:20} ({:.2}%) {} / {} [{}] {}\t",
count,
100. * percent,
print_duration(start.elapsed(), 0..3),
print_duration(remaining_time_estimate, 0..3),
if held_best == f64::MAX {
String::from("?")
} else {
format!("{}", held_best)
},
if data.thread_execution_reporting {
let (average_execution_times, recent_execution_times): (
Vec<String>,
Vec<String>,
) = (0..thread_execution_times[0].len())
.map(|i| {
let (mut sum, mut num) = (Duration::new(0, 0), 0);
for n in 0..thread_execution_times.len() {
{
let mut data = thread_execution_times[n][i].lock().unwrap();
sum += data.0;
held_average_execution_times[i].0 += data.0;
num += data.1;
held_average_execution_times[i].1 += data.1;
*data = (Duration::new(0, 0), 0);
}
}
if num > 0 {
held_recent_execution_times[i] = sum.div_f64(num as f64);
}
(
if held_average_execution_times[i].1 > 0 {
format!(
"{:.1?}",
held_average_execution_times[i]
.0
.div_f64(held_average_execution_times[i].1 as f64)
)
} else {
String::from("?")
},
if held_recent_execution_times[i] > Duration::new(0, 0) {
format!("{:.1?}", held_recent_execution_times[i])
} else {
String::from("?")
},
)
})
.unzip();
let execution_positions: Vec<u8> = thread_execution_positions
.iter()
.map(|pos| pos.load(Ordering::SeqCst))
.collect();
format!(
"{{ [{}] [{}] {:.?} }}",
recent_execution_times.join(", "),
average_execution_times.join(", "),
execution_positions
)
} else {
String::from("")
}
);
stdout.flush().unwrap();
}
// Updates best and does early exiting
match (data.early_exit_minimum, data.printing) {
(Some(early_exit), true) => {
for thread_best in thread_bests.iter() {
let thread_best_temp = *thread_best.lock().unwrap();
if thread_best_temp < held_best {
held_best = thread_best_temp;
if thread_best_temp <= early_exit {
thread_exit.store(true, Ordering::SeqCst);
println!();
return;
}
}
}
}
(None, true) => {
for thread_best in thread_bests.iter() {
let thread_best_temp = *thread_best.lock().unwrap();
if thread_best_temp < held_best {
held_best = thread_best_temp;
}
}
}
(Some(early_exit), false) => {
for thread_best in thread_bests.iter() {
if *thread_best.lock().unwrap() <= early_exit {
thread_exit.store(true, Ordering::SeqCst);
return;
}
}
}
(None, false) => {}
}
thread::sleep(saturating_sub(
Duration::from_millis(data.poll_rate),
poll_time.elapsed(),
));
poll_time = Instant::now();
count = offset
+ counters
.iter()
.map(|c| c.load(Ordering::SeqCst))
.sum::<u64>();
}
if data.printing {
println!(
"\r{:20} (100.00%) {} / {} [{}] {}\t",
count,
print_duration(start.elapsed(), 0..3),
print_duration(start.elapsed(), 0..3),
held_best,
if data.thread_execution_reporting {
let (average_execution_times, recent_execution_times): (Vec<String>, Vec<String>) =
(0..thread_execution_times[0].len())
.map(|i| {
let (mut sum, mut num) = (Duration::new(0, 0), 0);
for n in 0..thread_execution_times.len() {
{
let mut data = thread_execution_times[n][i].lock().unwrap();
sum += data.0;
held_average_execution_times[i].0 += data.0;
num += data.1;
held_average_execution_times[i].1 += data.1;
*data = (Duration::new(0, 0), 0);
}
}
if num > 0 {
held_recent_execution_times[i] = sum.div_f64(num as f64);
}
(
if held_average_execution_times[i].1 > 0 {
format!(
"{:.1?}",
held_average_execution_times[i]
.0
.div_f64(held_average_execution_times[i].1 as f64)
)
} else {
String::from("?")
},
if held_recent_execution_times[i] > Duration::new(0, 0) {
format!("{:.1?}", held_recent_execution_times[i])
} else {
String::from("?")
},
)
})
.unzip();
let execution_positions: Vec<u8> = thread_execution_positions
.iter()
.map(|pos| pos.load(Ordering::SeqCst))
.collect();
format!(
"{{ [{}] [{}] {:.?} }}",
recent_execution_times.join(", "),
average_execution_times.join(", "),
execution_positions
)
} else {
String::from("")
}
);
stdout.flush().unwrap();
}
}
// Since `Duration::saturating_sub` is unstable this is an alternative.
fn saturating_sub(a: Duration, b: Duration) -> Duration {
if let Some(dur) = a.checked_sub(b) {
dur
} else {
Duration::new(0, 0)
}
}
main.rs
use std::{cmp,sync::Arc};
type Image = Vec<Vec<Pixel>>;
#[derive(Clone)]
pub struct Pixel {
pub luma: u8,
}
impl From<&u8> for Pixel {
fn from(x: &u8) -> Pixel {
Pixel { luma: *x }
}
}
fn main() {
// Setup
// -------------------------------------------
fn open_image(path: &str) -> Image {
let example = image::open(path).unwrap().to_rgb8();
let dims = example.dimensions();
let size = (dims.0 as usize, dims.1 as usize);
let example_vec = example.into_raw();
// Binarizes image
let img_vec = from_raw(&example_vec, size);
img_vec
}
println!("Started ...");
let example: Image = open_image("example.jpg");
let target: Image = open_image("target.jpg");
// let first_image = Some(Arc::new((examples[0].clone(), targets[0].clone())));
println!("Opened...");
let image = Some(Arc::new((example, target)));
// Running the optimization
// -------------------------------------------
println!("Started opt...");
let best = simple_optimization::random_search(
[0..255, 0..255, 0..255, 1..255, 1..255],
eval_one,
image,
Some(simple_optimization::Polling {
poll_rate: 100,
printing: true,
early_exit_minimum: None,
thread_execution_reporting: true,
}),
2300,
);
println!("{:.?}", best); // [34, 220, 43, 253, 168]
assert!(false);
fn eval_one(arr: &[u8; 5], opt: Option<Arc<(Image, Image)>>) -> f64 {
let bin_params = (
arr[0] as u8,
arr[1] as u8,
arr[2] as u8,
arr[3] as usize,
arr[4] as usize,
);
let arc = opt.unwrap();
// Gets average mean-squared-error
let binary_pixels = binarize_buffer(arc.0.clone(), bin_params);
mse(binary_pixels, &arc.1)
}
// Mean-squared-error
fn mse(prediction: Image, target: &Image) -> f64 {
let n = target.len() * target[0].len();
prediction
.iter()
.flatten()
.zip(target.iter().flatten())
.map(|(p, t)| difference(p, t).powf(2.))
.sum::<f64>()
/ (2. * n as f64)
}
#[rustfmt::skip]
fn difference(p: &Pixel, t: &Pixel) -> f64 {
p.luma as f64 - t.luma as f64
}
}
pub fn from_raw(raw: &[u8], (_i_size, j_size): (usize, usize)) -> Vec<Vec<Pixel>> {
(0..raw.len())
.step_by(j_size)
.map(|index| {
raw[index..index + j_size]
.iter()
.map(Pixel::from)
.collect::<Vec<Pixel>>()
})
.collect()
}
pub fn binarize_buffer(
mut img: Vec<Vec<Pixel>>,
(_, _, local_luma_boundary, local_field_reach, local_field_size): (u8, u8, u8, usize, usize),
) -> Vec<Vec<Pixel>> {
let (i_size, j_size) = (img.len(), img[0].len());
let i_chunks = (i_size as f32 / local_field_size as f32).ceil() as usize;
let j_chunks = (j_size as f32 / local_field_size as f32).ceil() as usize;
let mut local_luma: Vec<Vec<u8>> = vec![vec![u8::default(); j_chunks]; i_chunks];
// Gets average luma in local fields
// O((s+r)^2*(n/s)*(m/s)) : s = local field size, r = local field reach
for (i_chunk, i) in (0..i_size).step_by(local_field_size).enumerate() {
let i_range = zero_checked_sub(i, local_field_reach)
..cmp::min(i + local_field_size + local_field_reach, i_size);
let i_range_length = i_range.end - i_range.start;
for (j_chunk, j) in (0..j_size).step_by(local_field_size).enumerate() {
let j_range = zero_checked_sub(j, local_field_reach)
..cmp::min(j + local_field_size + local_field_reach, j_size);
let j_range_length = j_range.end - j_range.start;
let total: u32 = i_range
.clone()
.map(|i_range_indx| {
img[i_range_indx][j_range.clone()]
.iter()
.map(|p| p.luma as u32)
.sum::<u32>()
})
.sum();
local_luma[i_chunk][j_chunk] = (total / (i_range_length * j_range_length) as u32) as u8;
}
}
// Apply binarization
// O(nm)
for i in 0..i_size {
let i_group: usize = i / local_field_size; // == floor(i as f32 / local_field_size as f32) as usize
for j in 0..j_size {
let j_group: usize = j / local_field_size;
// Local average boundaries
// --------------------------------
if let Some(local) = local_luma[i_group][j_group].checked_sub(local_luma_boundary) {
if img[i][j].luma < local {
img[i][j].luma = 0;
continue;
}
}
if let Some(local) = local_luma[i_group][j_group].checked_add(local_luma_boundary) {
if img[i][j].luma > local {
img[i][j].luma = 255;
continue;
}
}
// White is the negative (false/0) colour in our binarization, thus this is our else case
img[i][j].luma = 255;
}
}
img
}
#[rustfmt::skip]
fn zero_checked_sub(a: usize, b: usize) -> usize { if a > b { a - b } else { 0 } }
Project zip (in case you'd rather not spend time setting it up).
Else, here are the images being used as /target.jpg and /example.jpg (it shouldn't matter it being specifically these images, any should work):
And Cargo.toml dependencies:
[dependencies]
rand = "0.8.4"
itertools = "0.10.1" # izip!
num_cpus = "1.13.0" # Multi-threading
print_duration = "1.0.0" # Printing progress
num = "0.4.0" # Generics
rand_distr = "0.4.1" # Normal distribution
image = "0.23.14"
serde = { version="1.0.118", features=["derive"] }
serde_json = "1.0.50"
I do feel rather reluctant to post such a large question and
inevitably require people to read a few hundred lines (especially given the project doesn't work in a playground), but I'm really lost here and can see no other way to communicate the whole area of the problem. Apologies for this.
As noted, I have tried for a while to figure out what is happening here, but I have come up short, any help would be really appreciate.
Some basic debugging (aka println! everywhere) shows that your performance problem is not related to the multithreading at all. It just happens randomly, and when there are 24 threads doing their job, the fact that one is randomly stalling is not noticeable, but when there is only one or two threads left, they stand out as slow.
But where is this performance bottleneck? Well, you are stating it yourself in the code: in binary_buffer you say:
// Gets average luma in local fields
// O((s+r)^2*(n/s)*(m/s)) : s = local field size, r = local field reach
The values of s and r seem to be random values between 0 and 255, while n is the length of a image row, in bytes 3984 * 3 = 11952, and m is the number of rows 2271.
Now, most of the times that O() is around a few millions, quite manageable. But if s happens to be small and r big, such as (3, 200) then the number of computations blows up to over 1e11!
Fortunately I think you can define the ranges of those values in the original call to random_search so a bit of tweaking there should send you back to reasonable complexity. Changing the ranges to:
[0..255, 0..255, 0..255, 1..255, 20..255],
// ^ here
seems to do the trick for me.
PS: These lines at the beginning of binary_buffer were key to discover this:
let o = (i_size / local_field_size) * (j_size / local_field_size) * (local_field_size + local_field_reach).pow(2);
println!("\nO() = {}", o);

How to lazily deserialize from a JSON array?

Problem description
Using serde_json to deserialize a very long array of objects into a Vec<T> can take a long time, because the entire array must be read into memory up front. I'd like to iterate over the items in the array instead to avoid the up-front processing and memory requirements.
My approach so far
StreamDeserializer cannot be used directly, because it can only iterate over self-delimiting types placed back-to-back. So what I've done so far is to write a custom struct to implement Read, wrapping another Read but omitting the starting and ending square brackets, as well as any commas.
For example, the reader will transform the JSON [{"name": "foo"}, {"name": "bar"}, {"name": "baz"}] into {"name": "foo"} {"name": "bar"} {"name": "baz"} so it can be used with StreamDeserializer.
Here is the code in its entirety:
use std::io;
/// An implementation of `Read` that transforms JSON input where the outermost
/// structure is an array. The enclosing brackets and commas are removed,
/// causing the items to be adjacent to one another. This works with
/// [`serde_json::StreamDeserializer`].
pub(crate) struct ArrayStreamReader<T> {
inner: T,
depth: Option<usize>,
inside_string: bool,
escape_next: bool,
}
impl<T: io::Read> ArrayStreamReader<T> {
pub(crate) fn new_buffered(inner: T) -> io::BufReader<Self> {
io::BufReader::new(ArrayStreamReader {
inner,
depth: None,
inside_string: false,
escape_next: false,
})
}
}
#[inline]
fn do_copy(dst: &mut [u8], src: &[u8], len: usize) {
if len == 1 {
dst[0] = src[0]; // Avoids memcpy call.
} else {
dst[..len].copy_from_slice(&src[..len]);
}
}
impl<T: io::Read> io::Read for ArrayStreamReader<T> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let mut tmp = vec![0u8; buf.len()];
// The outer loop is here in case every byte was skipped, which can happen
// easily if `buf.len()` is 1. In this situation, the operation is retried
// until either no bytes are read from the inner stream, or at least 1 byte
// is written to `buf`.
loop {
let byte_count = self.inner.read(&mut tmp)?;
if byte_count == 0 {
return if self.depth.is_some() {
Err(io::ErrorKind::UnexpectedEof.into())
} else {
Ok(0)
};
}
let mut tmp_pos = 0;
let mut buf_pos = 0;
for (i, b) in tmp.iter().cloned().enumerate() {
if self.depth.is_none() {
match b {
b'[' => {
tmp_pos = i + 1;
self.depth = Some(0);
},
b if b.is_ascii_whitespace() => {},
b'\0' => break,
_ => return Err(io::ErrorKind::InvalidData.into()),
}
continue;
}
if self.inside_string {
match b {
_ if self.escape_next => self.escape_next = false,
b'\\' => self.escape_next = true,
b'"' if !self.escape_next => self.inside_string = false,
_ => {},
}
continue;
}
let depth = self.depth.unwrap();
match b {
b'[' | b'{' => self.depth = Some(depth + 1),
b']' | b'}' if depth > 0 => self.depth = Some(depth - 1),
b'"' => self.inside_string = true,
b'}' if depth == 0 => return Err(io::ErrorKind::InvalidData.into()),
b',' | b']' if depth == 0 => {
let len = i - tmp_pos;
do_copy(&mut buf[buf_pos..], &tmp[tmp_pos..], len);
tmp_pos = i + 1;
buf_pos += len;
// Then write a space to separate items.
buf[buf_pos] = b' ';
buf_pos += 1;
if b == b']' {
// Reached the end of outer array. If another array
// follows, the stream will continue.
self.depth = None;
}
},
_ => {},
}
}
if tmp_pos < byte_count {
let len = byte_count - tmp_pos;
do_copy(&mut buf[buf_pos..], &tmp[tmp_pos..], len);
buf_pos += len;
}
if buf_pos > 0 {
// If at least some data was read, return with the amount. Otherwise, the outer
// loop will try again.
return Ok(buf_pos);
}
}
}
}
It is used like so:
use std::io;
use serde::Deserialize;
#[derive(Deserialize)]
struct Item {
name: String,
}
fn main() -> io::Result<()> {
let json = br#"[{"name": "foo"}, {"name": "bar"}]"#;
let wrapped = ArrayStreamReader::new_buffered(&json[..]);
let first_item: Item = serde_json::Deserializer::from_reader(wrapped)
.into_iter()
.next()
.unwrap()?;
assert_eq!(first_item.name, "foo");
Ok(())
}
At last, a question
There must be a better way to do this, right?

Resources