|
| 1 | +####################################################################### |
| 2 | +# Copyright (c) 2019-present, Blosc Development Team <[email protected]> |
| 3 | +# All rights reserved. |
| 4 | +# |
| 5 | +# This source code is licensed under a BSD-style license (found in the |
| 6 | +# LICENSE file in the root directory of this source tree) |
| 7 | +####################################################################### |
| 8 | + |
| 9 | +# Benchmark for comparing compute speeds of Blosc2 and Numexpr. |
| 10 | +# One can use different distributions of data: |
| 11 | +# ones, arange, linspace, or random |
| 12 | +# The expression can be any valid Numexpr expression. |
| 13 | + |
| 14 | +import blosc2 |
| 15 | +from time import time |
| 16 | +import numpy as np |
| 17 | +import numexpr as ne |
| 18 | + |
| 19 | +# Bench params |
| 20 | +N = 10_000 |
| 21 | +step = 1000 |
| 22 | +dtype = np.dtype(np.float64) |
| 23 | +persistent = False |
| 24 | +dist = "linspace" # "ones" or "linspace" or "arange" or "random" |
| 25 | +expr = "(a - b)" |
| 26 | +#expr = "sum(a - b)" |
| 27 | +#expr = "cos(a)**2 + sin(b)**2 - 1" |
| 28 | +#expr = "sum(cos(a)**2 + sin(b)**2 - 1)" |
| 29 | + |
| 30 | +# Set default compression params |
| 31 | +cparams = blosc2.CParams(clevel=1, codec=blosc2.Codec.BLOSCLZ) |
| 32 | +blosc2.cparams_dflts["codec"] = cparams.codec |
| 33 | +blosc2.cparams_dflts["clevel"] = cparams.clevel |
| 34 | +# Set default storage params |
| 35 | +storage = blosc2.Storage(contiguous=True, mode="w") |
| 36 | +blosc2.storage_dflts["contiguous"] = storage.contiguous |
| 37 | +blosc2.storage_dflts["mode"] = storage.mode |
| 38 | + |
| 39 | +urlpath = dict((aname, None) for aname in ("a", "b", "c")) |
| 40 | +if persistent: |
| 41 | + urlpath = dict((aname, f"{aname}.b2nd") for aname in ("a", "b", "c")) |
| 42 | + |
| 43 | +btimes = [] |
| 44 | +bspeeds = [] |
| 45 | +ws_sizes = [] |
| 46 | +rng = np.random.default_rng() |
| 47 | +for i in range(step, N + step, step): |
| 48 | + shape = (i, i) |
| 49 | + # shape = (i * i,) |
| 50 | + if dist == "ones": |
| 51 | + a = blosc2.ones(shape, dtype=dtype, urlpath=urlpath['a']) |
| 52 | + b = blosc2.ones(shape, dtype=dtype, urlpath=urlpath['b']) |
| 53 | + elif dist == "arange": |
| 54 | + a = blosc2.arange(0, i**2, dtype=dtype, shape=shape, urlpath=urlpath['a']) |
| 55 | + b = blosc2.arange(0, i**2, dtype=dtype, shape=shape, urlpath=urlpath['b']) |
| 56 | + elif dist == "linspace": |
| 57 | + a = blosc2.linspace(0, 1, dtype=dtype, shape=shape, urlpath=urlpath['a']) |
| 58 | + b = blosc2.linspace(0, 1, dtype=dtype, shape=shape, urlpath=urlpath['b']) |
| 59 | + elif dist == "random": |
| 60 | + t0 = time() |
| 61 | + _ = np.random.random(shape) |
| 62 | + a = blosc2.fromiter(np.nditer(_), dtype=dtype, shape=shape, urlpath=urlpath['a']) |
| 63 | + b = blosc2.fromiter(np.nditer(_), dtype=dtype, shape=shape, urlpath=urlpath['b']) |
| 64 | + # This uses less memory, but it is 2x-3x slower |
| 65 | + # iter_ = (rng.random() for _ in range(i**2 * 2)) |
| 66 | + # a = blosc2.fromiter(iter_, dtype=dtype, shape=shape, urlpath=urlpath['a']) |
| 67 | + # b = blosc2.fromiter(iter_, dtype=dtype, shape=shape, urlpath=urlpath['b']) |
| 68 | + t = time() - t0 |
| 69 | + #print(f"Time to create data: {t:.5f} s - {a.schunk.nbytes/t / 1e9:.2f} GB/s") |
| 70 | + else: |
| 71 | + raise ValueError("Invalid distribution type") |
| 72 | + |
| 73 | + t0 = time() |
| 74 | + c = blosc2.lazyexpr(expr).compute(urlpath=urlpath['c']) |
| 75 | + t = time() - t0 |
| 76 | + ws_sizes.append((a.schunk.nbytes + b.schunk.nbytes) / 2**30) |
| 77 | + speed = ws_sizes[-1] / t |
| 78 | + print(f"Time to compute a - b: {t:.5f} s -- {speed:.2f} GB/s -- cratio: {c.schunk.cratio:.1f}x") |
| 79 | + #print(f"result: {c[()]}") |
| 80 | + btimes.append(t) |
| 81 | + bspeeds.append(speed) |
| 82 | + |
| 83 | +# Evaluate using Numexpr compute engine |
| 84 | +ntimes = [] |
| 85 | +nspeeds = [] |
| 86 | +for i in range(step, N + step, step): |
| 87 | + shape = (i, i) |
| 88 | + # shape = (i * i,) |
| 89 | + if dist == "ones": |
| 90 | + a = np.ones(shape, dtype=dtype) |
| 91 | + b = np.ones(shape, dtype=dtype) |
| 92 | + elif dist == "arange": |
| 93 | + a = np.arange(0, i**2, dtype=dtype).reshape(shape) |
| 94 | + b = np.arange(0, i**2, dtype=dtype).reshape(shape) |
| 95 | + elif dist == "linspace": |
| 96 | + a = np.linspace(0, 1, num=i**2, dtype=dtype).reshape(shape) |
| 97 | + b = np.linspace(0, 1, num=i**2, dtype=dtype).reshape(shape) |
| 98 | + elif dist == "random": |
| 99 | + a = np.random.random(shape) |
| 100 | + b = np.random.random(shape) |
| 101 | + else: |
| 102 | + raise ValueError("Invalid distribution type") |
| 103 | + |
| 104 | + t0 = time() |
| 105 | + c = ne.evaluate(expr) |
| 106 | + t = time() - t0 |
| 107 | + ws_size = (a.nbytes + b.nbytes) / 2**30 |
| 108 | + speed = ws_size / t |
| 109 | + print(f"Time to compute with Numexpr: {t:.5f} s - {speed:.2f} GB/s") |
| 110 | + #print(f"result: {c}") |
| 111 | + ntimes.append(t) |
| 112 | + nspeeds.append(speed) |
| 113 | + |
| 114 | +# Plot |
| 115 | +import matplotlib.pyplot as plt |
| 116 | +import matplotlib.ticker as ticker |
| 117 | +import seaborn as sns |
| 118 | + |
| 119 | +sns.set_theme(style="whitegrid") |
| 120 | +plt.figure(figsize=(10, 6)) |
| 121 | +plt.plot(ws_sizes, bspeeds, label="Blosc2", marker='o') |
| 122 | +plt.plot(ws_sizes, nspeeds, label="Numexpr", marker='o') |
| 123 | +# Set y-axis to start from 0 |
| 124 | +plt.ylim(bottom=0) |
| 125 | +plt.xlabel("Working set (GB)") |
| 126 | +#plt.ylabel("Time (s)") |
| 127 | +plt.ylabel("Speed (GB/s)") |
| 128 | +plt.title(f"Blosc2 vs Numexpr performance -- {dist} distribution") |
| 129 | +plt.legend() |
| 130 | +#plt.gca().xaxis.set_major_locator(ticker.MaxNLocator(integer=True)) |
| 131 | +#plt.gca().yaxis.set_major_formatter(ticker.FuncFormatter(lambda x, _: f'{x:.2f}')) |
| 132 | +plt.grid() |
| 133 | +plt.show() |
| 134 | +# Save the figure |
| 135 | +plt.savefig("blosc2_vs_numexpr.png", dpi=300, bbox_inches='tight') |
| 136 | +plt.close() |
0 commit comments