Don't store intermediate copies of compressed image data unless it is current best.

Reduces peak memory usage significantly.
This commit is contained in:
Joshua Holmer 2016-03-19 10:42:03 -04:00
parent 87e21c2020
commit 132a15403a

View file

@ -11,6 +11,7 @@ use std::collections::{HashMap, HashSet};
use std::fs::{File, copy};
use std::io::{BufWriter, Write, stderr, stdout};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
pub mod deflate {
pub mod deflate;
@ -80,6 +81,8 @@ pub struct Options {
/// Perform optimization on the input file using the options provided
pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
type TrialWithData = (u8, u8, u8, u8, Vec<u8>);
// Decode PNG from file
if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Processing: {}", filepath.to_str().unwrap()).ok();
@ -196,9 +199,9 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
let thread_count = num_cpus + (num_cpus >> 1);
let pool = Pool::new(thread_count);
// Go through selected permutations and determine the best
let mut best: Option<(u8, u8, u8, u8, Vec<u8>)> = None;
let best: Arc<Mutex<Option<TrialWithData>>> = Arc::new(Mutex::new(None));
let combinations = filter.len() * compression.len() * memory.len() * strategies.len();
let mut results: Vec<(u8, u8, u8, u8, Vec<u8>)> = Vec::with_capacity(combinations);
let mut results: Vec<(u8, u8, u8, u8)> = Vec::with_capacity(combinations);
let mut filters: HashMap<u8, Vec<u8>> = HashMap::with_capacity(filter.len());
if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Trying: {} combinations", combinations).ok();
@ -210,14 +213,18 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
for zc in &compression {
for zm in &memory {
for zs in &strategies {
results.push((*f, *zc, *zm, *zs, Vec::new()));
results.push((*f, *zc, *zm, *zs));
}
}
}
}
pool.scoped(|scope| {
for trial in &mut results {
let original_len = png.idat_data.len();
let interlacing_changed = opts.interlace.is_some() &&
opts.interlace != Some(png.ihdr_data.interlaced);
for trial in &results {
let filtered = filters.get(&trial.0).unwrap();
let best = best.clone();
scope.execute(move || {
let new_idat = deflate::deflate::deflate(filtered,
trial.1,
@ -237,22 +244,19 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> {
.ok();
}
trial.4 = new_idat;
let mut best = best.lock().unwrap();
if (best.is_some() &&
new_idat.len() < best.as_ref().map(|x| x.4.len()).unwrap()) ||
(best.is_none() &&
(new_idat.len() < original_len || interlacing_changed || opts.force)) {
*best = Some((trial.0, trial.1, trial.2, trial.3, new_idat));
}
});
}
});
for result in &results {
if (best.is_some() && result.4.len() < best.as_ref().map(|x| x.4.len()).unwrap()) ||
(best.is_none() &&
(result.4.len() < png.idat_data.len() ||
(opts.interlace.is_some() && opts.interlace != Some(png.ihdr_data.interlaced)) ||
opts.force)) {
best = Some(result.clone());
}
}
if let Some(better) = best {
let mut final_best = best.lock().unwrap();
if let Some(better) = final_best.take() {
png.idat_data = better.4.clone();
if opts.verbosity.is_some() {
writeln!(&mut stderr(), "Found better combination:").ok();