ENH: fast path for full contiguous reductions by eendebakpt · Pull Request #31274 · numpy/numpy · GitHub
Skip to content

ENH: fast path for full contiguous reductions - #31274

Merged
seberg merged 13 commits into
numpy:mainfrom
eendebakpt:reduction_contiguous_fast_path
Apr 29, 2026
Merged

seberg merged 13 commits into
numpy:mainfrom
eendebakpt:reduction_contiguous_fast_path

Conversation

@eendebakpt

@eendebakpt eendebakpt commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

PR summary

Adds a fast path in PyUFunc_Reduce for the common case of a full reduction (axis=None) over a contiguous, aligned, non-object input where the matching dtype equals the input dtype and the operation has an identity value (e.g. np.sum, np.prod, np.any, np.all).

(EDIT: Seberg, later broadened to include any 1-D arrays and no identity value.)

Benchmark results

Benchmark main branch
np.sum(array_4) 1.47 us 1.09 us (1.36x)
np.sum(array_20) 1.47 us 1.10 us (1.34x)
np.sum(array_100) 1.47 us 1.10 us (1.34x)
np.prod(array_20) 1.43 us 1.07 us (1.34x)
np.sum(10x10) 1.51 us 1.11 us (1.36x)
np.sum(2x2) 1.51 us 1.09 us (1.38x)
np.any(bool_20) 1.33 us 926 ns (1.44x)
np.all(bool_20) 1.33 us 937 ns (1.42x)
np.array_equal(a,b) 1.39 us 1.06 us (1.31x)
np.allclose(a,b) 12.4 us 11.2 us (1.10x)
np.max(array_20) n/s n/s (correctly bypassed: no identity)
np.sum(10x10,axis=0) n/s n/s (correctly bypassed: axis-reduction)
np.sin(array_20) 400 ns 396 ns (≈noise, control)
Geometric mean (ref) 1.25x faster

np.array_equal and np.allclose benefit indirectly: both internally call .all() on a contiguous boolean array.

Benchmark script
"""Benchmark script for the contiguous-reduction fast path PR."""
import pyperf
import numpy as np

runner = pyperf.Runner()

# Direct fast-path targets (axis=None reductions, contiguous, identity op)
a4 = np.ones(4)
a20 = np.ones(20)
a100 = np.ones(100)
runner.bench_func('np.sum(array_4)', np.sum, a4)
runner.bench_func('np.sum(array_20)', np.sum, a20)
runner.bench_func('np.sum(array_100)', np.sum, a100)
runner.bench_func('np.prod(array_20)', np.prod, a20)

# 2D, full reduction (still contiguous → fast path)
a10x10 = np.ones((10, 10))
runner.bench_func('np.sum(10x10)', np.sum, a10x10)
runner.bench_func('np.sum(2x2)', np.sum, np.ones((2, 2)))

# Boolean reductions
abool20 = np.ones(20, dtype=bool)
runner.bench_func('np.any(bool_20)', np.any, abool20)
runner.bench_func('np.all(bool_20)', np.all, abool20)

# Indirect beneficiaries (numpy functions that internally do .all())
ai = np.arange(20, dtype=np.float64)
bi = ai.copy()
runner.bench_func('np.array_equal(a,b)', np.array_equal, ai, bi)
runner.bench_func('np.allclose(a,b)', np.allclose, ai, bi)

# Cases that should NOT take the fast path (sanity / regression watch)
runner.bench_func('np.max(array_20)', np.max, a20)
runner.bench_func('np.sum(10x10,axis=0)', np.sum, a10x10, 0)

# Pure baseline (ufunc, not a reduction → not affected by this PR)
runner.bench_func('np.sin(array_20)', np.sin, a20)

AI Disclosure

Claude code was used to identify performance bottlenecks for the reductions. Improvement of the general case is also possible, but requires many changes for a much smaller gain. The fast path was written by Claude and manually refined.

Convert the function to use a single ``cleanup:`` exit point that
decrefs the descriptors and returns ``result``.  No behaviour change.
This makes the function easy to extend with extra paths (e.g. fast
paths for special inputs) without duplicating the cleanup code.
For full reductions (``axis=None``) on contiguous, aligned, non-object
arrays where the matching dtype matches the input dtype and the
operation has an identity, call the strided reduce loop directly on the
input buffer.  This bypasses ``NpyIter`` and ``PyUFunc_ReduceWrapper``
entirely.

Speeds up ``np.sum``, ``np.prod``, ``np.any``, ``np.all`` and similar
reductions on small/medium arrays by ~1.3x; ``np.array_equal`` and
``np.allclose`` benefit indirectly because they call ``.all()``
internally on a comparison's contiguous boolean output.

The reduction loop writes into the result array's data buffer
directly, so no extra ``memcpy`` is needed.  Any unmet condition or
runtime failure within the fast path falls through to the existing
slow path.
@ngoldbaum ngoldbaum added this to the 2.5.0 Release milestone Apr 21, 2026
@ngoldbaum ngoldbaum added 56 - Needs Release Note. Needs an entry in doc/release/upcoming_changes 01 - Enhancement labels Apr 21, 2026
@ngoldbaum

Copy link
Copy Markdown
Member

Do the existing benchmarks in the benchmark suite cover the cases in the benchmark you included in the description?

This needs a release note to be mergeable.

@mhvk would you mind taking a look at the C implementation for the new fast path? Please let me know if you'd prefer I don't ping you like this on AI-generated PRs.

@eendebakpt

eendebakpt commented Apr 21, 2026

Copy link
Copy Markdown
Contributor Author

Do the existing benchmarks in the benchmark suite cover the cases in the benchmark you included in the description?

Yes, they are covered by bench_reduce. The PR targets small arrays specifically, so I added a commit to extend with a few more small arrays cases. Output of ASV is in the details below.

Details
All benchmarks:

| Change   | Before [12e16ab3] <main>   | After [45105175] <reduction_contiguous_fast_path>   |   Ratio | Benchmark (Parameter)                                       |
|----------|----------------------------|-----------------------------------------------------|---------|-------------------------------------------------------------|
|          | 7.17±0.2ms                 | 7.12±0.1ms                                          |    0.99 | bench_reduce.AddReduce.time_axis_0                          |
|          | 6.07±0.1ms                 | 5.82±0.1ms                                          |    0.96 | bench_reduce.AddReduce.time_axis_1                          |
|          | 408±10μs                   | 389±1μs                                             |    0.95 | bench_reduce.AddReduceSeparate.time_reduce(0, 'complex128') |
|          | 186±6μs                    | 184±0.7μs                                           |    0.99 | bench_reduce.AddReduceSeparate.time_reduce(0, 'complex64')  |
|          | 3.50±0ms                   | 3.50±0ms                                            |    1    | bench_reduce.AddReduceSeparate.time_reduce(0, 'float16')    |
|          | 106±2μs                    | 104±3μs                                             |    0.98 | bench_reduce.AddReduceSeparate.time_reduce(0, 'float32')    |
| -        | 189±1μs                    | 165±0.4μs                                           |    0.87 | bench_reduce.AddReduceSeparate.time_reduce(0, 'float64')    |
|          | 352±0.09μs                 | 352±0.2μs                                           |    1    | bench_reduce.AddReduceSeparate.time_reduce(0, 'int16')      |
|          | 354±6μs                    | 336±0.8μs                                           |    0.95 | bench_reduce.AddReduceSeparate.time_reduce(0, 'int32')      |
|          | 225±1μs                    | 213±1μs                                             |    0.95 | bench_reduce.AddReduceSeparate.time_reduce(0, 'int64')      |
|          | 450±8μs                    | 446±5μs                                             |    0.99 | bench_reduce.AddReduceSeparate.time_reduce(1, 'complex128') |
|          | 340±0.6μs                  | 340±0.2μs                                           |    1    | bench_reduce.AddReduceSeparate.time_reduce(1, 'complex64')  |
|          | 2.36±0ms                   | 2.36±0ms                                            |    1    | bench_reduce.AddReduceSeparate.time_reduce(1, 'float16')    |
|          | 178±0.6μs                  | 178±0.03μs                                          |    1    | bench_reduce.AddReduceSeparate.time_reduce(1, 'float32')    |
|          | 200±5μs                    | 196±5μs                                             |    0.98 | bench_reduce.AddReduceSeparate.time_reduce(1, 'float64')    |
|          | 291±0.4μs                  | 290±0.3μs                                           |    1    | bench_reduce.AddReduceSeparate.time_reduce(1, 'int16')      |
|          | 270±1μs                    | 269±2μs                                             |    1    | bench_reduce.AddReduceSeparate.time_reduce(1, 'int32')      |
|          | 185±2μs                    | 175±0.9μs                                           |    0.95 | bench_reduce.AddReduceSeparate.time_reduce(1, 'int64')      |
| -        | 781±3ns                    | 414±0.5ns                                           |    0.53 | bench_reduce.AnyAll.time_all_fast                           |
| -        | 2.67±0.1μs                 | 2.24±0μs                                            |    0.84 | bench_reduce.AnyAll.time_all_slow                           |
| -        | 790±1ns                    | 413±0.9ns                                           |    0.52 | bench_reduce.AnyAll.time_any_fast                           |
| -        | 2.51±0.1μs                 | 2.14±0.1μs                                          |    0.85 | bench_reduce.AnyAll.time_any_slow                           |
|          | 3.36±0.5μs                 | 3.25±0.5μs                                          |    0.97 | bench_reduce.ArgMax.time_argmax(<class 'bool'>)             |
|          | 23.3±0.2μs                 | 23.3±0.2μs                                          |    1    | bench_reduce.ArgMax.time_argmax(<class 'numpy.float32'>)    |
|          | 46.0±0.7μs                 | 46.3±1μs                                            |    1.01 | bench_reduce.ArgMax.time_argmax(<class 'numpy.float64'>)    |
|          | 9.02±0.4μs                 | 9.03±0.3μs                                          |    1    | bench_reduce.ArgMax.time_argmax(<class 'numpy.int16'>)      |
|          | 17.0±0.5μs                 | 17.1±0.6μs                                          |    1    | bench_reduce.ArgMax.time_argmax(<class 'numpy.int32'>)      |
|          | 44.3±0.4μs                 | 44.2±0.5μs                                          |    1    | bench_reduce.ArgMax.time_argmax(<class 'numpy.int64'>)      |
|          | 5.74±0.2μs                 | 5.47±0.2μs                                          |    0.95 | bench_reduce.ArgMax.time_argmax(<class 'numpy.int8'>)       |
|          | 12.8±0.3μs                 | 12.9±0.3μs                                          |    1    | bench_reduce.ArgMax.time_argmax(<class 'numpy.uint16'>)     |
|          | 24.6±0.5μs                 | 24.5±0.5μs                                          |    1    | bench_reduce.ArgMax.time_argmax(<class 'numpy.uint32'>)     |
|          | 49.4±0.7μs                 | 49.4±0.7μs                                          |    1    | bench_reduce.ArgMax.time_argmax(<class 'numpy.uint64'>)     |
|          | 7.33±0.2μs                 | 7.46±0.2μs                                          |    1.02 | bench_reduce.ArgMax.time_argmax(<class 'numpy.uint8'>)      |
|          | 3.19±0.03μs                | 3.22±0.01μs                                         |    1.01 | bench_reduce.ArgMin.time_argmin(<class 'bool'>)             |
|          | 23.5±0.3μs                 | 23.5±0.2μs                                          |    1    | bench_reduce.ArgMin.time_argmin(<class 'numpy.float32'>)    |
|          | 46.6±0.6μs                 | 46.7±2μs                                            |    1    | bench_reduce.ArgMin.time_argmin(<class 'numpy.float64'>)    |
|          | 9.16±0.3μs                 | 9.03±0.3μs                                          |    0.99 | bench_reduce.ArgMin.time_argmin(<class 'numpy.int16'>)      |
|          | 17.3±0.5μs                 | 17.3±0.6μs                                          |    1    | bench_reduce.ArgMin.time_argmin(<class 'numpy.int32'>)      |
|          | 42.5±0.6μs                 | 42.6±0.8μs                                          |    1    | bench_reduce.ArgMin.time_argmin(<class 'numpy.int64'>)      |
|          | 5.52±0.2μs                 | 5.38±0.2μs                                          |    0.98 | bench_reduce.ArgMin.time_argmin(<class 'numpy.int8'>)       |
|          | 14.0±0.8μs                 | 12.8±0.3μs                                          |    0.92 | bench_reduce.ArgMin.time_argmin(<class 'numpy.uint16'>)     |
|          | 24.9±0.6μs                 | 24.8±0.6μs                                          |    1    | bench_reduce.ArgMin.time_argmin(<class 'numpy.uint32'>)     |
|          | 50.0±1μs                   | 50.0±1μs                                            |    1    | bench_reduce.ArgMin.time_argmin(<class 'numpy.uint64'>)     |
|          | 7.77±0.5μs                 | 7.51±0.2μs                                          |    0.97 | bench_reduce.ArgMin.time_argmin(<class 'numpy.uint8'>)      |
|          | 2.25±0.01μs                | 2.23±0.01μs                                         |    0.99 | bench_reduce.FMinMax.time_max(<class 'numpy.float32'>)      |
|          | 3.48±0.02μs                | 3.48±0.02μs                                         |    1    | bench_reduce.FMinMax.time_max(<class 'numpy.float64'>)      |
|          | 2.22±0.01μs                | 2.22±0.01μs                                         |    1    | bench_reduce.FMinMax.time_min(<class 'numpy.float32'>)      |
|          | 3.50±0.02μs                | 3.68±0.2μs                                          |    1.05 | bench_reduce.FMinMax.time_min(<class 'numpy.float64'>)      |
| -        | 1.40±0μs                   | 1.03±0.02μs                                         |    0.73 | bench_reduce.SmallReduction.time_any(100)                   |
| -        | 1.40±0.01μs                | 1.02±0.02μs                                         |    0.73 | bench_reduce.SmallReduction.time_any(4)                     |
|          | 1.56±0.01μs                | 1.60±0.01μs                                         |    1.02 | bench_reduce.SmallReduction.time_max(100)                   |
|          | 1.56±0.01μs                | 1.60±0.01μs                                         |    1.02 | bench_reduce.SmallReduction.time_max(4)                     |
| -        | 1.54±0.01μs                | 1.19±0.02μs                                         |    0.77 | bench_reduce.SmallReduction.time_sum(100)                   |
| -        | 1.56±0μs                   | 1.20±0.01μs                                         |    0.77 | bench_reduce.SmallReduction.time_sum(4)                     |
|          | 1.60±0.01μs                | 1.60±0.02μs                                         |    1    | bench_reduce.SmallReduction2D.time_sum_axis_1               |
|          | 1.55±0.02μs                | 1.57±0.04μs                                         |    1.01 | bench_reduce.StatsReductions.time_max('bool_')              |
|          | 2.12±0.01μs                | 2.17±0.02μs                                         |    1.02 | bench_reduce.StatsReductions.time_max('complex64')          |
|          | 1.57±0.01μs                | 1.59±0.03μs                                         |    1.02 | bench_reduce.StatsReductions.time_max('float32')            |
|          | 1.58±0.01μs                | 1.61±0.04μs                                         |    1.01 | bench_reduce.StatsReductions.time_max('float64')            |
|          | 1.59±0.02μs                | 1.64±0.02μs                                         |    1.04 | bench_reduce.StatsReductions.time_max('int64')              |
|          | 1.59±0.01μs                | 1.63±0.02μs                                         |    1.03 | bench_reduce.StatsReductions.time_max('uint64')             |
|          | 2.47±0.01μs                | 2.57±0.06μs                                         |    1.04 | bench_reduce.StatsReductions.time_mean('bool_')             |
|          | 3.70±0.02μs                | 3.38±0.04μs                                         |    0.91 | bench_reduce.StatsReductions.time_mean('complex64')         |
| -        | 3.68±0.02μs                | 3.29±0.04μs                                         |    0.89 | bench_reduce.StatsReductions.time_mean('float32')           |
| -        | 2.21±0.01μs                | 1.88±0.02μs                                         |    0.85 | bench_reduce.StatsReductions.time_mean('float64')           |
|          | 2.53±0.02μs                | 2.57±0.06μs                                         |    1.01 | bench_reduce.StatsReductions.time_mean('int64')             |
|          | 2.56±0.02μs                | 2.65±0.04μs                                         |    1.03 | bench_reduce.StatsReductions.time_mean('uint64')            |
|          | 1.55±0.01μs                | 1.56±0.03μs                                         |    1.01 | bench_reduce.StatsReductions.time_min('bool_')              |
|          | 2.12±0.01μs                | 2.17±0.03μs                                         |    1.02 | bench_reduce.StatsReductions.time_min('complex64')          |
|          | 1.58±0.01μs                | 1.60±0.03μs                                         |    1.02 | bench_reduce.StatsReductions.time_min('float32')            |
|          | 1.57±0.01μs                | 1.61±0.01μs                                         |    1.02 | bench_reduce.StatsReductions.time_min('float64')            |
|          | 1.57±0.01μs                | 1.61±0.04μs                                         |    1.03 | bench_reduce.StatsReductions.time_min('int64')              |
|          | 1.58±0.01μs                | 1.60±0.03μs                                         |    1.01 | bench_reduce.StatsReductions.time_min('uint64')             |
|          | 1.77±0.01μs                | 1.79±0.01μs                                         |    1.01 | bench_reduce.StatsReductions.time_prod('bool_')             |
| -        | 2.44±0μs                   | 2.10±0.02μs                                         |    0.86 | bench_reduce.StatsReductions.time_prod('complex64')         |
| -        | 1.68±0μs                   | 1.35±0.02μs                                         |    0.8  | bench_reduce.StatsReductions.time_prod('float32')           |
| -        | 1.67±0.02μs                | 1.35±0.02μs                                         |    0.81 | bench_reduce.StatsReductions.time_prod('float64')           |
| -        | 1.59±0.01μs                | 1.25±0.02μs                                         |    0.78 | bench_reduce.StatsReductions.time_prod('int64')             |
| -        | 1.63±0μs                   | 1.25±0.02μs                                         |    0.77 | bench_reduce.StatsReductions.time_prod('uint64')            |
|          | 8.04±0.04μs                | 7.70±0.07μs                                         |    0.96 | bench_reduce.StatsReductions.time_std('bool_')              |
|          | 10.4±0.01μs                | 10.4±0.1μs                                          |    1    | bench_reduce.StatsReductions.time_std('complex64')          |
|          | 8.61±0.03μs                | 8.31±0.06μs                                         |    0.96 | bench_reduce.StatsReductions.time_std('float32')            |
|          | 6.81±0.01μs                | 6.50±0.05μs                                         |    0.95 | bench_reduce.StatsReductions.time_std('float64')            |
|          | 7.56±0μs                   | 7.23±0.05μs                                         |    0.96 | bench_reduce.StatsReductions.time_std('int64')              |
|          | 7.68±0.02μs                | 7.32±0.1μs                                          |    0.95 | bench_reduce.StatsReductions.time_std('uint64')             |
|          | 7.61±0.01μs                | 7.36±0.08μs                                         |    0.97 | bench_reduce.StatsReductions.time_var('bool_')              |
|          | 9.85±0.01μs                | 9.91±0.1μs                                          |    1.01 | bench_reduce.StatsReductions.time_var('complex64')          |
|          | 8.09±0.01μs                | 7.77±0.09μs                                         |    0.96 | bench_reduce.StatsReductions.time_var('float32')            |
|          | 6.43±0.03μs                | 6.22±0.04μs                                         |    0.97 | bench_reduce.StatsReductions.time_var('float64')            |
|          | 7.19±0.03μs                | 6.90±0.05μs                                         |    0.96 | bench_reduce.StatsReductions.time_var('int64')              |
|          | 7.23±0.03μs                | 7.02±0.05μs                                         |    0.97 | bench_reduce.StatsReductions.time_var('uint64')             |

This needs a release note to be mergeable.

I will add one ( maybe it can be merged later with some of the other performance related PRs).

@mhvk mhvk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the ping. This looks good and the performance increase is quite impressive. My main comment is to try to make it more like we do in regular calls, using a separate try_fast function, for readability.

Comment thread numpy/_core/src/umath/ufunc_object.c Outdated
&& PyArray_DESCR(arr) == descrs[1]
&& ufuncimpl->get_reduction_initial != NULL
&& !PyDataType_REFCHK(descrs[0])) {
npy_intp count = PyArray_SIZE(arr);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about making this a separate function, so the flow is less interrupted? It would then be analogous to try_trivial_single_output_loop in this same file, and can similarly have a nice comment above it. (Of course, the compiler will just put it inline, so this is really for human consumption only.) Maybe only keep in the ->get_reduction_initial part here, as that is part of the if statement at some level.

Comment thread numpy/_core/src/umath/ufunc_object.c Outdated
Move the inline fast path inside ``PyUFunc_Reduce`` into a static helper
``try_reduce_contiguous`` that returns 1/0/-1 (success / not applicable
/ hard error).  The caller now reduces to a single conditional call.

The condition ``PyArray_ISCARRAY_RO(arr)`` is widened to also accept
``PyArray_ISFARRAY_RO(arr)``: for full reductions the loop walks all
elements in memory order, which is correct for any contiguous layout.

For multi-axis reductions, this makes order-of-iteration vary (it
matches storage order rather than NpyIter's order), so an explicit
``NPY_METH_IS_REORDERABLE`` check is added.  This also closes a small
latent gap where the previous fast path silently succeeded on
non-reorderable ops with multi-D C-contiguous input, while the slow
path raises ``ValueError``.
Comment thread numpy/_core/src/umath/ufunc_object.c Outdated
eendebakpt and others added 2 commits April 23, 2026 21:32
Co-authored-by: Nathan Goldbaum <nathan.goldbaum@gmail.com>
@seberg seberg removed the 56 - Needs Release Note. Needs an entry in doc/release/upcoming_changes label Apr 28, 2026

@seberg seberg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, LGTM. One small comment to refine, but doesn't even matter, I was first surprised it's just 1.3x, but any/all being 1.9x makes sense then ;).

It might make sense to also deal with the no-identity version, but probably not in this PR.

Comment thread numpy/_core/src/umath/ufunc_object.c Outdated
if (!has_initial) {
/* No identity available -- fall back to the slow path. */
Py_DECREF(result);
return 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too bad we create/delete an array here, but OK. (I was wondering how annoying it is to just handle this, but maybe not, it does require copying the first element from the array over, which might be a bit ugly.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added cases without an identify to the PR. Added code is limited, and it avoids creating an array for nothing.

/* Allocate the 0-d result first so the loop can write into it. */
Py_INCREF(descrs[0]);
PyArrayObject *result = (PyArrayObject *)PyArray_NewFromDescr(
&PyArray_Type, descrs[0], 0, NULL, NULL, NULL, 0, NULL);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This path is so close to being able to just return a scalar directly :) (if it worked out with the array-wrap)... But let's not think about it here.

Comment thread numpy/_core/src/umath/ufunc_object.c Outdated
PyArrayMethodObject *ufuncimpl = context->method;
if (!(out == NULL && wheremask == NULL && initial == NULL && keepdims == 0
&& naxes == ndim
&& (PyArray_ISCARRAY_RO(arr) || PyArray_ISFARRAY_RO(arr))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could actually use PyArray_TRIVIALLY_ITERABLE I think. Then use the actual strides below (because that lets 1-D arrays always pass).
(Doesn't include ALIGNED check though.)

Comment thread numpy/_core/src/umath/ufunc_object.c Outdated
res = -1;
}
if (res == 0 && needs_fperr) {
res = _check_ufunc_fperr(errormask, ufunc_name);

@seberg seberg Apr 28, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
res = _check_ufunc_fperr(errormask, ufunc_name);
res = _check_ufunc_fperr(errormask, "reduce");

Weird that the new changes now hit path that actually tests this...

Other changes look good since you have the REFCHK in there, I think. We could possibly broaden it up eventually, but really no need here.

EDIT: Ah, because these tests are functions without an identity.

EDIT2: Commit, since I think it might be ready then, but feel free to force push it away!

@seberg

seberg commented Apr 28, 2026

Copy link
Copy Markdown
Member

@seberg
seberg merged commit a2d43cb into numpy:main Apr 29, 2026
84 of 86 checks passed
MaanasArora pushed a commit to MaanasArora/numpy that referenced this pull request May 7, 2026
Adds a fast path in PyUFunc_Reduce for the common case of a full reduction (axis=None) over a trivially-iterable, aligned, non-object/reference input where the matching dtype equals the input dtype.

Co-authored-by: Nathan Goldbaum <nathan.goldbaum@gmail.com>
Co-authored-by: Sebastian Berg <sebastianb@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants