Benchmark more jpeg decoders. Do downsizing decode with jpeg_decoder.

This commit is contained in:
Bill Thiede 2020-02-22 17:19:57 -08:00
parent 7f64a4d2f6
commit 1c82313c98
4 changed files with 225 additions and 19 deletions

159
Cargo.lock generated
View File

@ -30,6 +30,12 @@ version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c" checksum = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c"
[[package]]
name = "arrayvec"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.11" version = "0.2.11"
@ -465,6 +471,12 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
[[package]]
name = "dunce"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0ad6bf6a88548d1126045c413548df1453d9be094a8ab9fd59bf1fdd338da4f"
[[package]] [[package]]
name = "either" name = "either"
version = "1.5.3" version = "1.5.3"
@ -526,7 +538,28 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [ 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]] [[package]]
@ -535,6 +568,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "foreign-types-shared"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61f9b85573bf0f203eed3633f5018abce85250886a62ca073e0eee022ed564d"
[[package]] [[package]]
name = "fuchsia-cprng" name = "fuchsia-cprng"
version = "0.1.1" version = "0.1.1"
@ -644,6 +683,12 @@ dependencies = [
"byteorder 1.3.4", "byteorder 1.3.4",
] ]
[[package]]
name = "gcc"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.12.3" version = "0.12.3"
@ -1014,6 +1059,12 @@ dependencies = [
"tiff", "tiff",
] ]
[[package]]
name = "imgref"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a45f5dd9db42f924c8e75d53777c4ea87a024b162ddb4af5dbe7c1add901a212"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.3.2" version = "1.3.2"
@ -1115,6 +1166,28 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 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]] [[package]]
name = "libc" name = "libc"
version = "0.2.66" version = "0.2.66"
@ -1143,6 +1216,20 @@ dependencies = [
"libc", "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]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.3.3" version = "0.3.3"
@ -1152,6 +1239,16 @@ dependencies = [
"scopeguard", "scopeguard",
] ]
[[package]]
name = "lodepng"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ed9859b15e009b494528f32ad054c5baf67afabf3c05d27973ea151563d430"
dependencies = [
"libc",
"rgb",
]
[[package]] [[package]]
name = "log" name = "log"
version = "0.3.9" version = "0.3.9"
@ -1291,6 +1388,30 @@ dependencies = [
"ws2_32-sys", "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]] [[package]]
name = "multipart" name = "multipart"
version = "0.16.1" version = "0.16.1"
@ -1309,6 +1430,15 @@ dependencies = [
"twoway", "twoway",
] ]
[[package]]
name = "nasm-rs"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea4fa2ba8f650120445ddcf137d8a6370ff01d55f00e6b29e0ab01b7c846464"
dependencies = [
"rayon",
]
[[package]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.3" version = "0.2.3"
@ -1431,7 +1561,7 @@ checksum = "973293749822d7dd6370d6da1e523b0d1db19f06c459134c658b2a4261378b52"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cfg-if", "cfg-if",
"foreign-types", "foreign-types 0.3.2",
"lazy_static 1.4.0", "lazy_static 1.4.0",
"libc", "libc",
"openssl-sys", "openssl-sys",
@ -1548,7 +1678,9 @@ dependencies = [
"google_api_auth", "google_api_auth",
"hexihasher", "hexihasher",
"image", "image",
"jpeg-decoder",
"lazy_static 1.4.0", "lazy_static 1.4.0",
"load_image",
"log 0.4.8", "log 0.4.8",
"mime_guess 2.0.1", "mime_guess 2.0.1",
"prometheus", "prometheus",
@ -1558,6 +1690,7 @@ dependencies = [
"rust-embed", "rust-embed",
"serde", "serde",
"serde_json", "serde_json",
"stb_image",
"stderrlog", "stderrlog",
"structopt", "structopt",
"tempdir", "tempdir",
@ -2036,6 +2169,18 @@ dependencies = [
"winreg", "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]] [[package]]
name = "ring" name = "ring"
version = "0.16.11" version = "0.16.11"
@ -2377,6 +2522,16 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 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]] [[package]]
name = "stderrlog" name = "stderrlog"
version = "0.4.3" version = "0.4.3"

View File

@ -25,6 +25,7 @@ image = { version = "0.23.0" } #, default-features = false, features = ["jpeg"]
rust-embed = "5.2.0" rust-embed = "5.2.0"
mime_guess = "2.0.1" mime_guess = "2.0.1"
rocksdb = "0.13.0" rocksdb = "0.13.0"
jpeg-decoder = "0.1.18"
[dependencies.prometheus] [dependencies.prometheus]
features = ["process"] features = ["process"]
@ -33,6 +34,8 @@ version = "0.7.0"
[dev-dependencies] [dev-dependencies]
tempdir = "0.3.7" tempdir = "0.3.7"
criterion = "0.3" criterion = "0.3"
stb_image = "0.2.2"
load_image = "2.12.0"
[[bench]] [[bench]]
name = "image" name = "image"

View File

@ -1,9 +1,11 @@
use criterion::BenchmarkId; use criterion::BenchmarkId;
use criterion::Throughput; use criterion::Throughput;
use criterion::{black_box, criterion_group, criterion_main, Criterion}; use criterion::{black_box, criterion_group, criterion_main, Criterion};
use image::imageops; use image::imageops;
use image::GenericImageView; 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::load_image;
use photosync::library::resize; use photosync::library::resize;
@ -12,9 +14,31 @@ use photosync::library::FilterType;
pub fn criterion_benchmark(c: &mut Criterion) { pub fn criterion_benchmark(c: &mut Criterion) {
const TEST_IMAGE_PATH: &'static str = "testdata/image.jpg"; 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| { 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"); let mut group = c.benchmark_group("Resizing");
@ -81,7 +105,8 @@ pub fn criterion_benchmark(c: &mut Criterion) {
size, size,
|b, size| { |b, size| {
b.iter(|| { 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)); let small_img = resize(&img, *size, FilterType::Builtin(imageops::Lanczos3));
black_box(save_to_jpeg_bytes(&small_img)) black_box(save_to_jpeg_bytes(&small_img))
}) })

View File

@ -1,6 +1,7 @@
use std::fs; use std::fs;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::io::BufReader;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
@ -10,8 +11,10 @@ use image::imageops;
use image::DynamicImage; use image::DynamicImage;
use image::GenericImage; use image::GenericImage;
use image::GenericImageView; use image::GenericImageView;
use image::ImageBuffer;
use image::ImageFormat; use image::ImageFormat;
use image::ImageResult; use image::ImageResult;
use jpeg_decoder::Decoder;
use log::error; use log::error;
use log::info; use log::info;
use log::warn; use log::warn;
@ -22,7 +25,7 @@ use rocksdb::IteratorMode;
use rocksdb::DB; use rocksdb::DB;
// Used to ensure DB is invalidated after schema changes. // Used to ensure DB is invalidated after schema changes.
const LIBRARY_GENERATION: &'static str = "12"; const LIBRARY_GENERATION: &'static str = "13";
#[derive(Clone)] #[derive(Clone)]
pub struct Library { pub struct Library {
@ -31,13 +34,35 @@ pub struct Library {
cache_db: Arc<DB>, cache_db: Arc<DB>,
} }
pub fn load_image<P>(path: P) -> ImageResult<DynamicImage> pub fn load_image<P>(
path: P,
width_hint: Option<u32>,
height_hint: Option<u32>,
) -> Result<DynamicImage, Box<dyn std::error::Error>>
where where
P: AsRef<Path>, P: AsRef<Path>,
{ {
image::io::Reader::open(&path)? // TODO(wathiede): fall back to image::load_image when jpeg decoding fails.
.with_guessed_format()? let file = File::open(path).expect("failed to open file");
.decode() 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)] #[derive(Clone, Copy, Debug)]
@ -241,18 +266,15 @@ impl Library {
media_items_id: &str, media_items_id: &str,
dimensions: (Option<u32>, Option<u32>), dimensions: (Option<u32>, Option<u32>),
filter: FilterType, filter: FilterType,
) -> Result<Vec<u8>, io::Error> { ) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
match self.original(&media_items_id) { match self.original(&media_items_id) {
None => { None => {
warn!("Couldn't find original {}", &media_items_id); warn!("Couldn't find original {}", &media_items_id);
Err(io::Error::new( Err(io::Error::new(io::ErrorKind::NotFound, format!("{}", media_items_id)).into())
io::ErrorKind::NotFound,
format!("{}", media_items_id),
))
} }
Some(path) => { Some(path) => {
let orig_img = let orig_img = load_image(&path, dimensions.0, dimensions.1)?;
load_image(&path).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; //.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let img = resize(&orig_img, dimensions, filter); let img = resize(&orig_img, dimensions, filter);
let buf = save_to_jpeg_bytes(&img) let buf = save_to_jpeg_bytes(&img)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
@ -288,7 +310,8 @@ impl Library {
let bytes = match self.generate_thumbnail( let bytes = match self.generate_thumbnail(
media_items_id, media_items_id,
dimensions, dimensions,
FilterType::Builtin(imageops::FilterType::Lanczos3), FilterType::Nearest,
//FilterType::Builtin(imageops::FilterType::Lanczos3),
) { ) {
Ok(bytes) => bytes, Ok(bytes) => bytes,
Err(e) => { Err(e) => {