{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4ffb1e15-8ff1-4142-bbb8-e52c47e4eab8",
   "metadata": {
    "tags": []
   },
   "source": [
    "# hdf5plugin benchmark\n",
    "\n",
    "This notebook displays benchmark of the HDF5 compression filters provided by [hdf5plugin](https://github.com/silx-kit/hdf5plugin) as well as \"gzip\" and \"lzf\".\n",
    "Benchmarks can be run with the [benchmark.py](benchmark.py) companion script and saved as json files that this notebooks can load and display."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "502eff8a-51bf-4a62-b3c8-e48cc5d71aca",
   "metadata": {
    "tags": []
   },
   "source": [
    "## Preparation\n",
    "\n",
    "Dependencies: h5py ipympl matplotlib numpy\n",
    "\n",
    "### Install `hdf5plugin`\n",
    "\n",
    "- Pre-compiled package:\n",
    "  - Only SSE2 SIMD instructions enabled\n",
    "  - OpenMP disabled\n",
    "  - `pip install hdf5plugin` or `conda install -c conda-forge hdf5plugin`\n",
    "- From source:\n",
    "  - SIMD instructions available on host enabled\n",
    "  - OpenMP enabled\n",
    "  - `pip install hdf5plugin --no-binary hdf5plugin`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ffa8a089-8e7e-4f01-8995-0d7f2e259f81",
   "metadata": {},
   "source": [
    "### Get dataset\n",
    "\n",
    "Download benchmark dataset: http://www.silx.org/pub/pyFAI/pyFAI_UM_2020/data_ID13/kevlar.h5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a6de9268-77ad-4d5c-8a0a-a553c5373999",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "!wget -O /dev/shm/kevlar.h5 http://www.silx.org/pub/pyFAI/pyFAI_UM_2020/data_ID13/kevlar.h5"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ba3a41c-402a-4240-8cdb-51fda2f2deed",
   "metadata": {},
   "source": [
    "### What hardware is available\n",
    "\n",
    "The CPU layout can have a significative impact on performance for multi-threaded execution"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "6c526270-e938-49e9-9b70-ad3692048767",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "available: 2 nodes (0-1)\n",
      "node 0 cpus: 0 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38\n",
      "node 0 size: 192072 MB\n",
      "node 0 free: 170763 MB\n",
      "node 1 cpus: 1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39\n",
      "node 1 size: 193489 MB\n",
      "node 1 free: 164284 MB\n",
      "node distances:\n",
      "node   0   1 \n",
      "  0:  10  21 \n",
      "  1:  21  10 \n"
     ]
    }
   ],
   "source": [
    "!numactl --hardware"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0cdf1f00-40dc-4ad3-8960-c4a6ad82b6c4",
   "metadata": {},
   "source": [
    "### Load plotting function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "db584633-bc62-496b-88d6-c83520085769",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Initialization\n",
    "#%matplotlib inline\n",
    "%matplotlib widget\n",
    "\n",
    "from matplotlib import pyplot as plt\n",
    "from matplotlib.colors import LogNorm\n",
    "\n",
    "import numpy\n",
    "import h5py\n",
    "import hdf5plugin\n",
    "from h5glance import H5Glance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "64700d7d-bf8c-40b9-a59d-24cf6c7cb3ec",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Plot helper\n",
    "\n",
    "from typing import NamedTuple\n",
    "\n",
    "class Result(NamedTuple):\n",
    "    \"\"\"Store benchmark result\"\"\"\n",
    "\n",
    "    raw_nbytes: int\n",
    "    compressed_nbytes: int\n",
    "    write_duration: float\n",
    "    read_duration: float\n",
    "    chunks: tuple[int]\n",
    "\n",
    "    compression_rate = property(\n",
    "        lambda self: self.raw_nbytes / self.compressed_nbytes)\n",
    "    write_speed = property(\n",
    "        lambda self: (self.raw_nbytes / 1024**2) / self.write_duration,\n",
    "        doc=\"Unit: MB/sec\")\n",
    "    read_speed = property(\n",
    "        lambda self: (self.raw_nbytes / 1024**2) / self.read_duration,\n",
    "        doc=\"Unit: MB/sec\")\n",
    "\n",
    "    \n",
    "def display_results(title: str='', key=None, **results):\n",
    "    \"\"\"Plot comparison of compression results.\n",
    "    \n",
    "    :param title: Additional dataset name\n",
    "    :param key: Key function to customize the sort\n",
    "    :param **results: {compression-name: Result} mapping \n",
    "    \"\"\"\n",
    "    # Sort results by compression rate\n",
    "    results = dict(sorted(\n",
    "        results.items(),\n",
    "        key=(lambda item: item[1].compression_rate) if key is None else key,\n",
    "    ))\n",
    "    \n",
    "    names = list(results.keys())\n",
    "    write_speed = numpy.array([r.write_speed for r in results.values()])\n",
    "    read_speed = numpy.array([r.read_speed for r in results.values()])\n",
    "    c_rate = numpy.array([r.compression_rate for r in results.values()])\n",
    "    \n",
    "    #print(f\"hdf5plugin_config: {results[names[0]].config}\")\n",
    "\n",
    "    fig, axes = plt.subplots(1, 2)\n",
    "    speed_plt, c_rate_plt = axes\n",
    "\n",
    "    fig.suptitle(title)\n",
    "\n",
    "    ticks = numpy.arange(len(names))\n",
    "\n",
    "    # Speed\n",
    "    speed_plt.barh(y=ticks, width=write_speed, height=0.4, align='edge', label=\"Write\")\n",
    "    speed_plt.barh(y=ticks-.4, width=read_speed, height=0.4, align='edge', label=\"Read\")\n",
    "    speed_plt.set_xlabel(\"Speed [MB/s]\")\n",
    "    speed_plt.autoscale(axis='y')\n",
    "    speed_plt.yaxis.set_ticks(ticks)\n",
    "    speed_plt.yaxis.set_ticklabels(names, fontsize=8)\n",
    "    speed_plt.set_ylim(-0.5, len(ticks)-0.5)\n",
    "    speed_plt.grid(axis=\"x\")\n",
    "    speed_plt.legend()\n",
    "    \n",
    "    # Compression ratio\n",
    "    c_rate_plt.barh(y=ticks, width=c_rate)\n",
    "    c_rate_plt.set_xlabel(\"Compression rate\")\n",
    "    c_rate_plt.autoscale(axis='y')\n",
    "    c_rate_plt.yaxis.set_ticks(ticks)\n",
    "    c_rate_plt.yaxis.set_ticklabels([''] * len(ticks))\n",
    "    c_rate_plt.set_ylim(-0.5,len(ticks)-0.5)\n",
    "    c_rate_plt.axvline(1, color=\"red\")\n",
    "    c_rate_plt.grid(axis=\"x\")\n",
    "\n",
    "    fig.tight_layout()\n",
    "\n",
    "    # Tooltip\n",
    "    tooltip = c_rate_plt.annotate(\n",
    "        \"\", (0, 0), backgroundcolor=\"lightyellow\",\n",
    "        verticalalignment=\"top\", xycoords='figure pixels')\n",
    "    tooltip.set_visible(False)\n",
    "\n",
    "    def hover(event):\n",
    "        if event.inaxes in (speed_plt, c_rate_plt):\n",
    "            index = numpy.clip(round(event.ydata), 0, len(names)-1)\n",
    "            tooltip.set_text(\n",
    "                f\"{names[index]}\\n\"\n",
    "                f\"Comp. Rate: {c_rate[index]:.2f}x\\n\"\n",
    "                f\"Write: {write_speed[index]:.1f}MB/s\\n\"\n",
    "                f\"Read: {read_speed[index]:.1f}MB/s\")\n",
    "            tooltip.set_x(event.x + 15)\n",
    "            tooltip.set_y(event.y)\n",
    "            tooltip.set_visible(True)\n",
    "        else:\n",
    "            tooltip.set_visible(False)\n",
    "        fig.canvas.draw_idle()\n",
    "    \n",
    "    def resize(event):\n",
    "        fig.tight_layout()\n",
    "\n",
    "    fig.canvas.mpl_connect(\"motion_notify_event\", hover)\n",
    "    fig.canvas.mpl_connect(\"resize_event\", resize)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1eac474-0c3b-4792-bb45-8f13e3438d03",
   "metadata": {},
   "source": [
    "### Display information"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "c30c0f37-0c54-4e19-9f50-67ec7b543a8d",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Python: 3.9.12 | packaged by conda-forge | (main, Mar 24 2022, 23:22:55) \n",
      "[GCC 10.3.0]\n",
      "hdf5plugin:\n",
      "* Version: 4.2.0\n",
      "* Build config:\n",
      "  - openmp: True\n",
      "  - native: True\n",
      "  - bmi2: True\n",
      "  - sse2: True\n",
      "  - avx2: True\n",
      "  - avx512: False\n",
      "  - cpp11: True\n",
      "  - cpp14: True\n",
      "  - ipp: False\n",
      "  - filter_file_extension: .so\n",
      "  - embedded_filters: ('blosc', 'blosc2', 'bshuf', 'bzip2', 'fcidecomp', 'lz4', 'sz', 'sz3', 'zfp', 'zstd')\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import sys\n",
    "\n",
    "print('Python:', sys.version)\n",
    "\n",
    "config = hdf5plugin.get_config()\n",
    "print(f\"\"\"hdf5plugin:\n",
    "* Version: {hdf5plugin.version}\n",
    "* Build config:\n",
    "{'''\n",
    "'''.join(f'  - {k}: {v}' for k, v in config.build_config._asdict().items())}\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7a3b05f-7495-46a9-b24c-db24d45f4572",
   "metadata": {},
   "source": [
    "## Benchmark results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "aac83400-8189-464f-9ca3-d1c946a2728e",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "benchmarks = json.load(open(\"benchmark.json\"))\n",
    "config = benchmarks[\"config\"]\n",
    "results = {k: Result(**v) for k, v in benchmarks[\"results\"].items()}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "48364896-0cf2-4593-9301-bf486af69a66",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "c74bd80df1d24465a3ef4d3bc75c7602",
       "version_major": 2,
       "version_minor": 0
      },
      "image/png": "[encoded data, 86188 characters]",
      "text/html": [
       "\n",
       "            <div style=\"display: inline-block;\">\n",
       "                <div class=\"jupyter-widgets widget-label\" style=\"text-align: center;\">\n",
       "                    Figure\n",
       "                </div>\n",
       "                <img src='data:image/png;base64,[encoded data, 86188 characters]' width=640.0/>\n",
       "            </div>\n",
       "        "
      ],
      "text/plain": [
       "Canvas(toolbar=Toolbar(toolitems=[('Home', 'Reset original view', 'home', 'home'), ('Back', 'Back to previous …"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "display_results(\n",
    "    title=\"Filters comparison\",\n",
    "    **results\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "75864ef7-d9dc-4989-aad6-758ad872d6c1",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
