From e1f4545e1dd70dead73f25949dd936fcc67f0584 Mon Sep 17 00:00:00 2001 From: vchamarthi Date: Mon, 13 Apr 2026 20:55:34 -0500 Subject: [PATCH 1/4] asv benchmarking configs --- .gitignore | 4 + benchmarks/asv.conf.json | 15 ++ benchmarks/benchmarks/__init__.py | 48 ++++ benchmarks/benchmarks/bench_fft1d.py | 132 ++++++++++ benchmarks/benchmarks/bench_fftnd.py | 195 +++++++++++++++ benchmarks/benchmarks/bench_memory.py | 120 +++++++++ benchmarks/benchmarks/bench_numpy_fft.py | 231 +++++++++++++++++ benchmarks/benchmarks/bench_scipy_fft.py | 304 +++++++++++++++++++++++ 8 files changed, 1049 insertions(+) create mode 100644 benchmarks/asv.conf.json create mode 100644 benchmarks/benchmarks/__init__.py create mode 100644 benchmarks/benchmarks/bench_fft1d.py create mode 100644 benchmarks/benchmarks/bench_fftnd.py create mode 100644 benchmarks/benchmarks/bench_memory.py create mode 100644 benchmarks/benchmarks/bench_numpy_fft.py create mode 100644 benchmarks/benchmarks/bench_scipy_fft.py diff --git a/.gitignore b/.gitignore index b7738655..20ecca31 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ mkl_fft/_pydfti.c mkl_fft/_pydfti.cpython*.so mkl_fft/_pydfti.*-win_amd64.pyd mkl_fft/src/mklfft.c + +# ASV benchmark artifacts +.asv/ +benchmarks/.asv/ diff --git a/benchmarks/asv.conf.json b/benchmarks/asv.conf.json new file mode 100644 index 00000000..29ebbfe1 --- /dev/null +++ b/benchmarks/asv.conf.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "project": "mkl_fft", + "project_url": "https://github.com/IntelPython/mkl_fft", + "show_commit_url": "https://github.com/IntelPython/mkl_fft/commit/", + "repo": "..", + "branches": ["master"], + "benchmark_dir": "benchmarks", + "env_dir": ".asv/env", + "results_dir": ".asv/results", + "html_dir": ".asv/html", + "build_cache_size": 2, + "default_benchmark_timeout": 500, + "regressions_thresholds": {".*": 0.3} +} diff --git a/benchmarks/benchmarks/__init__.py b/benchmarks/benchmarks/__init__.py new file mode 100644 index 00000000..74dd0614 --- /dev/null +++ b/benchmarks/benchmarks/__init__.py @@ -0,0 +1,48 @@ +"""ASV benchmarks for mkl_fft. + +Thread control — design rationale +---------------------------------- +Since we do not have a dedicated CI benchmark machine, benchmarks run on a shared CI pool +whose machines vary in core count over time. +Using the full physical core count of each machine would make results +incomparable across runs on different machines. + +Strategy: + - Physical cores >= 4 → fix MKL_NUM_THREADS = 4 + 4 is the lowest common denominator that guarantees multi-threaded MKL + behavior and is achievable on any modern CI machine. Results from + different machines in the pool are therefore directly comparable. + - Physical cores < 4 → fall back to MKL_NUM_THREADS = 1 (single-threaded) + Prevents over-subscription on under-resourced machines and avoids + misleading comparisons against 4-thread baselines. + +MKL recommendation: use physical cores, not logical (hyperthreaded) CPUs. +""" + +import os +import re + +_MIN_THREADS = 4 # minimum physical cores required for multi-threaded mode + + +def _physical_cores(): + """Return physical core count from /proc/cpuinfo; fall back to os.cpu_count().""" + try: + with open("/proc/cpuinfo") as f: + content = f.read() + cpu_cores = int(re.search(r"cpu cores\s*:\s*(\d+)", content).group(1)) + sockets = max(len(set(re.findall(r"physical id\s*:\s*(\d+)", content))), 1) + return cpu_cores * sockets + except Exception: + return os.cpu_count() or 1 + + +def _thread_count(): + physical = _physical_cores() + return str(_MIN_THREADS) if physical >= _MIN_THREADS else "1" + + +_THREADS = os.environ.get("MKL_NUM_THREADS", _thread_count()) +os.environ["MKL_NUM_THREADS"] = _THREADS +os.environ["OMP_NUM_THREADS"] = _THREADS +os.environ["OPENBLAS_NUM_THREADS"] = _THREADS diff --git a/benchmarks/benchmarks/bench_fft1d.py b/benchmarks/benchmarks/bench_fft1d.py new file mode 100644 index 00000000..ac8fa358 --- /dev/null +++ b/benchmarks/benchmarks/bench_fft1d.py @@ -0,0 +1,132 @@ +"""Benchmarks for 1-D FFT operations using the mkl_fft root API.""" + +import numpy as np + + +def _make_input(rng, n, dtype): + """Return a 1-D array of length *n* with the given *dtype*. + + Complex dtypes are populated with non-zero imaginary parts so the + benchmark exercises a genuine complex transform path. + """ + dt = np.dtype(dtype) + if dt.kind == "c": + return (rng.randn(n) + 1j * rng.randn(n)).astype(dt) + return rng.randn(n).astype(dt) + + +# --------------------------------------------------------------------------- +# Complex-to-complex 1-D (power-of-two sizes) +# --------------------------------------------------------------------------- + +class TimeFFT1D: + """Forward and inverse complex FFT — power-of-two sizes.""" + + params = [ + [64, 256, 1024, 4096, 16384, 65536], + ["float32", "float64", "complex64", "complex128"], + ] + param_names = ["n", "dtype"] + + def setup(self, n, dtype): + rng = np.random.RandomState(42) + self.x = _make_input(rng, n, dtype) + + def time_fft(self, n, dtype): + import mkl_fft + mkl_fft.fft(self.x) + + def time_ifft(self, n, dtype): + import mkl_fft + mkl_fft.ifft(self.x) + + +# --------------------------------------------------------------------------- +# Real-to-complex / complex-to-real 1-D (power-of-two sizes) +# --------------------------------------------------------------------------- + +class TimeRFFT1D: + """Forward rfft and inverse irfft — power-of-two sizes.""" + + params = [ + [64, 256, 1024, 4096, 16384, 65536], + ["float32", "float64"], + ] + param_names = ["n", "dtype"] + + def setup(self, n, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + self.x_real = rng.randn(n).astype(dtype) + # irfft input: complex half-spectrum of length n//2+1 + self.x_complex = ( + rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + ).astype(cdtype) + + def time_rfft(self, n, dtype): + import mkl_fft + mkl_fft.rfft(self.x_real) + + def time_irfft(self, n, dtype): + import mkl_fft + mkl_fft.irfft(self.x_complex, n=n) + + +# --------------------------------------------------------------------------- +# Complex-to-complex 1-D (non-power-of-two sizes) +# --------------------------------------------------------------------------- + +class TimeFFT1DNonPow2: + """Forward and inverse complex FFT — non-power-of-two sizes. + + MKL uses a different code path for non-power-of-two transforms; + this suite catches regressions in that path. + """ + + params = [ + [127, 509, 1000, 4001, 10007], + ["float64", "complex128", "complex64"], + ] + param_names = ["n", "dtype"] + + def setup(self, n, dtype): + rng = np.random.RandomState(42) + self.x = _make_input(rng, n, dtype) + + def time_fft(self, n, dtype): + import mkl_fft + mkl_fft.fft(self.x) + + def time_ifft(self, n, dtype): + import mkl_fft + mkl_fft.ifft(self.x) + + +# --------------------------------------------------------------------------- +# Real-to-complex / complex-to-real 1-D (non-power-of-two sizes) +# --------------------------------------------------------------------------- + +class TimeRFFT1DNonPow2: + """Forward rfft and inverse irfft — non-power-of-two sizes.""" + + params = [ + [127, 509, 1000, 4001, 10007], + ["float32", "float64"], + ] + param_names = ["n", "dtype"] + + def setup(self, n, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + self.x_real = rng.randn(n).astype(dtype) + self.x_complex = ( + rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + ).astype(cdtype) + + def time_rfft(self, n, dtype): + import mkl_fft + mkl_fft.rfft(self.x_real) + + def time_irfft(self, n, dtype): + import mkl_fft + mkl_fft.irfft(self.x_complex, n=n) diff --git a/benchmarks/benchmarks/bench_fftnd.py b/benchmarks/benchmarks/bench_fftnd.py new file mode 100644 index 00000000..89468da4 --- /dev/null +++ b/benchmarks/benchmarks/bench_fftnd.py @@ -0,0 +1,195 @@ +"""Benchmarks for 2-D and N-D FFT operations using the mkl_fft root API.""" + +import numpy as np + + +def _make_input(rng, shape, dtype): + """Return an array of the given *shape* and *dtype*. + + Complex dtypes are populated with non-zero imaginary parts so the + benchmark exercises a genuine complex transform path. + """ + dt = np.dtype(dtype) + if dt.kind == "c": + return (rng.randn(*shape) + 1j * rng.randn(*shape)).astype(dt) + return rng.randn(*shape).astype(dt) + + +# --------------------------------------------------------------------------- +# 2-D complex-to-complex (power-of-two, square + non-square) +# --------------------------------------------------------------------------- + +class TimeFFT2D: + """Forward and inverse 2-D FFT — square and non-square shapes.""" + + params = [ + [ + (64, 64), (128, 128), (256, 256), (512, 512), + (256, 128), (512, 256), # non-square + ], + ["float32", "float64", "complex64", "complex128"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + rng = np.random.RandomState(42) + self.x = _make_input(rng, shape, dtype) + + def time_fft2(self, shape, dtype): + import mkl_fft + mkl_fft.fft2(self.x) + + def time_ifft2(self, shape, dtype): + import mkl_fft + mkl_fft.ifft2(self.x) + + +# --------------------------------------------------------------------------- +# 2-D real-to-complex / complex-to-real +# --------------------------------------------------------------------------- + +class TimeRFFT2D: + """Forward rfft2 and inverse irfft2.""" + + params = [ + [(64, 64), (128, 128), (256, 256), (512, 512)], + ["float32", "float64"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + half_shape = (shape[0], shape[1] // 2 + 1) + self.x_real = rng.randn(*shape).astype(dtype) + # irfft2 input: complex half-spectrum — shape (M, N//2+1) + self.x_complex = ( + rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + ).astype(cdtype) + + def time_rfft2(self, shape, dtype): + import mkl_fft + mkl_fft.rfft2(self.x_real) + + def time_irfft2(self, shape, dtype): + import mkl_fft + mkl_fft.irfft2(self.x_complex, s=shape) + + +# --------------------------------------------------------------------------- +# 2-D complex-to-complex (non-power-of-two) +# --------------------------------------------------------------------------- + +class TimeFFT2DNonPow2: + """Forward and inverse 2-D FFT — non-power-of-two sizes.""" + + params = [ + [ + (96, 96), (100, 100), (270, 270), (500, 500), + (100, 200), # non-square non-pow2 + ], + ["float64", "complex128"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + rng = np.random.RandomState(42) + self.x = _make_input(rng, shape, dtype) + + def time_fft2(self, shape, dtype): + import mkl_fft + mkl_fft.fft2(self.x) + + def time_ifft2(self, shape, dtype): + import mkl_fft + mkl_fft.ifft2(self.x) + + +# --------------------------------------------------------------------------- +# N-D complex-to-complex (3-D cubes + non-cubic shape) +# --------------------------------------------------------------------------- + +class TimeFFTnD: + """Forward and inverse N-D FFT.""" + + params = [ + [ + (16, 16, 16), (32, 32, 32), (64, 64, 64), + (32, 64, 128), # non-cubic + ], + ["float32", "float64", "complex64", "complex128"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + rng = np.random.RandomState(42) + self.x = _make_input(rng, shape, dtype) + + def time_fftn(self, shape, dtype): + import mkl_fft + mkl_fft.fftn(self.x) + + def time_ifftn(self, shape, dtype): + import mkl_fft + mkl_fft.ifftn(self.x) + + +# --------------------------------------------------------------------------- +# N-D real-to-complex / complex-to-real +# --------------------------------------------------------------------------- + +class TimeRFFTnD: + """Forward rfftn and inverse irfftn.""" + + params = [ + [(16, 16, 16), (32, 32, 32), (64, 64, 64)], + ["float32", "float64"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + # irfftn input: complex half-spectrum — last axis is shape[-1]//2+1 + half_shape = shape[:-1] + (shape[-1] // 2 + 1,) + self.x_real = rng.randn(*shape).astype(dtype) + self.x_complex = ( + rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + ).astype(cdtype) + + def time_rfftn(self, shape, dtype): + import mkl_fft + mkl_fft.rfftn(self.x_real) + + def time_irfftn(self, shape, dtype): + import mkl_fft + mkl_fft.irfftn(self.x_complex, s=shape) + + +# --------------------------------------------------------------------------- +# N-D complex-to-complex (non-power-of-two 3-D) +# --------------------------------------------------------------------------- + +class TimeFFTnDNonPow2: + """Forward and inverse N-D FFT — non-power-of-two sizes.""" + + params = [ + [ + (24, 24, 24), (30, 30, 30), (50, 50, 50), + (30, 40, 50), # non-cubic non-pow2 + ], + ["float64", "complex128"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + rng = np.random.RandomState(42) + self.x = _make_input(rng, shape, dtype) + + def time_fftn(self, shape, dtype): + import mkl_fft + mkl_fft.fftn(self.x) + + def time_ifftn(self, shape, dtype): + import mkl_fft + mkl_fft.ifftn(self.x) diff --git a/benchmarks/benchmarks/bench_memory.py b/benchmarks/benchmarks/bench_memory.py new file mode 100644 index 00000000..c57d7692 --- /dev/null +++ b/benchmarks/benchmarks/bench_memory.py @@ -0,0 +1,120 @@ +"""Peak-memory benchmarks for FFT operations. + +Measures peak RSS (resident set size) to detect memory regressions +in the mkl_fft root API across 1-D, 2-D, and 3-D transforms. +""" + +import numpy as np + + +def _make_input(rng, shape, dtype): + dt = np.dtype(dtype) + s = (shape,) if isinstance(shape, int) else shape + if dt.kind == "c": + return (rng.randn(*s) + 1j * rng.randn(*s)).astype(dt) + return rng.randn(*s).astype(dt) + + +# --------------------------------------------------------------------------- +# 1-D complex FFT +# --------------------------------------------------------------------------- + +class PeakMemFFT1D: + """Peak RSS for 1-D complex FFT.""" + + params = [ + [1024, 16384, 65536, 262144], + ["float64", "complex128"], + ] + param_names = ["n", "dtype"] + + def setup(self, n, dtype): + self.x = _make_input(np.random.RandomState(42), n, dtype) + + def peakmem_fft(self, n, dtype): + import mkl_fft + mkl_fft.fft(self.x) + + def peakmem_ifft(self, n, dtype): + import mkl_fft + mkl_fft.ifft(self.x) + + +# --------------------------------------------------------------------------- +# 1-D real FFT +# --------------------------------------------------------------------------- + +class PeakMemRFFT1D: + """Peak RSS for 1-D real FFT (forward and inverse).""" + + params = [ + [1024, 16384, 65536, 262144], + ["float32", "float64"], + ] + param_names = ["n", "dtype"] + + def setup(self, n, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + self.x_real = rng.randn(n).astype(dtype) + self.x_complex = ( + rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + ).astype(cdtype) + + def peakmem_rfft(self, n, dtype): + import mkl_fft + mkl_fft.rfft(self.x_real) + + def peakmem_irfft(self, n, dtype): + import mkl_fft + mkl_fft.irfft(self.x_complex, n=n) + + +# --------------------------------------------------------------------------- +# 2-D complex FFT +# --------------------------------------------------------------------------- + +class PeakMemFFT2D: + """Peak RSS for 2-D complex FFT.""" + + params = [ + [(64, 64), (128, 128), (256, 256), (512, 512)], + ["float64", "complex128"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + self.x = _make_input(np.random.RandomState(42), shape, dtype) + + def peakmem_fft2(self, shape, dtype): + import mkl_fft + mkl_fft.fft2(self.x) + + def peakmem_ifft2(self, shape, dtype): + import mkl_fft + mkl_fft.ifft2(self.x) + + +# --------------------------------------------------------------------------- +# N-D complex FFT (3-D) +# --------------------------------------------------------------------------- + +class PeakMemFFTnD: + """Peak RSS for N-D complex FFT (3-D shapes).""" + + params = [ + [(16, 16, 16), (32, 32, 32), (64, 64, 64)], + ["float64", "complex128"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + self.x = _make_input(np.random.RandomState(42), shape, dtype) + + def peakmem_fftn(self, shape, dtype): + import mkl_fft + mkl_fft.fftn(self.x) + + def peakmem_ifftn(self, shape, dtype): + import mkl_fft + mkl_fft.ifftn(self.x) diff --git a/benchmarks/benchmarks/bench_numpy_fft.py b/benchmarks/benchmarks/bench_numpy_fft.py new file mode 100644 index 00000000..cd75d271 --- /dev/null +++ b/benchmarks/benchmarks/bench_numpy_fft.py @@ -0,0 +1,231 @@ +"""Benchmarks for mkl_fft.interfaces.numpy_fft. + +Covers every function exported by the interface: + fft / ifft — 1-D C2C + rfft / irfft — 1-D R2C / C2R + hfft / ihfft — 1-D Hermitian + fft2 / ifft2 — 2-D C2C + rfft2 / irfft2 — 2-D R2C / C2R + fftn / ifftn — N-D C2C + rfftn / irfftn — N-D R2C / C2R +""" + +import numpy as np + + +def _make_input(rng, shape, dtype): + """Return an array of *shape* and *dtype*. + + Complex dtypes get non-zero imaginary parts for a realistic signal. + *shape* may be an int (1-D) or a tuple. + """ + dt = np.dtype(dtype) + s = (shape,) if isinstance(shape, int) else shape + if dt.kind == "c": + return (rng.randn(*s) + 1j * rng.randn(*s)).astype(dt) + return rng.randn(*s).astype(dt) + + +# --------------------------------------------------------------------------- +# 1-D complex-to-complex +# --------------------------------------------------------------------------- + +class TimeC2C1D: + """numpy_fft.fft / ifft — 1-D.""" + + params = [ + [256, 1024, 16384], + ["float32", "float64", "complex64", "complex128"], + ] + param_names = ["n", "dtype"] + + def setup(self, n, dtype): + self.x = _make_input(np.random.RandomState(42), n, dtype) + + def time_fft(self, n, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.fft(self.x) + + def time_ifft(self, n, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.ifft(self.x) + + +# --------------------------------------------------------------------------- +# 1-D real-to-complex / complex-to-real +# --------------------------------------------------------------------------- + +class TimeRC1D: + """numpy_fft.rfft / irfft — 1-D.""" + + params = [ + [256, 1024, 16384], + ["float32", "float64"], + ] + param_names = ["n", "dtype"] + + def setup(self, n, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + self.x_real = rng.randn(n).astype(dtype) + self.x_complex = ( + rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + ).astype(cdtype) + + def time_rfft(self, n, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.rfft(self.x_real) + + def time_irfft(self, n, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.irfft(self.x_complex, n=n) + + +# --------------------------------------------------------------------------- +# 1-D Hermitian +# hfft: input complex length n//2+1 → output real length n +# ihfft: input real length n → output complex length n//2+1 +# --------------------------------------------------------------------------- + +class TimeHermitian1D: + """numpy_fft.hfft / ihfft — 1-D Hermitian. + + *dtype* is the **output** dtype of hfft (real); the inverse ihfft + takes the same real input and produces the corresponding complex output. + """ + + params = [ + [256, 1024, 16384], + ["float32", "float64"], + ] + param_names = ["n", "dtype"] + + def setup(self, n, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + # hfft input: complex half-spectrum of length n//2+1 + self.x_hfft = ( + rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + ).astype(cdtype) + # ihfft input: real signal of length n + self.x_ihfft = rng.randn(n).astype(dtype) + + def time_hfft(self, n, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.hfft(self.x_hfft, n=n) + + def time_ihfft(self, n, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.ihfft(self.x_ihfft) + + +# --------------------------------------------------------------------------- +# 2-D complex-to-complex +# --------------------------------------------------------------------------- + +class TimeC2C2D: + """numpy_fft.fft2 / ifft2 — 2-D.""" + + params = [ + [(64, 64), (256, 256), (512, 512)], + ["float64", "complex128"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + self.x = _make_input(np.random.RandomState(42), shape, dtype) + + def time_fft2(self, shape, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.fft2(self.x) + + def time_ifft2(self, shape, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.ifft2(self.x) + + +# --------------------------------------------------------------------------- +# 2-D real-to-complex / complex-to-real +# --------------------------------------------------------------------------- + +class TimeRC2D: + """numpy_fft.rfft2 / irfft2 — 2-D.""" + + params = [ + [(64, 64), (256, 256), (512, 512)], + ["float32", "float64"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + half_shape = (shape[0], shape[1] // 2 + 1) + self.x_real = rng.randn(*shape).astype(dtype) + self.x_complex = ( + rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + ).astype(cdtype) + + def time_rfft2(self, shape, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.rfft2(self.x_real) + + def time_irfft2(self, shape, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.irfft2(self.x_complex, s=shape) + + +# --------------------------------------------------------------------------- +# N-D complex-to-complex +# --------------------------------------------------------------------------- + +class TimeCCND: + """numpy_fft.fftn / ifftn — N-D.""" + + params = [ + [(16, 16, 16), (32, 32, 32), (64, 64, 64)], + ["float64", "complex128"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + self.x = _make_input(np.random.RandomState(42), shape, dtype) + + def time_fftn(self, shape, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.fftn(self.x) + + def time_ifftn(self, shape, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.ifftn(self.x) + + +# --------------------------------------------------------------------------- +# N-D real-to-complex / complex-to-real +# --------------------------------------------------------------------------- + +class TimeRCND: + """numpy_fft.rfftn / irfftn — N-D.""" + + params = [ + [(16, 16, 16), (32, 32, 32), (64, 64, 64)], + ["float32", "float64"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + half_shape = shape[:-1] + (shape[-1] // 2 + 1,) + self.x_real = rng.randn(*shape).astype(dtype) + self.x_complex = ( + rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + ).astype(cdtype) + + def time_rfftn(self, shape, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.rfftn(self.x_real) + + def time_irfftn(self, shape, dtype): + from mkl_fft.interfaces import numpy_fft + numpy_fft.irfftn(self.x_complex, s=shape) diff --git a/benchmarks/benchmarks/bench_scipy_fft.py b/benchmarks/benchmarks/bench_scipy_fft.py new file mode 100644 index 00000000..6b955ec6 --- /dev/null +++ b/benchmarks/benchmarks/bench_scipy_fft.py @@ -0,0 +1,304 @@ +"""Benchmarks for mkl_fft.interfaces.scipy_fft. + +Covers every function exported by the interface: + fft / ifft — 1-D C2C + rfft / irfft — 1-D R2C / C2R + hfft / ihfft — 1-D Hermitian + fft2 / ifft2 — 2-D C2C + rfft2 / irfft2 — 2-D R2C / C2R + hfft2 / ihfft2 — 2-D Hermitian (scipy_fft only) + fftn / ifftn — N-D C2C + rfftn / irfftn — N-D R2C / C2R + hfftn / ihfftn — N-D Hermitian (scipy_fft only) +""" + +import numpy as np + + +def _make_input(rng, shape, dtype): + """Return an array of *shape* and *dtype*. + + Complex dtypes get non-zero imaginary parts for a realistic signal. + *shape* may be an int (1-D) or a tuple. + """ + dt = np.dtype(dtype) + s = (shape,) if isinstance(shape, int) else shape + if dt.kind == "c": + return (rng.randn(*s) + 1j * rng.randn(*s)).astype(dt) + return rng.randn(*s).astype(dt) + + +# --------------------------------------------------------------------------- +# 1-D complex-to-complex +# --------------------------------------------------------------------------- + +class TimeC2C1D: + """scipy_fft.fft / ifft — 1-D.""" + + params = [ + [256, 1024, 16384], + ["float32", "float64", "complex64", "complex128"], + ] + param_names = ["n", "dtype"] + + def setup(self, n, dtype): + self.x = _make_input(np.random.RandomState(42), n, dtype) + + def time_fft(self, n, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.fft(self.x) + + def time_ifft(self, n, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.ifft(self.x) + + +# --------------------------------------------------------------------------- +# 1-D real-to-complex / complex-to-real +# --------------------------------------------------------------------------- + +class TimeRC1D: + """scipy_fft.rfft / irfft — 1-D.""" + + params = [ + [256, 1024, 16384], + ["float32", "float64"], + ] + param_names = ["n", "dtype"] + + def setup(self, n, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + self.x_real = rng.randn(n).astype(dtype) + self.x_complex = ( + rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + ).astype(cdtype) + + def time_rfft(self, n, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.rfft(self.x_real) + + def time_irfft(self, n, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.irfft(self.x_complex, n=n) + + +# --------------------------------------------------------------------------- +# 1-D Hermitian +# hfft: input complex length n//2+1 → output real length n +# ihfft: input real length n → output complex length n//2+1 +# --------------------------------------------------------------------------- + +class TimeHermitian1D: + """scipy_fft.hfft / ihfft — 1-D Hermitian. + + *dtype* is the **output** dtype of hfft (real); the corresponding + complex input dtype is derived automatically. + """ + + params = [ + [256, 1024, 16384], + ["float32", "float64"], + ] + param_names = ["n", "dtype"] + + def setup(self, n, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + self.x_hfft = ( + rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + ).astype(cdtype) + self.x_ihfft = rng.randn(n).astype(dtype) + + def time_hfft(self, n, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.hfft(self.x_hfft, n=n) + + def time_ihfft(self, n, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.ihfft(self.x_ihfft) + + +# --------------------------------------------------------------------------- +# 2-D complex-to-complex +# --------------------------------------------------------------------------- + +class TimeC2C2D: + """scipy_fft.fft2 / ifft2 — 2-D.""" + + params = [ + [(64, 64), (256, 256), (512, 512)], + ["float64", "complex128"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + self.x = _make_input(np.random.RandomState(42), shape, dtype) + + def time_fft2(self, shape, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.fft2(self.x) + + def time_ifft2(self, shape, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.ifft2(self.x) + + +# --------------------------------------------------------------------------- +# 2-D real-to-complex / complex-to-real +# --------------------------------------------------------------------------- + +class TimeRC2D: + """scipy_fft.rfft2 / irfft2 — 2-D.""" + + params = [ + [(64, 64), (256, 256), (512, 512)], + ["float32", "float64"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + half_shape = (shape[0], shape[1] // 2 + 1) + self.x_real = rng.randn(*shape).astype(dtype) + self.x_complex = ( + rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + ).astype(cdtype) + + def time_rfft2(self, shape, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.rfft2(self.x_real) + + def time_irfft2(self, shape, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.irfft2(self.x_complex, s=shape) + + +# --------------------------------------------------------------------------- +# 2-D Hermitian (scipy_fft only — not in numpy_fft interface) +# hfft2: input complex shape (M, N//2+1) → output real shape (M, N) +# ihfft2: input real shape (M, N) → output complex shape (M, N//2+1) +# --------------------------------------------------------------------------- + +class TimeHermitian2D: + """scipy_fft.hfft2 / ihfft2 — 2-D Hermitian. + + *dtype* is the **output** dtype of hfft2 (real). + """ + + params = [ + [(64, 64), (256, 256), (512, 512)], + ["float32", "float64"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + half_shape = (shape[0], shape[1] // 2 + 1) + self.x_hfft2 = ( + rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + ).astype(cdtype) + self.x_ihfft2 = rng.randn(*shape).astype(dtype) + + def time_hfft2(self, shape, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.hfft2(self.x_hfft2, s=shape) + + def time_ihfft2(self, shape, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.ihfft2(self.x_ihfft2) + + +# --------------------------------------------------------------------------- +# N-D complex-to-complex +# --------------------------------------------------------------------------- + +class TimeCCND: + """scipy_fft.fftn / ifftn — N-D.""" + + params = [ + [(16, 16, 16), (32, 32, 32), (64, 64, 64)], + ["float64", "complex128"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + self.x = _make_input(np.random.RandomState(42), shape, dtype) + + def time_fftn(self, shape, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.fftn(self.x) + + def time_ifftn(self, shape, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.ifftn(self.x) + + +# --------------------------------------------------------------------------- +# N-D real-to-complex / complex-to-real +# --------------------------------------------------------------------------- + +class TimeRCND: + """scipy_fft.rfftn / irfftn — N-D.""" + + params = [ + [(16, 16, 16), (32, 32, 32), (64, 64, 64)], + ["float32", "float64"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + half_shape = shape[:-1] + (shape[-1] // 2 + 1,) + self.x_real = rng.randn(*shape).astype(dtype) + self.x_complex = ( + rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + ).astype(cdtype) + + def time_rfftn(self, shape, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.rfftn(self.x_real) + + def time_irfftn(self, shape, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.irfftn(self.x_complex, s=shape) + + +# --------------------------------------------------------------------------- +# N-D Hermitian (scipy_fft only) +# hfftn: input complex, last axis length s[-1]//2+1 → output real shape s +# ihfftn: input real shape s → output complex, last axis length s[-1]//2+1 +# --------------------------------------------------------------------------- + +class TimeHermitianND: + """scipy_fft.hfftn / ihfftn — N-D Hermitian. + + *dtype* is the **output** dtype of hfftn (real). + """ + + params = [ + [(16, 16, 16), (32, 32, 32), (64, 64, 64)], + ["float32", "float64"], + ] + param_names = ["shape", "dtype"] + + def setup(self, shape, dtype): + rng = np.random.RandomState(42) + cdtype = "complex64" if dtype == "float32" else "complex128" + # hfftn input: last axis has length shape[-1]//2+1 + half_shape = shape[:-1] + (shape[-1] // 2 + 1,) + self.x_hfftn = ( + rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + ).astype(cdtype) + self.x_ihfftn = rng.randn(*shape).astype(dtype) + + def time_hfftn(self, shape, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.hfftn(self.x_hfftn, s=shape) + + def time_ihfftn(self, shape, dtype): + from mkl_fft.interfaces import scipy_fft + scipy_fft.ihfftn(self.x_ihfftn) From fd8f6fdda3a0dd5d2661e5dd2fcc8faf77c947f3 Mon Sep 17 00:00:00 2001 From: vchamarthi Date: Mon, 13 Apr 2026 21:06:05 -0500 Subject: [PATCH 2/4] add readme --- benchmarks/README.md | 79 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 benchmarks/README.md diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 00000000..9720a713 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,79 @@ +# mkl_fft ASV Benchmarks + +Performance benchmarks for [mkl_fft](https://github.com/IntelPython/mkl_fft) using +[Airspeed Velocity (ASV)](https://asv.readthedocs.io/en/stable/). + +## Structure + +``` +benchmarks/ +├── asv.conf.json # ASV configuration (CI-only, no env/build settings) +└── benchmarks/ + ├── __init__.py # Thread pinning (MKL_NUM_THREADS) + ├── bench_fft1d.py # mkl_fft root API — 1-D transforms + ├── bench_fftnd.py # mkl_fft root API — 2-D and N-D transforms + ├── bench_numpy_fft.py # mkl_fft.interfaces.numpy_fft — full coverage + ├── bench_scipy_fft.py # mkl_fft.interfaces.scipy_fft — full coverage + └── bench_memory.py # Peak RSS memory benchmarks +``` + +### Coverage + +| File | API | Transforms | +|------|-----|-----------| +| `bench_fft1d.py` | `mkl_fft` | `fft`, `ifft`, `rfft`, `irfft` — power-of-two and non-power-of-two | +| `bench_fftnd.py` | `mkl_fft` | `fft2`, `ifft2`, `rfft2`, `irfft2`, `fftn`, `ifftn`, `rfftn`, `irfftn` | +| `bench_numpy_fft.py` | `mkl_fft.interfaces.numpy_fft` | All exported functions including Hermitian (`hfft`, `ihfft`) | +| `bench_scipy_fft.py` | `mkl_fft.interfaces.scipy_fft` | All exported functions including Hermitian 2-D/N-D (`hfft2`, `hfftn`) | +| `bench_memory.py` | `mkl_fft` | Peak RSS for 1-D, 2-D, and 3-D transforms | + +Benchmarks cover float32, float64, complex64, complex128 dtypes, power-of-two +and non-power-of-two sizes, square and non-square/non-cubic shapes. + +## Threading + +`__init__.py` pins `MKL_NUM_THREADS` to **4** when the machine has 4 or more +physical cores, or falls back to **1** (single-threaded) otherwise. This keeps +results comparable across CI machines in the shared pool regardless of their +total core count. Physical cores are read from `/proc/cpuinfo` — hyperthreads +are excluded per MKL recommendation. + +Override by setting `MKL_NUM_THREADS` in the environment before running ASV. + +## Running Locally + +> Benchmarks are designed for CI. Local runs require mkl_fft to be installed +> in the active Python environment. + +```bash +cd benchmarks/ + +# Quick smoke-run against the current working tree (no env management) +asv run --python=same --quick --show-stderr HEAD^! + +# Run a specific benchmark file +asv run --python=same --quick --bench bench_fft1d HEAD^! + +# View and publish results +asv publish # generates .asv/html/ +asv preview # serves at http://localhost:8080 +``` + +## CI + +Benchmarks run automatically in Jenkins on the `auto-bench` node via +`benchmarkHelper.performanceTest()` from the shared library. The pipeline uses: + +```bash +asv run --environment existing: --set-commit-hash $COMMIT_SHA +``` + +This bypasses ASV environment management entirely — mkl_fft is pre-installed +into a conda environment by the pipeline before ASV is invoked. + +- **Nightly (prod):** results are published to the benchmark dashboard +- **PR (dev):** `asv compare` output is evaluated for regressions; a 30% slowdown + triggers a failed GitHub commit status + +Results are stored in the `mkl_fft-results` branch of +`intel-innersource/libraries.python.intel.infrastructure.benchmark-dashboards`. From 048ed75258e65eb7e2361eb0aaeefd8af3b2c244 Mon Sep 17 00:00:00 2001 From: vchamarthi Date: Mon, 13 Apr 2026 22:18:39 -0500 Subject: [PATCH 3/4] cp-pr fixes --- benchmarks/README.md | 10 ++++++++-- benchmarks/benchmarks/__init__.py | 4 ++-- benchmarks/benchmarks/bench_fft1d.py | 9 +-------- benchmarks/benchmarks/bench_fftnd.py | 13 +------------ benchmarks/benchmarks/bench_memory.py | 9 +-------- benchmarks/benchmarks/bench_numpy_fft.py | 15 +-------------- benchmarks/benchmarks/bench_scipy_fft.py | 19 +------------------ 7 files changed, 15 insertions(+), 64 deletions(-) diff --git a/benchmarks/README.md b/benchmarks/README.md index 9720a713..9d9c2bac 100644 --- a/benchmarks/README.md +++ b/benchmarks/README.md @@ -42,8 +42,14 @@ Override by setting `MKL_NUM_THREADS` in the environment before running ASV. ## Running Locally -> Benchmarks are designed for CI. Local runs require mkl_fft to be installed -> in the active Python environment. +> Benchmarks are designed for CI. Local runs require `mkl_fft` to be installed +> in the active Python environment. Benchmarks that exercise SciPy interface +> (`bench_scipy_fft.py`) also require SciPy: +> +> ```bash +> python -m pip install -e .. +> python -m pip install scipy +> ``` ```bash cd benchmarks/ diff --git a/benchmarks/benchmarks/__init__.py b/benchmarks/benchmarks/__init__.py index 74dd0614..3010d84b 100644 --- a/benchmarks/benchmarks/__init__.py +++ b/benchmarks/benchmarks/__init__.py @@ -44,5 +44,5 @@ def _thread_count(): _THREADS = os.environ.get("MKL_NUM_THREADS", _thread_count()) os.environ["MKL_NUM_THREADS"] = _THREADS -os.environ["OMP_NUM_THREADS"] = _THREADS -os.environ["OPENBLAS_NUM_THREADS"] = _THREADS +os.environ.setdefault("OMP_NUM_THREADS", _THREADS) +os.environ.setdefault("OPENBLAS_NUM_THREADS", _THREADS) diff --git a/benchmarks/benchmarks/bench_fft1d.py b/benchmarks/benchmarks/bench_fft1d.py index ac8fa358..02d8b547 100644 --- a/benchmarks/benchmarks/bench_fft1d.py +++ b/benchmarks/benchmarks/bench_fft1d.py @@ -1,6 +1,7 @@ """Benchmarks for 1-D FFT operations using the mkl_fft root API.""" import numpy as np +import mkl_fft def _make_input(rng, n, dtype): @@ -33,11 +34,9 @@ def setup(self, n, dtype): self.x = _make_input(rng, n, dtype) def time_fft(self, n, dtype): - import mkl_fft mkl_fft.fft(self.x) def time_ifft(self, n, dtype): - import mkl_fft mkl_fft.ifft(self.x) @@ -64,11 +63,9 @@ def setup(self, n, dtype): ).astype(cdtype) def time_rfft(self, n, dtype): - import mkl_fft mkl_fft.rfft(self.x_real) def time_irfft(self, n, dtype): - import mkl_fft mkl_fft.irfft(self.x_complex, n=n) @@ -94,11 +91,9 @@ def setup(self, n, dtype): self.x = _make_input(rng, n, dtype) def time_fft(self, n, dtype): - import mkl_fft mkl_fft.fft(self.x) def time_ifft(self, n, dtype): - import mkl_fft mkl_fft.ifft(self.x) @@ -124,9 +119,7 @@ def setup(self, n, dtype): ).astype(cdtype) def time_rfft(self, n, dtype): - import mkl_fft mkl_fft.rfft(self.x_real) def time_irfft(self, n, dtype): - import mkl_fft mkl_fft.irfft(self.x_complex, n=n) diff --git a/benchmarks/benchmarks/bench_fftnd.py b/benchmarks/benchmarks/bench_fftnd.py index 89468da4..e54fc31d 100644 --- a/benchmarks/benchmarks/bench_fftnd.py +++ b/benchmarks/benchmarks/bench_fftnd.py @@ -1,6 +1,7 @@ """Benchmarks for 2-D and N-D FFT operations using the mkl_fft root API.""" import numpy as np +import mkl_fft def _make_input(rng, shape, dtype): @@ -36,11 +37,9 @@ def setup(self, shape, dtype): self.x = _make_input(rng, shape, dtype) def time_fft2(self, shape, dtype): - import mkl_fft mkl_fft.fft2(self.x) def time_ifft2(self, shape, dtype): - import mkl_fft mkl_fft.ifft2(self.x) @@ -68,11 +67,9 @@ def setup(self, shape, dtype): ).astype(cdtype) def time_rfft2(self, shape, dtype): - import mkl_fft mkl_fft.rfft2(self.x_real) def time_irfft2(self, shape, dtype): - import mkl_fft mkl_fft.irfft2(self.x_complex, s=shape) @@ -97,11 +94,9 @@ def setup(self, shape, dtype): self.x = _make_input(rng, shape, dtype) def time_fft2(self, shape, dtype): - import mkl_fft mkl_fft.fft2(self.x) def time_ifft2(self, shape, dtype): - import mkl_fft mkl_fft.ifft2(self.x) @@ -126,11 +121,9 @@ def setup(self, shape, dtype): self.x = _make_input(rng, shape, dtype) def time_fftn(self, shape, dtype): - import mkl_fft mkl_fft.fftn(self.x) def time_ifftn(self, shape, dtype): - import mkl_fft mkl_fft.ifftn(self.x) @@ -158,11 +151,9 @@ def setup(self, shape, dtype): ).astype(cdtype) def time_rfftn(self, shape, dtype): - import mkl_fft mkl_fft.rfftn(self.x_real) def time_irfftn(self, shape, dtype): - import mkl_fft mkl_fft.irfftn(self.x_complex, s=shape) @@ -187,9 +178,7 @@ def setup(self, shape, dtype): self.x = _make_input(rng, shape, dtype) def time_fftn(self, shape, dtype): - import mkl_fft mkl_fft.fftn(self.x) def time_ifftn(self, shape, dtype): - import mkl_fft mkl_fft.ifftn(self.x) diff --git a/benchmarks/benchmarks/bench_memory.py b/benchmarks/benchmarks/bench_memory.py index c57d7692..44388afa 100644 --- a/benchmarks/benchmarks/bench_memory.py +++ b/benchmarks/benchmarks/bench_memory.py @@ -5,6 +5,7 @@ """ import numpy as np +import mkl_fft def _make_input(rng, shape, dtype): @@ -32,11 +33,9 @@ def setup(self, n, dtype): self.x = _make_input(np.random.RandomState(42), n, dtype) def peakmem_fft(self, n, dtype): - import mkl_fft mkl_fft.fft(self.x) def peakmem_ifft(self, n, dtype): - import mkl_fft mkl_fft.ifft(self.x) @@ -62,11 +61,9 @@ def setup(self, n, dtype): ).astype(cdtype) def peakmem_rfft(self, n, dtype): - import mkl_fft mkl_fft.rfft(self.x_real) def peakmem_irfft(self, n, dtype): - import mkl_fft mkl_fft.irfft(self.x_complex, n=n) @@ -87,11 +84,9 @@ def setup(self, shape, dtype): self.x = _make_input(np.random.RandomState(42), shape, dtype) def peakmem_fft2(self, shape, dtype): - import mkl_fft mkl_fft.fft2(self.x) def peakmem_ifft2(self, shape, dtype): - import mkl_fft mkl_fft.ifft2(self.x) @@ -112,9 +107,7 @@ def setup(self, shape, dtype): self.x = _make_input(np.random.RandomState(42), shape, dtype) def peakmem_fftn(self, shape, dtype): - import mkl_fft mkl_fft.fftn(self.x) def peakmem_ifftn(self, shape, dtype): - import mkl_fft mkl_fft.ifftn(self.x) diff --git a/benchmarks/benchmarks/bench_numpy_fft.py b/benchmarks/benchmarks/bench_numpy_fft.py index cd75d271..e41bbc10 100644 --- a/benchmarks/benchmarks/bench_numpy_fft.py +++ b/benchmarks/benchmarks/bench_numpy_fft.py @@ -11,6 +11,7 @@ """ import numpy as np +from mkl_fft.interfaces import numpy_fft def _make_input(rng, shape, dtype): @@ -43,11 +44,9 @@ def setup(self, n, dtype): self.x = _make_input(np.random.RandomState(42), n, dtype) def time_fft(self, n, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.fft(self.x) def time_ifft(self, n, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.ifft(self.x) @@ -73,11 +72,9 @@ def setup(self, n, dtype): ).astype(cdtype) def time_rfft(self, n, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.rfft(self.x_real) def time_irfft(self, n, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.irfft(self.x_complex, n=n) @@ -111,11 +108,9 @@ def setup(self, n, dtype): self.x_ihfft = rng.randn(n).astype(dtype) def time_hfft(self, n, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.hfft(self.x_hfft, n=n) def time_ihfft(self, n, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.ihfft(self.x_ihfft) @@ -136,11 +131,9 @@ def setup(self, shape, dtype): self.x = _make_input(np.random.RandomState(42), shape, dtype) def time_fft2(self, shape, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.fft2(self.x) def time_ifft2(self, shape, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.ifft2(self.x) @@ -167,11 +160,9 @@ def setup(self, shape, dtype): ).astype(cdtype) def time_rfft2(self, shape, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.rfft2(self.x_real) def time_irfft2(self, shape, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.irfft2(self.x_complex, s=shape) @@ -192,11 +183,9 @@ def setup(self, shape, dtype): self.x = _make_input(np.random.RandomState(42), shape, dtype) def time_fftn(self, shape, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.fftn(self.x) def time_ifftn(self, shape, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.ifftn(self.x) @@ -223,9 +212,7 @@ def setup(self, shape, dtype): ).astype(cdtype) def time_rfftn(self, shape, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.rfftn(self.x_real) def time_irfftn(self, shape, dtype): - from mkl_fft.interfaces import numpy_fft numpy_fft.irfftn(self.x_complex, s=shape) diff --git a/benchmarks/benchmarks/bench_scipy_fft.py b/benchmarks/benchmarks/bench_scipy_fft.py index 6b955ec6..2d83078a 100644 --- a/benchmarks/benchmarks/bench_scipy_fft.py +++ b/benchmarks/benchmarks/bench_scipy_fft.py @@ -13,6 +13,7 @@ """ import numpy as np +from mkl_fft.interfaces import scipy_fft def _make_input(rng, shape, dtype): @@ -45,11 +46,9 @@ def setup(self, n, dtype): self.x = _make_input(np.random.RandomState(42), n, dtype) def time_fft(self, n, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.fft(self.x) def time_ifft(self, n, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.ifft(self.x) @@ -75,11 +74,9 @@ def setup(self, n, dtype): ).astype(cdtype) def time_rfft(self, n, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.rfft(self.x_real) def time_irfft(self, n, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.irfft(self.x_complex, n=n) @@ -111,11 +108,9 @@ def setup(self, n, dtype): self.x_ihfft = rng.randn(n).astype(dtype) def time_hfft(self, n, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.hfft(self.x_hfft, n=n) def time_ihfft(self, n, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.ihfft(self.x_ihfft) @@ -136,11 +131,9 @@ def setup(self, shape, dtype): self.x = _make_input(np.random.RandomState(42), shape, dtype) def time_fft2(self, shape, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.fft2(self.x) def time_ifft2(self, shape, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.ifft2(self.x) @@ -167,11 +160,9 @@ def setup(self, shape, dtype): ).astype(cdtype) def time_rfft2(self, shape, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.rfft2(self.x_real) def time_irfft2(self, shape, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.irfft2(self.x_complex, s=shape) @@ -203,11 +194,9 @@ def setup(self, shape, dtype): self.x_ihfft2 = rng.randn(*shape).astype(dtype) def time_hfft2(self, shape, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.hfft2(self.x_hfft2, s=shape) def time_ihfft2(self, shape, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.ihfft2(self.x_ihfft2) @@ -228,11 +217,9 @@ def setup(self, shape, dtype): self.x = _make_input(np.random.RandomState(42), shape, dtype) def time_fftn(self, shape, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.fftn(self.x) def time_ifftn(self, shape, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.ifftn(self.x) @@ -259,11 +246,9 @@ def setup(self, shape, dtype): ).astype(cdtype) def time_rfftn(self, shape, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.rfftn(self.x_real) def time_irfftn(self, shape, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.irfftn(self.x_complex, s=shape) @@ -296,9 +281,7 @@ def setup(self, shape, dtype): self.x_ihfftn = rng.randn(*shape).astype(dtype) def time_hfftn(self, shape, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.hfftn(self.x_hfftn, s=shape) def time_ihfftn(self, shape, dtype): - from mkl_fft.interfaces import scipy_fft scipy_fft.ihfftn(self.x_ihfftn) From 64ebd02dc8ec4218b6190776b22dba6495fe5133 Mon Sep 17 00:00:00 2001 From: vchamarthi Date: Tue, 14 Apr 2026 10:20:49 -0500 Subject: [PATCH 4/4] pre-commit fixes --- benchmarks/asv.conf.json | 8 ++- benchmarks/benchmarks/__init__.py | 4 +- benchmarks/benchmarks/bench_fft1d.py | 29 +++++++---- benchmarks/benchmarks/bench_fftnd.py | 64 ++++++++++++++++-------- benchmarks/benchmarks/bench_memory.py | 24 ++++++--- benchmarks/benchmarks/bench_numpy_fft.py | 48 +++++++++++------- benchmarks/benchmarks/bench_scipy_fft.py | 64 +++++++++++++++--------- 7 files changed, 160 insertions(+), 81 deletions(-) diff --git a/benchmarks/asv.conf.json b/benchmarks/asv.conf.json index 29ebbfe1..aa661032 100644 --- a/benchmarks/asv.conf.json +++ b/benchmarks/asv.conf.json @@ -4,12 +4,16 @@ "project_url": "https://github.com/IntelPython/mkl_fft", "show_commit_url": "https://github.com/IntelPython/mkl_fft/commit/", "repo": "..", - "branches": ["master"], + "branches": [ + "master" + ], "benchmark_dir": "benchmarks", "env_dir": ".asv/env", "results_dir": ".asv/results", "html_dir": ".asv/html", "build_cache_size": 2, "default_benchmark_timeout": 500, - "regressions_thresholds": {".*": 0.3} + "regressions_thresholds": { + ".*": 0.3 + } } diff --git a/benchmarks/benchmarks/__init__.py b/benchmarks/benchmarks/__init__.py index 3010d84b..2a37b3d3 100644 --- a/benchmarks/benchmarks/__init__.py +++ b/benchmarks/benchmarks/__init__.py @@ -31,7 +31,9 @@ def _physical_cores(): with open("/proc/cpuinfo") as f: content = f.read() cpu_cores = int(re.search(r"cpu cores\s*:\s*(\d+)", content).group(1)) - sockets = max(len(set(re.findall(r"physical id\s*:\s*(\d+)", content))), 1) + sockets = max( + len(set(re.findall(r"physical id\s*:\s*(\d+)", content))), 1 + ) return cpu_cores * sockets except Exception: return os.cpu_count() or 1 diff --git a/benchmarks/benchmarks/bench_fft1d.py b/benchmarks/benchmarks/bench_fft1d.py index 02d8b547..62d0c40f 100644 --- a/benchmarks/benchmarks/bench_fft1d.py +++ b/benchmarks/benchmarks/bench_fft1d.py @@ -1,8 +1,11 @@ """Benchmarks for 1-D FFT operations using the mkl_fft root API.""" import numpy as np + import mkl_fft +_RNG_SEED = 42 + def _make_input(rng, n, dtype): """Return a 1-D array of length *n* with the given *dtype*. @@ -12,14 +15,15 @@ def _make_input(rng, n, dtype): """ dt = np.dtype(dtype) if dt.kind == "c": - return (rng.randn(n) + 1j * rng.randn(n)).astype(dt) - return rng.randn(n).astype(dt) + return (rng.standard_normal(n) + 1j * rng.standard_normal(n)).astype(dt) + return rng.standard_normal(n).astype(dt) # --------------------------------------------------------------------------- # Complex-to-complex 1-D (power-of-two sizes) # --------------------------------------------------------------------------- + class TimeFFT1D: """Forward and inverse complex FFT — power-of-two sizes.""" @@ -30,7 +34,7 @@ class TimeFFT1D: param_names = ["n", "dtype"] def setup(self, n, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) self.x = _make_input(rng, n, dtype) def time_fft(self, n, dtype): @@ -44,6 +48,7 @@ def time_ifft(self, n, dtype): # Real-to-complex / complex-to-real 1-D (power-of-two sizes) # --------------------------------------------------------------------------- + class TimeRFFT1D: """Forward rfft and inverse irfft — power-of-two sizes.""" @@ -54,12 +59,13 @@ class TimeRFFT1D: param_names = ["n", "dtype"] def setup(self, n, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" - self.x_real = rng.randn(n).astype(dtype) + self.x_real = rng.standard_normal(n).astype(dtype) # irfft input: complex half-spectrum of length n//2+1 self.x_complex = ( - rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + rng.standard_normal(n // 2 + 1) + + 1j * rng.standard_normal(n // 2 + 1) ).astype(cdtype) def time_rfft(self, n, dtype): @@ -73,6 +79,7 @@ def time_irfft(self, n, dtype): # Complex-to-complex 1-D (non-power-of-two sizes) # --------------------------------------------------------------------------- + class TimeFFT1DNonPow2: """Forward and inverse complex FFT — non-power-of-two sizes. @@ -87,7 +94,7 @@ class TimeFFT1DNonPow2: param_names = ["n", "dtype"] def setup(self, n, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) self.x = _make_input(rng, n, dtype) def time_fft(self, n, dtype): @@ -101,6 +108,7 @@ def time_ifft(self, n, dtype): # Real-to-complex / complex-to-real 1-D (non-power-of-two sizes) # --------------------------------------------------------------------------- + class TimeRFFT1DNonPow2: """Forward rfft and inverse irfft — non-power-of-two sizes.""" @@ -111,11 +119,12 @@ class TimeRFFT1DNonPow2: param_names = ["n", "dtype"] def setup(self, n, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" - self.x_real = rng.randn(n).astype(dtype) + self.x_real = rng.standard_normal(n).astype(dtype) self.x_complex = ( - rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + rng.standard_normal(n // 2 + 1) + + 1j * rng.standard_normal(n // 2 + 1) ).astype(cdtype) def time_rfft(self, n, dtype): diff --git a/benchmarks/benchmarks/bench_fftnd.py b/benchmarks/benchmarks/bench_fftnd.py index e54fc31d..b5503031 100644 --- a/benchmarks/benchmarks/bench_fftnd.py +++ b/benchmarks/benchmarks/bench_fftnd.py @@ -1,8 +1,11 @@ """Benchmarks for 2-D and N-D FFT operations using the mkl_fft root API.""" import numpy as np + import mkl_fft +_RNG_SEED = 42 + def _make_input(rng, shape, dtype): """Return an array of the given *shape* and *dtype*. @@ -12,28 +15,35 @@ def _make_input(rng, shape, dtype): """ dt = np.dtype(dtype) if dt.kind == "c": - return (rng.randn(*shape) + 1j * rng.randn(*shape)).astype(dt) - return rng.randn(*shape).astype(dt) + return ( + rng.standard_normal(shape) + 1j * rng.standard_normal(shape) + ).astype(dt) + return rng.standard_normal(shape).astype(dt) # --------------------------------------------------------------------------- # 2-D complex-to-complex (power-of-two, square + non-square) # --------------------------------------------------------------------------- + class TimeFFT2D: """Forward and inverse 2-D FFT — square and non-square shapes.""" params = [ [ - (64, 64), (128, 128), (256, 256), (512, 512), - (256, 128), (512, 256), # non-square + (64, 64), + (128, 128), + (256, 256), + (512, 512), + (256, 128), + (512, 256), # non-square ], ["float32", "float64", "complex64", "complex128"], ] param_names = ["shape", "dtype"] def setup(self, shape, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) self.x = _make_input(rng, shape, dtype) def time_fft2(self, shape, dtype): @@ -47,6 +57,7 @@ def time_ifft2(self, shape, dtype): # 2-D real-to-complex / complex-to-real # --------------------------------------------------------------------------- + class TimeRFFT2D: """Forward rfft2 and inverse irfft2.""" @@ -57,13 +68,14 @@ class TimeRFFT2D: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" half_shape = (shape[0], shape[1] // 2 + 1) - self.x_real = rng.randn(*shape).astype(dtype) + self.x_real = rng.standard_normal(shape).astype(dtype) # irfft2 input: complex half-spectrum — shape (M, N//2+1) self.x_complex = ( - rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + rng.standard_normal(half_shape) + + 1j * rng.standard_normal(half_shape) ).astype(cdtype) def time_rfft2(self, shape, dtype): @@ -77,20 +89,24 @@ def time_irfft2(self, shape, dtype): # 2-D complex-to-complex (non-power-of-two) # --------------------------------------------------------------------------- + class TimeFFT2DNonPow2: """Forward and inverse 2-D FFT — non-power-of-two sizes.""" params = [ [ - (96, 96), (100, 100), (270, 270), (500, 500), - (100, 200), # non-square non-pow2 + (96, 96), + (100, 100), + (270, 270), + (500, 500), + (100, 200), # non-square non-pow2 ], ["float64", "complex128"], ] param_names = ["shape", "dtype"] def setup(self, shape, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) self.x = _make_input(rng, shape, dtype) def time_fft2(self, shape, dtype): @@ -104,20 +120,23 @@ def time_ifft2(self, shape, dtype): # N-D complex-to-complex (3-D cubes + non-cubic shape) # --------------------------------------------------------------------------- + class TimeFFTnD: """Forward and inverse N-D FFT.""" params = [ [ - (16, 16, 16), (32, 32, 32), (64, 64, 64), - (32, 64, 128), # non-cubic + (16, 16, 16), + (32, 32, 32), + (64, 64, 64), + (32, 64, 128), # non-cubic ], ["float32", "float64", "complex64", "complex128"], ] param_names = ["shape", "dtype"] def setup(self, shape, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) self.x = _make_input(rng, shape, dtype) def time_fftn(self, shape, dtype): @@ -131,6 +150,7 @@ def time_ifftn(self, shape, dtype): # N-D real-to-complex / complex-to-real # --------------------------------------------------------------------------- + class TimeRFFTnD: """Forward rfftn and inverse irfftn.""" @@ -141,13 +161,14 @@ class TimeRFFTnD: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" # irfftn input: complex half-spectrum — last axis is shape[-1]//2+1 half_shape = shape[:-1] + (shape[-1] // 2 + 1,) - self.x_real = rng.randn(*shape).astype(dtype) + self.x_real = rng.standard_normal(shape).astype(dtype) self.x_complex = ( - rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + rng.standard_normal(half_shape) + + 1j * rng.standard_normal(half_shape) ).astype(cdtype) def time_rfftn(self, shape, dtype): @@ -161,20 +182,23 @@ def time_irfftn(self, shape, dtype): # N-D complex-to-complex (non-power-of-two 3-D) # --------------------------------------------------------------------------- + class TimeFFTnDNonPow2: """Forward and inverse N-D FFT — non-power-of-two sizes.""" params = [ [ - (24, 24, 24), (30, 30, 30), (50, 50, 50), - (30, 40, 50), # non-cubic non-pow2 + (24, 24, 24), + (30, 30, 30), + (50, 50, 50), + (30, 40, 50), # non-cubic non-pow2 ], ["float64", "complex128"], ] param_names = ["shape", "dtype"] def setup(self, shape, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) self.x = _make_input(rng, shape, dtype) def time_fftn(self, shape, dtype): diff --git a/benchmarks/benchmarks/bench_memory.py b/benchmarks/benchmarks/bench_memory.py index 44388afa..1efe3ccd 100644 --- a/benchmarks/benchmarks/bench_memory.py +++ b/benchmarks/benchmarks/bench_memory.py @@ -5,21 +5,25 @@ """ import numpy as np + import mkl_fft +_RNG_SEED = 42 + def _make_input(rng, shape, dtype): dt = np.dtype(dtype) s = (shape,) if isinstance(shape, int) else shape if dt.kind == "c": - return (rng.randn(*s) + 1j * rng.randn(*s)).astype(dt) - return rng.randn(*s).astype(dt) + return (rng.standard_normal(s) + 1j * rng.standard_normal(s)).astype(dt) + return rng.standard_normal(s).astype(dt) # --------------------------------------------------------------------------- # 1-D complex FFT # --------------------------------------------------------------------------- + class PeakMemFFT1D: """Peak RSS for 1-D complex FFT.""" @@ -30,7 +34,7 @@ class PeakMemFFT1D: param_names = ["n", "dtype"] def setup(self, n, dtype): - self.x = _make_input(np.random.RandomState(42), n, dtype) + self.x = _make_input(np.random.default_rng(_RNG_SEED), n, dtype) def peakmem_fft(self, n, dtype): mkl_fft.fft(self.x) @@ -43,6 +47,7 @@ def peakmem_ifft(self, n, dtype): # 1-D real FFT # --------------------------------------------------------------------------- + class PeakMemRFFT1D: """Peak RSS for 1-D real FFT (forward and inverse).""" @@ -53,11 +58,12 @@ class PeakMemRFFT1D: param_names = ["n", "dtype"] def setup(self, n, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" - self.x_real = rng.randn(n).astype(dtype) + self.x_real = rng.standard_normal(n).astype(dtype) self.x_complex = ( - rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + rng.standard_normal(n // 2 + 1) + + 1j * rng.standard_normal(n // 2 + 1) ).astype(cdtype) def peakmem_rfft(self, n, dtype): @@ -71,6 +77,7 @@ def peakmem_irfft(self, n, dtype): # 2-D complex FFT # --------------------------------------------------------------------------- + class PeakMemFFT2D: """Peak RSS for 2-D complex FFT.""" @@ -81,7 +88,7 @@ class PeakMemFFT2D: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - self.x = _make_input(np.random.RandomState(42), shape, dtype) + self.x = _make_input(np.random.default_rng(_RNG_SEED), shape, dtype) def peakmem_fft2(self, shape, dtype): mkl_fft.fft2(self.x) @@ -94,6 +101,7 @@ def peakmem_ifft2(self, shape, dtype): # N-D complex FFT (3-D) # --------------------------------------------------------------------------- + class PeakMemFFTnD: """Peak RSS for N-D complex FFT (3-D shapes).""" @@ -104,7 +112,7 @@ class PeakMemFFTnD: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - self.x = _make_input(np.random.RandomState(42), shape, dtype) + self.x = _make_input(np.random.default_rng(_RNG_SEED), shape, dtype) def peakmem_fftn(self, shape, dtype): mkl_fft.fftn(self.x) diff --git a/benchmarks/benchmarks/bench_numpy_fft.py b/benchmarks/benchmarks/bench_numpy_fft.py index e41bbc10..f1373d8d 100644 --- a/benchmarks/benchmarks/bench_numpy_fft.py +++ b/benchmarks/benchmarks/bench_numpy_fft.py @@ -11,8 +11,11 @@ """ import numpy as np + from mkl_fft.interfaces import numpy_fft +_RNG_SEED = 42 + def _make_input(rng, shape, dtype): """Return an array of *shape* and *dtype*. @@ -23,14 +26,15 @@ def _make_input(rng, shape, dtype): dt = np.dtype(dtype) s = (shape,) if isinstance(shape, int) else shape if dt.kind == "c": - return (rng.randn(*s) + 1j * rng.randn(*s)).astype(dt) - return rng.randn(*s).astype(dt) + return (rng.standard_normal(s) + 1j * rng.standard_normal(s)).astype(dt) + return rng.standard_normal(s).astype(dt) # --------------------------------------------------------------------------- # 1-D complex-to-complex # --------------------------------------------------------------------------- + class TimeC2C1D: """numpy_fft.fft / ifft — 1-D.""" @@ -41,7 +45,7 @@ class TimeC2C1D: param_names = ["n", "dtype"] def setup(self, n, dtype): - self.x = _make_input(np.random.RandomState(42), n, dtype) + self.x = _make_input(np.random.default_rng(_RNG_SEED), n, dtype) def time_fft(self, n, dtype): numpy_fft.fft(self.x) @@ -54,6 +58,7 @@ def time_ifft(self, n, dtype): # 1-D real-to-complex / complex-to-real # --------------------------------------------------------------------------- + class TimeRC1D: """numpy_fft.rfft / irfft — 1-D.""" @@ -64,11 +69,12 @@ class TimeRC1D: param_names = ["n", "dtype"] def setup(self, n, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" - self.x_real = rng.randn(n).astype(dtype) + self.x_real = rng.standard_normal(n).astype(dtype) self.x_complex = ( - rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + rng.standard_normal(n // 2 + 1) + + 1j * rng.standard_normal(n // 2 + 1) ).astype(cdtype) def time_rfft(self, n, dtype): @@ -84,6 +90,7 @@ def time_irfft(self, n, dtype): # ihfft: input real length n → output complex length n//2+1 # --------------------------------------------------------------------------- + class TimeHermitian1D: """numpy_fft.hfft / ihfft — 1-D Hermitian. @@ -98,14 +105,15 @@ class TimeHermitian1D: param_names = ["n", "dtype"] def setup(self, n, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" # hfft input: complex half-spectrum of length n//2+1 self.x_hfft = ( - rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + rng.standard_normal(n // 2 + 1) + + 1j * rng.standard_normal(n // 2 + 1) ).astype(cdtype) # ihfft input: real signal of length n - self.x_ihfft = rng.randn(n).astype(dtype) + self.x_ihfft = rng.standard_normal(n).astype(dtype) def time_hfft(self, n, dtype): numpy_fft.hfft(self.x_hfft, n=n) @@ -118,6 +126,7 @@ def time_ihfft(self, n, dtype): # 2-D complex-to-complex # --------------------------------------------------------------------------- + class TimeC2C2D: """numpy_fft.fft2 / ifft2 — 2-D.""" @@ -128,7 +137,7 @@ class TimeC2C2D: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - self.x = _make_input(np.random.RandomState(42), shape, dtype) + self.x = _make_input(np.random.default_rng(_RNG_SEED), shape, dtype) def time_fft2(self, shape, dtype): numpy_fft.fft2(self.x) @@ -141,6 +150,7 @@ def time_ifft2(self, shape, dtype): # 2-D real-to-complex / complex-to-real # --------------------------------------------------------------------------- + class TimeRC2D: """numpy_fft.rfft2 / irfft2 — 2-D.""" @@ -151,12 +161,13 @@ class TimeRC2D: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" half_shape = (shape[0], shape[1] // 2 + 1) - self.x_real = rng.randn(*shape).astype(dtype) + self.x_real = rng.standard_normal(shape).astype(dtype) self.x_complex = ( - rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + rng.standard_normal(half_shape) + + 1j * rng.standard_normal(half_shape) ).astype(cdtype) def time_rfft2(self, shape, dtype): @@ -170,6 +181,7 @@ def time_irfft2(self, shape, dtype): # N-D complex-to-complex # --------------------------------------------------------------------------- + class TimeCCND: """numpy_fft.fftn / ifftn — N-D.""" @@ -180,7 +192,7 @@ class TimeCCND: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - self.x = _make_input(np.random.RandomState(42), shape, dtype) + self.x = _make_input(np.random.default_rng(_RNG_SEED), shape, dtype) def time_fftn(self, shape, dtype): numpy_fft.fftn(self.x) @@ -193,6 +205,7 @@ def time_ifftn(self, shape, dtype): # N-D real-to-complex / complex-to-real # --------------------------------------------------------------------------- + class TimeRCND: """numpy_fft.rfftn / irfftn — N-D.""" @@ -203,12 +216,13 @@ class TimeRCND: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" half_shape = shape[:-1] + (shape[-1] // 2 + 1,) - self.x_real = rng.randn(*shape).astype(dtype) + self.x_real = rng.standard_normal(shape).astype(dtype) self.x_complex = ( - rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + rng.standard_normal(half_shape) + + 1j * rng.standard_normal(half_shape) ).astype(cdtype) def time_rfftn(self, shape, dtype): diff --git a/benchmarks/benchmarks/bench_scipy_fft.py b/benchmarks/benchmarks/bench_scipy_fft.py index 2d83078a..ca79ea18 100644 --- a/benchmarks/benchmarks/bench_scipy_fft.py +++ b/benchmarks/benchmarks/bench_scipy_fft.py @@ -13,8 +13,11 @@ """ import numpy as np + from mkl_fft.interfaces import scipy_fft +_RNG_SEED = 42 + def _make_input(rng, shape, dtype): """Return an array of *shape* and *dtype*. @@ -25,14 +28,15 @@ def _make_input(rng, shape, dtype): dt = np.dtype(dtype) s = (shape,) if isinstance(shape, int) else shape if dt.kind == "c": - return (rng.randn(*s) + 1j * rng.randn(*s)).astype(dt) - return rng.randn(*s).astype(dt) + return (rng.standard_normal(s) + 1j * rng.standard_normal(s)).astype(dt) + return rng.standard_normal(s).astype(dt) # --------------------------------------------------------------------------- # 1-D complex-to-complex # --------------------------------------------------------------------------- + class TimeC2C1D: """scipy_fft.fft / ifft — 1-D.""" @@ -43,7 +47,7 @@ class TimeC2C1D: param_names = ["n", "dtype"] def setup(self, n, dtype): - self.x = _make_input(np.random.RandomState(42), n, dtype) + self.x = _make_input(np.random.default_rng(_RNG_SEED), n, dtype) def time_fft(self, n, dtype): scipy_fft.fft(self.x) @@ -56,6 +60,7 @@ def time_ifft(self, n, dtype): # 1-D real-to-complex / complex-to-real # --------------------------------------------------------------------------- + class TimeRC1D: """scipy_fft.rfft / irfft — 1-D.""" @@ -66,11 +71,12 @@ class TimeRC1D: param_names = ["n", "dtype"] def setup(self, n, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" - self.x_real = rng.randn(n).astype(dtype) + self.x_real = rng.standard_normal(n).astype(dtype) self.x_complex = ( - rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + rng.standard_normal(n // 2 + 1) + + 1j * rng.standard_normal(n // 2 + 1) ).astype(cdtype) def time_rfft(self, n, dtype): @@ -86,6 +92,7 @@ def time_irfft(self, n, dtype): # ihfft: input real length n → output complex length n//2+1 # --------------------------------------------------------------------------- + class TimeHermitian1D: """scipy_fft.hfft / ihfft — 1-D Hermitian. @@ -100,12 +107,13 @@ class TimeHermitian1D: param_names = ["n", "dtype"] def setup(self, n, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" self.x_hfft = ( - rng.randn(n // 2 + 1) + 1j * rng.randn(n // 2 + 1) + rng.standard_normal(n // 2 + 1) + + 1j * rng.standard_normal(n // 2 + 1) ).astype(cdtype) - self.x_ihfft = rng.randn(n).astype(dtype) + self.x_ihfft = rng.standard_normal(n).astype(dtype) def time_hfft(self, n, dtype): scipy_fft.hfft(self.x_hfft, n=n) @@ -118,6 +126,7 @@ def time_ihfft(self, n, dtype): # 2-D complex-to-complex # --------------------------------------------------------------------------- + class TimeC2C2D: """scipy_fft.fft2 / ifft2 — 2-D.""" @@ -128,7 +137,7 @@ class TimeC2C2D: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - self.x = _make_input(np.random.RandomState(42), shape, dtype) + self.x = _make_input(np.random.default_rng(_RNG_SEED), shape, dtype) def time_fft2(self, shape, dtype): scipy_fft.fft2(self.x) @@ -141,6 +150,7 @@ def time_ifft2(self, shape, dtype): # 2-D real-to-complex / complex-to-real # --------------------------------------------------------------------------- + class TimeRC2D: """scipy_fft.rfft2 / irfft2 — 2-D.""" @@ -151,12 +161,13 @@ class TimeRC2D: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" half_shape = (shape[0], shape[1] // 2 + 1) - self.x_real = rng.randn(*shape).astype(dtype) + self.x_real = rng.standard_normal(shape).astype(dtype) self.x_complex = ( - rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + rng.standard_normal(half_shape) + + 1j * rng.standard_normal(half_shape) ).astype(cdtype) def time_rfft2(self, shape, dtype): @@ -172,6 +183,7 @@ def time_irfft2(self, shape, dtype): # ihfft2: input real shape (M, N) → output complex shape (M, N//2+1) # --------------------------------------------------------------------------- + class TimeHermitian2D: """scipy_fft.hfft2 / ihfft2 — 2-D Hermitian. @@ -185,13 +197,14 @@ class TimeHermitian2D: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" half_shape = (shape[0], shape[1] // 2 + 1) self.x_hfft2 = ( - rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + rng.standard_normal(half_shape) + + 1j * rng.standard_normal(half_shape) ).astype(cdtype) - self.x_ihfft2 = rng.randn(*shape).astype(dtype) + self.x_ihfft2 = rng.standard_normal(shape).astype(dtype) def time_hfft2(self, shape, dtype): scipy_fft.hfft2(self.x_hfft2, s=shape) @@ -204,6 +217,7 @@ def time_ihfft2(self, shape, dtype): # N-D complex-to-complex # --------------------------------------------------------------------------- + class TimeCCND: """scipy_fft.fftn / ifftn — N-D.""" @@ -214,7 +228,7 @@ class TimeCCND: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - self.x = _make_input(np.random.RandomState(42), shape, dtype) + self.x = _make_input(np.random.default_rng(_RNG_SEED), shape, dtype) def time_fftn(self, shape, dtype): scipy_fft.fftn(self.x) @@ -227,6 +241,7 @@ def time_ifftn(self, shape, dtype): # N-D real-to-complex / complex-to-real # --------------------------------------------------------------------------- + class TimeRCND: """scipy_fft.rfftn / irfftn — N-D.""" @@ -237,12 +252,13 @@ class TimeRCND: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" half_shape = shape[:-1] + (shape[-1] // 2 + 1,) - self.x_real = rng.randn(*shape).astype(dtype) + self.x_real = rng.standard_normal(shape).astype(dtype) self.x_complex = ( - rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + rng.standard_normal(half_shape) + + 1j * rng.standard_normal(half_shape) ).astype(cdtype) def time_rfftn(self, shape, dtype): @@ -258,6 +274,7 @@ def time_irfftn(self, shape, dtype): # ihfftn: input real shape s → output complex, last axis length s[-1]//2+1 # --------------------------------------------------------------------------- + class TimeHermitianND: """scipy_fft.hfftn / ihfftn — N-D Hermitian. @@ -271,14 +288,15 @@ class TimeHermitianND: param_names = ["shape", "dtype"] def setup(self, shape, dtype): - rng = np.random.RandomState(42) + rng = np.random.default_rng(_RNG_SEED) cdtype = "complex64" if dtype == "float32" else "complex128" # hfftn input: last axis has length shape[-1]//2+1 half_shape = shape[:-1] + (shape[-1] // 2 + 1,) self.x_hfftn = ( - rng.randn(*half_shape) + 1j * rng.randn(*half_shape) + rng.standard_normal(half_shape) + + 1j * rng.standard_normal(half_shape) ).astype(cdtype) - self.x_ihfftn = rng.randn(*shape).astype(dtype) + self.x_ihfftn = rng.standard_normal(shape).astype(dtype) def time_hfftn(self, shape, dtype): scipy_fft.hfftn(self.x_hfftn, s=shape)