Skip to content

Commit

Permalink
Extra benchmark inputs: non-compressed, non-filtered data. (#420)
Browse files Browse the repository at this point in the history
  • Loading branch information
anforowicz authored Nov 9, 2023
1 parent 310edea commit ec2d257
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 9 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ flate2 = "1.0"
miniz_oxide = { version = "0.7.1", features = ["simd"] }

[dev-dependencies]
byteorder = "1.5.0"
clap = { version = "3.0", features = ["derive"] }
criterion = "0.3.1"
getopts = "0.2.14"
Expand Down
19 changes: 18 additions & 1 deletion benches/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use std::fs;
use criterion::{criterion_group, criterion_main, Criterion, Throughput};
use png::Decoder;

mod png_generator;

fn load_all(c: &mut Criterion) {
for entry in fs::read_dir("tests/benches/").unwrap().flatten() {
match entry.path().extension() {
Expand All @@ -13,14 +15,29 @@ fn load_all(c: &mut Criterion) {
let data = fs::read(entry.path()).unwrap();
bench_file(c, data, entry.file_name().into_string().unwrap());
}

bench_noncompressed_png(c, 8);
bench_noncompressed_png(c, 128);
}

criterion_group!(benches, load_all);
criterion_main!(benches);

fn bench_noncompressed_png(c: &mut Criterion, width: u32) {
let mut data = Vec::new();
png_generator::write_noncompressed_png(&mut data, width);
bench_file(
c,
data,
format!("png_generator::noncompressed-{width}x{width}.png"),
);
}

fn bench_file(c: &mut Criterion, data: Vec<u8>, name: String) {
let mut group = c.benchmark_group("decode");
group.sample_size(20);
if data.len() > 100000 {
group.sample_size(20);
}

let decoder = Decoder::new(&*data);
let mut reader = decoder.read_info().unwrap();
Expand Down
93 changes: 93 additions & 0 deletions benches/png_generator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
use byteorder::WriteBytesExt;
use std::io::Write;

/// Generates a store-only, non-compressed image:
///
/// * `00` compression mode (i.e.`BTYPE` = `00` = no compression) is used
/// * No filter is applied to the image rows
///
/// Currently the image always has the following properties:
///
/// * Single `IDAT` chunk
/// * Zlib chunks of maximum possible size
/// * 8-bit RGBA
///
/// These images are somewhat artificial, but may be useful for benchmarking performance of parts
/// outside of `fdeflate` crate and/or the `unfilter` function (e.g. these images were originally
/// used to evaluate changes to minimize copying of image pixels between various buffers - see
/// [this
/// discussion](https://github.com/image-rs/image-png/discussions/416#discussioncomment-7436871)
/// for more details).
pub fn write_noncompressed_png(w: &mut impl Write, width: u32) {
write_png_sig(w);
write_ihdr(w, width);
write_noncompressed_idat(w, width);
write_iend(w);
}

fn write_png_sig(w: &mut impl Write) {
const SIG: [u8; 8] = [137, 80, 78, 71, 13, 10, 26, 10];
w.write_all(&SIG).unwrap();
}

fn write_chunk(w: &mut impl Write, chunk_type: &[u8], data: &[u8]) {
let crc = {
let input = chunk_type
.iter()
.copied()
.chain(data.iter().copied())
.collect::<Vec<_>>();
crc32fast::hash(input.as_slice())
};
w.write_u32::<byteorder::BigEndian>(data.len() as u32)
.unwrap();
w.write_all(chunk_type).unwrap();
w.write_all(data).unwrap();
w.write_u32::<byteorder::BigEndian>(crc).unwrap();
}

fn write_ihdr(w: &mut impl Write, width: u32) {
let mut data = Vec::new();
data.write_u32::<byteorder::BigEndian>(width).unwrap();
data.write_u32::<byteorder::BigEndian>(width).unwrap(); // height
data.write_u8(8).unwrap(); // bit depth = always 8-bits per channel
data.write_u8(6).unwrap(); // color type = color + alpha
data.write_u8(0).unwrap(); // compression method (0 is the only allowed value)
data.write_u8(0).unwrap(); // filter method (0 is the only allowed value)
data.write_u8(0).unwrap(); // interlace method = no interlacing
write_chunk(w, b"IHDR", &data);
}

fn write_noncompressed_idat(w: &mut impl Write, width: u32) {
// Generate arbitrary test pixels.
let image_pixels = {
let mut row = Vec::new();
row.write_u8(0).unwrap(); // filter = no filter

let row_pixels = (0..width)
.map(|i| {
let color: u8 = (i * 255 / width) as u8;
let alpha: u8 = 0xff;
[color, 255 - color, color / 2, alpha]
})
.flatten();
row.extend(row_pixels);

std::iter::repeat(row)
.take(width as usize)
.flatten()
.collect::<Vec<_>>()
};

let mut zlib_data = Vec::new();
let mut store_only_compressor =
fdeflate::StoredOnlyCompressor::new(std::io::Cursor::new(&mut zlib_data)).unwrap();
store_only_compressor.write_data(&image_pixels).unwrap();
store_only_compressor.finish().unwrap();

write_chunk(w, b"IDAT", &zlib_data);
}

fn write_iend(w: &mut impl Write) {
write_chunk(w, b"IEND", &[]);
}
17 changes: 9 additions & 8 deletions tests/benches/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
Copyrights:

Lohengrin_-_Illustrated_Sporting_and_Dramatic_News.png: Public Domain, according to Wikimedia
kodim*.png: Eastman Kodak Company, released for unrestricted use
Transparency.png: Public Domain, according to Wikimedia
* `Lohengrin_-_Illustrated_Sporting_and_Dramatic_News.png`: Public Domain, according to Wikimedia
* `kodim*.png`: Eastman Kodak Company, released for unrestricted use
* `Transparency.png`: Public Domain, according to Wikimedia

The images use different filtering:
Lohengrin: no filtering
kodim02: mixed
kodim07: mainly paeth
kodim17: mainly sub
kodim23: mixed

* Lohengrin: no filtering
* kodim02: mixed
* kodim07: mainly paeth
* kodim17: mainly sub
* kodim23: mixed

0 comments on commit ec2d257

Please sign in to comment.