diff --git a/CHANGELOG.md b/CHANGELOG.md index b4525aa0..ab075049 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Minor compression improvement on interlaced images - Performance optimizations - [SEMVER_MINOR] Move default Options into a Default impl + - [SEMVER_MINOR] Add option for setting number of threads ([#39](https://github.com/shssoichiro/oxipng/issues/39)) **Version 0.6.0** - Fix issue where output directory would not be created if it did not exist diff --git a/src/lib.rs b/src/lib.rs index e1213a67..6049cfb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,6 +79,8 @@ pub struct Options { /// Whether to use heuristics to pick the best filter and compression /// Intended for use with `-o 1` from the CLI interface pub use_heuristics: bool, + /// Number of threads to use, defaults to 1.5x CPU cores, rounded down + pub threads: usize, } impl Default for Options { @@ -96,6 +98,10 @@ impl Default for Options { strategies.insert(i); } + // Default to 1 thread on single-core, otherwise use threads = 1.5x CPU cores + let num_cpus = num_cpus::get(); + let thread_count = num_cpus + (num_cpus >> 1); + Options { backup: false, out_file: PathBuf::new(), @@ -121,6 +127,7 @@ impl Default for Options { idat_recoding: true, strip: png::Headers::None, use_heuristics: false, + threads: thread_count, } } } @@ -200,9 +207,7 @@ pub fn optimize(filepath: &Path, opts: &Options) -> Result<(), String> { let something_changed = perform_reductions(&mut png, &opts); if opts.idat_recoding || something_changed { - // Use 1 thread on single-core, otherwise use threads = 1.5x CPU cores - let num_cpus = num_cpus::get(); - let thread_count = num_cpus + (num_cpus >> 1); + let thread_count = opts.threads; let pool = Pool::new(thread_count); // Go through selected permutations and determine the best let best: Arc>> = Arc::new(Mutex::new(None)); diff --git a/src/main.rs b/src/main.rs index 2251c72c..66e27b34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -172,6 +172,23 @@ fn main() { .help("Strip safely-removable metadata objects") .short("s") .conflicts_with("strip")) + .arg(Arg::with_name("threads") + .help("Set number of threads to use - default 1.5x CPU cores") + .long("threads") + .short("t") + .takes_value(true) + .validator(|x| { + match x.parse::() { + Ok(val) => { + if val > 0 { + Ok(()) + } else { + Err("Thread count must be >= 1".to_owned()) + } + } + Err(_) => Err("Thread count must be >= 1".to_owned()), + } + })) .after_help("Optimization levels: -o 0 => --zc 3 --nz (0 or 1 trials) -o 1 => --zc 9 (1 trial, determined heuristically) @@ -478,6 +495,10 @@ fn parse_opts_into_struct(matches: &ArgMatches, opts: &mut oxipng::Options) -> R opts.strip = png::Headers::Safe; } + if let Some(x) = matches.value_of("threads") { + opts.threads = x.parse::().unwrap(); + } + Ok(()) } diff --git a/tests/filters.rs b/tests/filters.rs index 507f865b..1a3d578b 100644 --- a/tests/filters.rs +++ b/tests/filters.rs @@ -10,43 +10,15 @@ use std::path::Path; use std::path::PathBuf; fn get_opts(input: &Path) -> oxipng::Options { + let mut options = oxipng::Options::default(); + options.out_file = input.with_extension("out.png").to_owned(); + options.verbosity = None; + options.force = true; let mut filter = HashSet::new(); filter.insert(0); - let mut compression = HashSet::new(); - compression.insert(9); - let mut memory = HashSet::new(); - memory.insert(9); - let mut strategies = HashSet::new(); - for i in 0..4 { - strategies.insert(i); - } + options.filter = filter; - oxipng::Options { - backup: false, - out_file: input.with_extension("out.png").to_owned(), - out_dir: None, - stdout: false, - pretend: false, - recursive: false, - fix_errors: false, - force: true, - clobber: true, - create: true, - preserve_attrs: false, - verbosity: None, - filter: filter, - interlace: None, - compression: compression, - memory: memory, - strategies: strategies, - window: 15, - bit_depth_reduction: true, - color_type_reduction: true, - palette_reduction: true, - idat_recoding: true, - strip: png::Headers::None, - use_heuristics: false, - } + options } fn test_it_converts(input: &Path, diff --git a/tests/flags.rs b/tests/flags.rs index 046f2c2c..90b10ca6 100644 --- a/tests/flags.rs +++ b/tests/flags.rs @@ -10,43 +10,15 @@ use std::path::Path; use std::path::PathBuf; fn get_opts(input: &Path) -> oxipng::Options { + let mut options = oxipng::Options::default(); + options.out_file = input.with_extension("out.png").to_owned(); + options.verbosity = None; + options.force = true; let mut filter = HashSet::new(); filter.insert(0); - let mut compression = HashSet::new(); - compression.insert(9); - let mut memory = HashSet::new(); - memory.insert(9); - let mut strategies = HashSet::new(); - for i in 0..4 { - strategies.insert(i); - } + options.filter = filter; - oxipng::Options { - backup: false, - out_file: input.with_extension("out.png").to_owned(), - out_dir: None, - stdout: false, - pretend: false, - recursive: false, - fix_errors: false, - force: true, - clobber: true, - create: true, - preserve_attrs: false, - verbosity: None, - filter: filter, - interlace: None, - compression: compression, - memory: memory, - strategies: strategies, - window: 15, - bit_depth_reduction: true, - color_type_reduction: true, - palette_reduction: true, - idat_recoding: true, - strip: png::Headers::None, - use_heuristics: false, - } + options } fn test_it_converts(input: &Path, diff --git a/tests/interlaced.rs b/tests/interlaced.rs index 564db747..394d697c 100644 --- a/tests/interlaced.rs +++ b/tests/interlaced.rs @@ -10,43 +10,15 @@ use std::path::Path; use std::path::PathBuf; fn get_opts(input: &Path) -> oxipng::Options { + let mut options = oxipng::Options::default(); + options.out_file = input.with_extension("out.png").to_owned(); + options.verbosity = None; + options.force = true; let mut filter = HashSet::new(); filter.insert(0); - let mut compression = HashSet::new(); - compression.insert(9); - let mut memory = HashSet::new(); - memory.insert(9); - let mut strategies = HashSet::new(); - for i in 0..4 { - strategies.insert(i); - } + options.filter = filter; - oxipng::Options { - backup: false, - out_file: input.with_extension("out.png").to_owned(), - out_dir: None, - stdout: false, - pretend: false, - recursive: false, - fix_errors: false, - force: true, - clobber: true, - create: true, - preserve_attrs: false, - verbosity: None, - filter: filter, - interlace: None, - compression: compression, - memory: memory, - strategies: strategies, - window: 15, - bit_depth_reduction: true, - color_type_reduction: true, - palette_reduction: true, - idat_recoding: true, - strip: png::Headers::None, - use_heuristics: false, - } + options } fn test_it_converts(input: &Path, diff --git a/tests/reduction.rs b/tests/reduction.rs index a694ea48..10f9fbb5 100644 --- a/tests/reduction.rs +++ b/tests/reduction.rs @@ -10,43 +10,15 @@ use std::path::Path; use std::path::PathBuf; fn get_opts(input: &Path) -> oxipng::Options { + let mut options = oxipng::Options::default(); + options.out_file = input.with_extension("out.png").to_owned(); + options.verbosity = None; + options.force = true; let mut filter = HashSet::new(); filter.insert(0); - let mut compression = HashSet::new(); - compression.insert(9); - let mut memory = HashSet::new(); - memory.insert(9); - let mut strategies = HashSet::new(); - for i in 0..4 { - strategies.insert(i); - } + options.filter = filter; - oxipng::Options { - backup: false, - out_file: input.with_extension("out.png").to_owned(), - out_dir: None, - stdout: false, - pretend: false, - recursive: false, - fix_errors: false, - force: true, - clobber: true, - create: true, - preserve_attrs: false, - verbosity: None, - filter: filter, - interlace: None, - compression: compression, - memory: memory, - strategies: strategies, - window: 15, - bit_depth_reduction: true, - color_type_reduction: true, - palette_reduction: true, - idat_recoding: true, - strip: png::Headers::None, - use_heuristics: false, - } + options } fn test_it_converts(input: &Path,