From 1c82313c985484c69d1cdb686320578972f064b4 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sat, 22 Feb 2020 17:19:57 -0800 Subject: [PATCH] Benchmark more jpeg decoders. Do downsizing decode with jpeg_decoder. --- Cargo.lock | 159 ++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 3 + benches/image.rs | 33 ++++++++-- src/library.rs | 49 +++++++++++---- 4 files changed, 225 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 214591b..0a7a2f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,12 @@ version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + [[package]] name = "atty" version = "0.2.11" @@ -465,6 +471,12 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" +[[package]] +name = "dunce" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0ad6bf6a88548d1126045c413548df1453d9be094a8ab9fd59bf1fdd338da4f" + [[package]] name = "either" version = "1.5.3" @@ -526,7 +538,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3684708dacd3b83f4bbe6506d4ccb08bed3c16f521d34366f131b9ecd1884431" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.2.0", +] + +[[package]] +name = "foreign-types-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "405b19947fc6a4a2c40bb4b47a220d7feba220c888aa160f4ad5c1c673f9062e" +dependencies = [ + "proc-macro2 1.0.8", + "quote 1.0.2", + "syn", ] [[package]] @@ -535,6 +568,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61f9b85573bf0f203eed3633f5018abce85250886a62ca073e0eee022ed564d" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -644,6 +683,12 @@ dependencies = [ "byteorder 1.3.4", ] +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + [[package]] name = "generic-array" version = "0.12.3" @@ -1014,6 +1059,12 @@ dependencies = [ "tiff", ] +[[package]] +name = "imgref" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45f5dd9db42f924c8e75d53777c4ea87a024b162ddb4af5dbe7c1add901a212" + [[package]] name = "indexmap" version = "1.3.2" @@ -1115,6 +1166,28 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lcms2" +version = "5.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67b7b6d94163f9d151626192f29bd6863b7ed98876838757ffdcd75e0505ac3" +dependencies = [ + "foreign-types 0.4.0", + "lcms2-sys", +] + +[[package]] +name = "lcms2-sys" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9297fc1600441b37ef05feb983c8150a88e4cfb3a2ce146b0f2615c00e9d8191" +dependencies = [ + "cc", + "dunce", + "libc", + "pkg-config", +] + [[package]] name = "libc" version = "0.2.66" @@ -1143,6 +1216,20 @@ dependencies = [ "libc", ] +[[package]] +name = "load_image" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b6842c9665e85c19ac2f2235c127deb9f9195aece72d55e4ee2cc97de7682c" +dependencies = [ + "imgref", + "lcms2", + "lodepng", + "mozjpeg", + "rexif", + "rgb", +] + [[package]] name = "lock_api" version = "0.3.3" @@ -1152,6 +1239,16 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lodepng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ed9859b15e009b494528f32ad054c5baf67afabf3c05d27973ea151563d430" +dependencies = [ + "libc", + "rgb", +] + [[package]] name = "log" version = "0.3.9" @@ -1291,6 +1388,30 @@ dependencies = [ "ws2_32-sys", ] +[[package]] +name = "mozjpeg" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f672ed5c96e386d6436643104c9bdc175d4b79ae3eb30538b7a58eb55f3bf318" +dependencies = [ + "arrayvec", + "libc", + "mozjpeg-sys", + "rgb", +] + +[[package]] +name = "mozjpeg-sys" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5b1e91d61ddd10490bcf67f488a09f07e39c41c1927d0142748ede14a1f97d" +dependencies = [ + "cc", + "dunce", + "libc", + "nasm-rs", +] + [[package]] name = "multipart" version = "0.16.1" @@ -1309,6 +1430,15 @@ dependencies = [ "twoway", ] +[[package]] +name = "nasm-rs" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea4fa2ba8f650120445ddcf137d8a6370ff01d55f00e6b29e0ab01b7c846464" +dependencies = [ + "rayon", +] + [[package]] name = "native-tls" version = "0.2.3" @@ -1431,7 +1561,7 @@ checksum = "973293749822d7dd6370d6da1e523b0d1db19f06c459134c658b2a4261378b52" dependencies = [ "bitflags", "cfg-if", - "foreign-types", + "foreign-types 0.3.2", "lazy_static 1.4.0", "libc", "openssl-sys", @@ -1548,7 +1678,9 @@ dependencies = [ "google_api_auth", "hexihasher", "image", + "jpeg-decoder", "lazy_static 1.4.0", + "load_image", "log 0.4.8", "mime_guess 2.0.1", "prometheus", @@ -1558,6 +1690,7 @@ dependencies = [ "rust-embed", "serde", "serde_json", + "stb_image", "stderrlog", "structopt", "tempdir", @@ -2036,6 +2169,18 @@ dependencies = [ "winreg", ] +[[package]] +name = "rexif" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958f3866c3a81ceaca304745792491944d0c9ed36e46eea43984b88cfac016f2" + +[[package]] +name = "rgb" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ec4ab2cf0b27e111e266e161cf7f9efd20125a161190da1c0945c4a4408fef3" + [[package]] name = "ring" version = "0.16.11" @@ -2377,6 +2522,16 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "stb_image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda28e54a600dfee451fbab5eddef5e987e50f7ace7ceced3087dfb043ee7692" +dependencies = [ + "gcc", + "libc", +] + [[package]] name = "stderrlog" version = "0.4.3" diff --git a/Cargo.toml b/Cargo.toml index e1e99ef..702a089 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ image = { version = "0.23.0" } #, default-features = false, features = ["jpeg"] rust-embed = "5.2.0" mime_guess = "2.0.1" rocksdb = "0.13.0" +jpeg-decoder = "0.1.18" [dependencies.prometheus] features = ["process"] @@ -33,6 +34,8 @@ version = "0.7.0" [dev-dependencies] tempdir = "0.3.7" criterion = "0.3" +stb_image = "0.2.2" +load_image = "2.12.0" [[bench]] name = "image" diff --git a/benches/image.rs b/benches/image.rs index 231f553..03b3274 100644 --- a/benches/image.rs +++ b/benches/image.rs @@ -1,9 +1,11 @@ use criterion::BenchmarkId; use criterion::Throughput; use criterion::{black_box, criterion_group, criterion_main, Criterion}; - use image::imageops; use image::GenericImageView; +use load_image as load_image_crate; +use stb_image::image::load as stb_load; +use stb_image::image::LoadResult; use photosync::library::load_image; use photosync::library::resize; @@ -12,9 +14,31 @@ use photosync::library::FilterType; pub fn criterion_benchmark(c: &mut Criterion) { const TEST_IMAGE_PATH: &'static str = "testdata/image.jpg"; - let img = load_image(TEST_IMAGE_PATH).expect("failed to load test image"); + let img = load_image(TEST_IMAGE_PATH, None, None).expect("failed to load test image"); + c.bench_function("Load image", |b| { - b.iter(|| black_box(load_image(TEST_IMAGE_PATH))) + b.iter(|| black_box(load_image(TEST_IMAGE_PATH, None, None))) + }); + + c.bench_function("Load image 256x256", |b| { + b.iter(|| black_box(load_image(TEST_IMAGE_PATH, Some(256), Some(256)))) + }); + + c.bench_function("Load load_image", |b| { + b.iter(|| { + black_box(load_image_crate::load_image(TEST_IMAGE_PATH, true).expect("failed to load")) + }) + }); + c.bench_function("Load stb_image", |b| { + b.iter(|| match stb_load(TEST_IMAGE_PATH) { + LoadResult::Error(err) => panic!(err), + LoadResult::ImageU8(img) => { + black_box(img); + } + LoadResult::ImageF32(img) => { + black_box(img); + } + }) }); let mut group = c.benchmark_group("Resizing"); @@ -81,7 +105,8 @@ pub fn criterion_benchmark(c: &mut Criterion) { size, |b, size| { b.iter(|| { - let img = load_image(TEST_IMAGE_PATH).expect("failed to load test image"); + let img = load_image(TEST_IMAGE_PATH, size.0, size.1) + .expect("failed to load test image"); let small_img = resize(&img, *size, FilterType::Builtin(imageops::Lanczos3)); black_box(save_to_jpeg_bytes(&small_img)) }) diff --git a/src/library.rs b/src/library.rs index 28ee28d..0e61eb1 100644 --- a/src/library.rs +++ b/src/library.rs @@ -1,6 +1,7 @@ use std::fs; use std::fs::File; use std::io; +use std::io::BufReader; use std::path::Path; use std::path::PathBuf; use std::sync::Arc; @@ -10,8 +11,10 @@ use image::imageops; use image::DynamicImage; use image::GenericImage; use image::GenericImageView; +use image::ImageBuffer; use image::ImageFormat; use image::ImageResult; +use jpeg_decoder::Decoder; use log::error; use log::info; use log::warn; @@ -22,7 +25,7 @@ use rocksdb::IteratorMode; use rocksdb::DB; // Used to ensure DB is invalidated after schema changes. -const LIBRARY_GENERATION: &'static str = "12"; +const LIBRARY_GENERATION: &'static str = "13"; #[derive(Clone)] pub struct Library { @@ -31,13 +34,35 @@ pub struct Library { cache_db: Arc, } -pub fn load_image

(path: P) -> ImageResult +pub fn load_image

( + path: P, + width_hint: Option, + height_hint: Option, +) -> Result> where P: AsRef, { - image::io::Reader::open(&path)? - .with_guessed_format()? - .decode() + // TODO(wathiede): fall back to image::load_image when jpeg decoding fails. + let file = File::open(path).expect("failed to open file"); + let mut decoder = Decoder::new(BufReader::new(file)); + let (w, h) = match (width_hint, height_hint) { + (Some(w), Some(h)) => { + let got = decoder.scale(w as u16, h as u16)?; + info!("Hinted at {}x{}, got {}x{}", w, h, got.0, got.1); + (got.0 as u32, got.1 as u32) + } + // TODO(wathiede): handle partial hints by grabbing info and then computing the absent + // dimenison. + _ => { + decoder.read_info()?; + let info = decoder.info().unwrap(); + (info.width as u32, info.height as u32) + } + }; + let pixels = decoder.decode().expect("failed to decode image"); + Ok(DynamicImage::ImageRgb8( + ImageBuffer::from_raw(w, h, pixels).expect("pixels to small for given dimensions"), + )) } #[derive(Clone, Copy, Debug)] @@ -241,18 +266,15 @@ impl Library { media_items_id: &str, dimensions: (Option, Option), filter: FilterType, - ) -> Result, io::Error> { + ) -> Result, Box> { match self.original(&media_items_id) { None => { warn!("Couldn't find original {}", &media_items_id); - Err(io::Error::new( - io::ErrorKind::NotFound, - format!("{}", media_items_id), - )) + Err(io::Error::new(io::ErrorKind::NotFound, format!("{}", media_items_id)).into()) } Some(path) => { - let orig_img = - load_image(&path).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + let orig_img = load_image(&path, dimensions.0, dimensions.1)?; + //.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let img = resize(&orig_img, dimensions, filter); let buf = save_to_jpeg_bytes(&img) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; @@ -288,7 +310,8 @@ impl Library { let bytes = match self.generate_thumbnail( media_items_id, dimensions, - FilterType::Builtin(imageops::FilterType::Lanczos3), + FilterType::Nearest, + //FilterType::Builtin(imageops::FilterType::Lanczos3), ) { Ok(bytes) => bytes, Err(e) => {