Skip to content

Commit 13e2437

Browse files
committed
Benchmark saving and loading various common formats
1 parent 667e648 commit 13e2437

1 file changed

Lines changed: 82 additions & 19 deletions

File tree

Tests/benchmarks.py

Lines changed: 82 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,21 @@
2020
TYPE_CHECKING = False
2121
if TYPE_CHECKING:
2222
from collections.abc import Callable
23+
from typing import Any
2324

24-
BenchmarkSave = Callable[[Image.Image], None]
25-
25+
from _pytest.mark.structures import ParameterSet
2626
from pytest_benchmark.fixture import ( # type: ignore[unused-ignore, import-not-found]
2727
BenchmarkFixture,
2828
)
2929

30+
BenchmarkSave = Callable[[Image.Image], None]
31+
32+
3033
if not (find_spec("pytest_benchmark") or find_spec("pytest_codspeed")):
3134
pytest.skip("pytest-benchmark or pytest-codspeed required", allow_module_level=True)
3235

36+
IMAGES_PATH = pathlib.Path(__file__).parent / "images"
37+
3338
_save_results = os.environ.get("PILLOW_BENCHMARK_SAVE_RESULTS_PATH")
3439
SAVE_RESULTS_PATH = pathlib.Path(_save_results) if _save_results else None
3540

@@ -43,12 +48,6 @@
4348
# for the benchmark run, so that throughput (Mpx/s) can be recomputed in the future.
4449
SIZES = [(1237, 811)] # Primes, non-power-of-two, asymmetric, approximately 1024x1024
4550

46-
# For benchmarks that act on test fixture files, these are the paths loaded.
47-
IMAGES_PATH = pathlib.Path(__file__).parent / "images"
48-
PATHS = [
49-
IMAGES_PATH / "flower2.jpg",
50-
]
51-
5251
# These are derived from the other configuration, above.
5352
RGB_MODES = [mode for mode in MODES if mode.startswith("RGB")]
5453
ALPHA_MODES = [mode for mode in MODES if mode.endswith("A")]
@@ -59,10 +58,6 @@ def _format_size(size: tuple[int, int]) -> str:
5958
return f"{size[0]}x{size[1]}"
6059

6160

62-
def _format_path(path: pathlib.Path) -> str:
63-
return path.name
64-
65-
6661
@pytest.fixture
6762
def bench(
6863
request: pytest.FixtureRequest,
@@ -567,21 +562,89 @@ def test_draw_lines_blend(
567562

568563

569564
@pytest.mark.benchmark(group="load")
570-
@pytest.mark.parametrize("path", PATHS, ids=_format_path)
565+
@pytest.mark.parametrize(
566+
"path",
567+
# Commonly used formats and decoder settings.
568+
[
569+
IMAGES_PATH / "avif" / "hopper.avif", # AVIF, RGB
570+
IMAGES_PATH / "hopper.bmp", # BMP, RGB
571+
IMAGES_PATH / "hopper.gif", # GIF, P
572+
IMAGES_PATH / "hopper.jpg", # JPEG, RGB
573+
IMAGES_PATH / "hopper.png", # PNG, RGB
574+
IMAGES_PATH / "hopper.tif", # TIFF, raw
575+
IMAGES_PATH / "hopper.webp", # WebP, lossy RGB
576+
IMAGES_PATH / "hopper_gray.jpg", # JPEG, L
577+
IMAGES_PATH / "hopper_lossless.webp", # WebP, lossless
578+
IMAGES_PATH / "hopper_lzw.tif", # TIFF, LZW
579+
IMAGES_PATH / "hopper_wal.png", # PNG, P
580+
IMAGES_PATH / "pil123rgba.png", # PNG, RGBA
581+
IMAGES_PATH / "pil_sample_cmyk.jpg", # JPEG, CMYK
582+
IMAGES_PATH / "tiff_adobe_deflate.tif", # TIFF, deflate
583+
IMAGES_PATH / "transparent.webp", # WebP, RGBA
584+
IMAGES_PATH / "uncompressed_rgb.dds", # DDS, uncompressed (see PR #9943)
585+
],
586+
ids=lambda path: path.name,
587+
)
571588
def test_load(bench: BenchmarkFixture, path: pathlib.Path) -> None:
572589
def run() -> None:
573590
with Image.open(path) as im:
574591
im.load()
575592

593+
try:
594+
run()
595+
except Exception as e: # e.g. the codec is not available in this build
596+
pytest.skip(f"cannot load {path.name}: {e}")
576597
bench(run)
577598

578599

600+
def _save_format(
601+
format: str,
602+
mode: str,
603+
**options: Any,
604+
) -> ParameterSet:
605+
id = "-".join([format, mode, *(f"{k}={v}" for k, v in options.items())])
606+
return pytest.param(format, mode, options, id=id)
607+
608+
579609
@pytest.mark.benchmark(group="save")
580-
@pytest.mark.parametrize("path", PATHS, ids=_format_path)
581-
def test_save_jpeg(bench: BenchmarkFixture, path: pathlib.Path) -> None:
582-
with Image.open(path) as im:
583-
im.load()
584-
bench(lambda: im.save(BytesIO(), format="JPEG", quality=85))
610+
@pytest.mark.parametrize(
611+
"format, mode, options",
612+
[
613+
_save_format("AVIF", "RGB"),
614+
_save_format("BMP", "RGB"),
615+
_save_format("GIF", "P"),
616+
_save_format("JPEG", "RGB"),
617+
_save_format("JPEG", "L"),
618+
_save_format("PNG", "L"),
619+
_save_format("PNG", "P"),
620+
_save_format("PNG", "RGB"),
621+
_save_format("PNG", "RGBA"),
622+
_save_format("TIFF", "RGB"),
623+
_save_format("TIFF", "RGB", compression="tiff_lzw"),
624+
_save_format("TIFF", "RGB", compression="tiff_adobe_deflate"),
625+
_save_format("WEBP", "RGB"),
626+
_save_format("WEBP", "RGBA", lossless=True),
627+
],
628+
)
629+
@pytest.mark.parametrize("size", SIZES, ids=_format_size)
630+
def test_save(
631+
bench: BenchmarkFixture,
632+
format: str,
633+
mode: str,
634+
options: dict[str, Any],
635+
size: tuple[int, int],
636+
) -> None:
637+
im = make_pillow_image(mode, size)
638+
bench.extra_info["label"] = [format, *(f"{k}={v}" for k, v in options.items())]
639+
640+
def run() -> None:
641+
im.save(BytesIO(), format, **options)
642+
643+
try:
644+
run()
645+
except Exception as e: # e.g. the codec is not available in this build
646+
pytest.skip(f"cannot save {format}: {e}")
647+
bench(run)
585648

586649

587650
@pytest.mark.benchmark(group="allocate")
@@ -853,7 +916,7 @@ def test_quantize_grayscale_to_palette(
853916
"source_type",
854917
[
855918
"synthetic",
856-
*(pytest.param(image, id=f"{image.stem}") for image in PATHS),
919+
pytest.param(IMAGES_PATH / "flower2.jpg", id="flower2.jpg"),
857920
],
858921
)
859922
@pytest.mark.parametrize("palette_type", ["exact", "grayscale", "web"])

0 commit comments

Comments
 (0)