# hdf5plugin benchmark

This notebook displays benchmark of the HDF5 compression filters provided by hdf5plugin as well as “gzip” and “lzf”. Benchmarks can be run with the benchmark.py companion script and saved as json files that this notebooks can load and display.

## Preparation

Dependencies: h5py ipympl matplotlib numpy

### Install `hdf5plugin`

- Pre-compiled package:

  - Only SSE2 SIMD instructions enabled

  - OpenMP disabled

  - `pip install hdf5plugin` or `conda install -c conda-forge hdf5plugin`

- From source:

  - SIMD instructions available on host enabled

  - OpenMP enabled

  - `pip install hdf5plugin --no-binary hdf5plugin`

### Get dataset

Download benchmark dataset: http://www.silx.org/pub/pyFAI/pyFAI_UM_2020/data_ID13/kevlar.h5

    [ ]:

    !wget -O /dev/shm/kevlar.h5 http://www.silx.org/pub/pyFAI/pyFAI_UM_2020/data_ID13/kevlar.h5

### What hardware is available

The CPU layout can have a significative impact on performance for multi-threaded execution

    [1]:

    !numactl --hardware

    available: 2 nodes (0-1)
    node 0 cpus: 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38
    node 0 size: 192072 MB
    node 0 free: 170763 MB
    node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39
    node 1 size: 193489 MB
    node 1 free: 164284 MB
    node distances:
    node   0   1
      0:  10  21
      1:  21  10

### Load plotting function

    [2]:

    # Initialization
    #%matplotlib inline
    %matplotlib widget

    from matplotlib import pyplot as plt
    from matplotlib.colors import LogNorm

    import numpy
    import h5py
    import hdf5plugin
    from h5glance import H5Glance

    [3]:

    # Plot helper

    from typing import NamedTuple

    class Result(NamedTuple):
        """Store benchmark result"""

        raw_nbytes: int
        compressed_nbytes: int
        write_duration: float
        read_duration: float
        chunks: tuple[int]

        compression_rate = property(
            lambda self: self.raw_nbytes / self.compressed_nbytes)
        write_speed = property(
            lambda self: (self.raw_nbytes / 1024**2) / self.write_duration,
            doc="Unit: MB/sec")
        read_speed = property(
            lambda self: (self.raw_nbytes / 1024**2) / self.read_duration,
            doc="Unit: MB/sec")

    def display_results(title: str='', key=None, **results):
        """Plot comparison of compression results.

        :param title: Additional dataset name
        :param key: Key function to customize the sort
        :param **results: {compression-name: Result} mapping
        """
        # Sort results by compression rate
        results = dict(sorted(
            results.items(),
            key=(lambda item: item[1].compression_rate) if key is None else key,
        ))

        names = list(results.keys())
        write_speed = numpy.array([r.write_speed for r in results.values()])
        read_speed = numpy.array([r.read_speed for r in results.values()])
        c_rate = numpy.array([r.compression_rate for r in results.values()])

        #print(f"hdf5plugin_config: {results[names[0]].config}")

        fig, axes = plt.subplots(1, 2)
        speed_plt, c_rate_plt = axes

        fig.suptitle(title)

        ticks = numpy.arange(len(names))

        # Speed
        speed_plt.barh(y=ticks, width=write_speed, height=0.4, align='edge', label="Write")
        speed_plt.barh(y=ticks-.4, width=read_speed, height=0.4, align='edge', label="Read")
        speed_plt.set_xlabel("Speed [MB/s]")
        speed_plt.autoscale(axis='y')
        speed_plt.yaxis.set_ticks(ticks)
        speed_plt.yaxis.set_ticklabels(names, fontsize=8)
        speed_plt.set_ylim(-0.5, len(ticks)-0.5)
        speed_plt.grid(axis="x")
        speed_plt.legend()

        # Compression ratio
        c_rate_plt.barh(y=ticks, width=c_rate)
        c_rate_plt.set_xlabel("Compression rate")
        c_rate_plt.autoscale(axis='y')
        c_rate_plt.yaxis.set_ticks(ticks)
        c_rate_plt.yaxis.set_ticklabels([''] * len(ticks))
        c_rate_plt.set_ylim(-0.5,len(ticks)-0.5)
        c_rate_plt.axvline(1, color="red")
        c_rate_plt.grid(axis="x")

        fig.tight_layout()

        # Tooltip
        tooltip = c_rate_plt.annotate(
            "", (0, 0), backgroundcolor="lightyellow",
            verticalalignment="top", xycoords='figure pixels')
        tooltip.set_visible(False)

        def hover(event):
            if event.inaxes in (speed_plt, c_rate_plt):
                index = numpy.clip(round(event.ydata), 0, len(names)-1)
                tooltip.set_text(
                    f"{names[index]}\n"
                    f"Comp. Rate: {c_rate[index]:.2f}x\n"
                    f"Write: {write_speed[index]:.1f}MB/s\n"
                    f"Read: {read_speed[index]:.1f}MB/s")
                tooltip.set_x(event.x + 15)
                tooltip.set_y(event.y)
                tooltip.set_visible(True)
            else:
                tooltip.set_visible(False)
            fig.canvas.draw_idle()

        def resize(event):
            fig.tight_layout()

        fig.canvas.mpl_connect("motion_notify_event", hover)
        fig.canvas.mpl_connect("resize_event", resize)

### Display information

    [4]:

    import os
    import sys

    print('Python:', sys.version)

    config = hdf5plugin.get_config()
    print(f"""hdf5plugin:
    * Version: {hdf5plugin.version}
    * Build config:
    {'''
    '''.join(f'  - {k}: {v}' for k, v in config.build_config._asdict().items())}
    """)

    Python: 3.9.12 | packaged by conda-forge | (main, Mar 24 2022, 23:22:55)
    [GCC 10.3.0]
    hdf5plugin:
    * Version: 4.2.0
    * Build config:
      - openmp: True
      - native: True
      - bmi2: True
      - sse2: True
      - avx2: True
      - avx512: False
      - cpp11: True
      - cpp14: True
      - ipp: False
      - filter_file_extension: .so
      - embedded_filters: ('blosc', 'blosc2', 'bshuf', 'bzip2', 'fcidecomp', 'lz4', 'sz', 'sz3', 'zfp', 'zstd')

## Benchmark results

    [5]:

    import json

    benchmarks = json.load(open("benchmark.json"))
    config = benchmarks["config"]
    results = {k: Result(**v) for k, v in benchmarks["results"].items()}

    [6]:

    display_results(
        title="Filters comparison",
        **results
    )

    [ ]: