From 27f703785fe5bbedbd6507e6685ffe51676b16e5 Mon Sep 17 00:00:00 2001 From: Python Infra CI Date: Mon, 28 Sep 2026 14:06:01 +0000 Subject: [PATCH 1/3] perf: skip numpy array creation in _compute_fwd_scale scalar path --- mkl_fft/_fft_utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mkl_fft/_fft_utils.py b/mkl_fft/_fft_utils.py index 9681adcb..a2ad6cc3 100644 --- a/mkl_fft/_fft_utils.py +++ b/mkl_fft/_fft_utils.py @@ -70,7 +70,15 @@ def _compute_fwd_scale(norm, n, shape): return 1.0 ss = n if n is not None else shape - nn = np.prod(ss) + # The 1-D callers in `_mkl_fft.py` always pass a plain scalar here + # (either `n` or `x.shape[axis]`), so avoid the overhead of wrapping + # it in a 0-d numpy array via `np.prod` on that hot path. Sequences + # (used by the N-D callers via `_nd_fwd_scale`) still go through + # `np.prod`, unchanged. + if isinstance(ss, (int, np.integer)): + nn = ss + else: + nn = np.prod(ss) fsc = 1 / nn if nn != 0 else 1 if norm == "forward": return fsc From badf8391d41faba6f30b3747d785bdd755936148 Mon Sep 17 00:00:00 2001 From: "Harlow, Jordan" Date: Mon, 28 Sep 2026 08:26:47 -0600 Subject: [PATCH 2/3] chore: improve --- mkl_fft/_fft_utils.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mkl_fft/_fft_utils.py b/mkl_fft/_fft_utils.py index a2ad6cc3..dd9568c3 100644 --- a/mkl_fft/_fft_utils.py +++ b/mkl_fft/_fft_utils.py @@ -23,6 +23,8 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import math + import numpy as np # pylint: disable=no-name-in-module @@ -70,13 +72,16 @@ def _compute_fwd_scale(norm, n, shape): return 1.0 ss = n if n is not None else shape - # The 1-D callers in `_mkl_fft.py` always pass a plain scalar here - # (either `n` or `x.shape[axis]`), so avoid the overhead of wrapping - # it in a 0-d numpy array via `np.prod` on that hot path. Sequences - # (used by the N-D callers via `_nd_fwd_scale`) still go through - # `np.prod`, unchanged. + # `np.prod` dominates the Python-side cost of a small normalized transform, + # so take cheaper routes for the two shapes the callers actually pass: a + # scalar `n` (1-D) and a sequence (`_nd_fwd_scale`). It stays the fallback + # because `numpy.fft` also accepts array-like `n` and `s` (e.g. a 0-d + # `n=np.array(8)`, a 1-D `s=np.array([4, 4])`), which `math.prod` cannot + # handle uniformly. if isinstance(ss, (int, np.integer)): nn = ss + elif isinstance(ss, (list, tuple)): + nn = math.prod(ss) else: nn = np.prod(ss) fsc = 1 / nn if nn != 0 else 1 From e6ef92cf7a0c25f73a7e90ff4bbb1704d1bec9ad Mon Sep 17 00:00:00 2001 From: "Harlow, Jordan" Date: Tue, 29 Sep 2026 07:39:47 -0600 Subject: [PATCH 3/3] task: review --- CHANGELOG.md | 1 + mkl_fft/_fft_utils.py | 9 +++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3945d1a..3d1795ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Multi-iterator constructors now return a status corresponding to allocation success or failure, and raise `MemoryError` instead of `ValueError` [gh-373](https://github.com/IntelPython/mkl_fft/pull/373) * `_direct_fftnd` now also checks the status returned by the backend instead of discarding it [gh-373](https://github.com/IntelPython/mkl_fft/pull/373) * Pinned Cython in the Coverity Scan workflow so generated code stays stable between scans, and added `coverity/README.md` documenting the known false-positive families and the scan review checklist [gh-374](https://github.com/IntelPython/mkl_fft/pull/374) +* Reduced Python overhead in `norm="forward"`/`"ortho"` scaling by computing the scale factor without `numpy.prod` [gh-384](https://github.com/IntelPython/mkl_fft/pull/384) ### Fixed * Fixed `norm="forward"`/`"ortho"` scaling in `fftn`, `ifftn`, `rfftn`, `irfftn` and the `fft2` family when only a subset of axes is transformed: the scale used the full array shape instead of the transformed axes [gh-336](https://github.com/IntelPython/mkl_fft/issues/336), [gh-370](https://github.com/IntelPython/mkl_fft/pull/370) diff --git a/mkl_fft/_fft_utils.py b/mkl_fft/_fft_utils.py index dd9568c3..8e2fb505 100644 --- a/mkl_fft/_fft_utils.py +++ b/mkl_fft/_fft_utils.py @@ -72,12 +72,9 @@ def _compute_fwd_scale(norm, n, shape): return 1.0 ss = n if n is not None else shape - # `np.prod` dominates the Python-side cost of a small normalized transform, - # so take cheaper routes for the two shapes the callers actually pass: a - # scalar `n` (1-D) and a sequence (`_nd_fwd_scale`). It stays the fallback - # because `numpy.fft` also accepts array-like `n` and `s` (e.g. a 0-d - # `n=np.array(8)`, a 1-D `s=np.array([4, 4])`), which `math.prod` cannot - # handle uniformly. + # Avoid np.prod's array-creation overhead on the hot scalar (1-D) and + # sequence (N-D) paths; np.prod stays as the fallback for array-like + # `n`/`s` (e.g. np.array(8)) that math.prod can't handle. if isinstance(ss, (int, np.integer)): nn = ss elif isinstance(ss, (list, tuple)):