Skip to content

PERF: avoid an unnecessary copy in np.take with out=#31940

Open
Ijtihed wants to merge 1 commit into
numpy:mainfrom
Ijtihed:perf/take-out-no-copy
Open

PERF: avoid an unnecessary copy in np.take with out=#31940
Ijtihed wants to merge 1 commit into
numpy:mainfrom
Ijtihed:perf/take-out-no-copy

Conversation

@Ijtihed

@Ijtihed Ijtihed commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

PR summary

Fixes #28636.

np.take(a, indices, out=out) in the default mode="raise" was slower than the same call without out. PyArray_TakeFrom forced a defensive ENSURECOPY of out and wrote through a temporary that was copied back on success and only so out stayed unchanged if an out-of-bounds index raised

this removes that copy so in raise mode the result is written directly into out just like mode="wrap"/"clip" already do. The overlap-with-self copy (arrays_overlap) was kept so aliasing stays correct.

there IS a behavior change on an out-of-bounds index since out may now be partially written before IndexError is raised (previously it was left unchanged). take still raises. This proooobably warrants a release note and I'll write it after review

Before/after out=

  • 1-D, 85 -> 9350 elems: 7.66us -> 4.90us (1.56x)
  • 1-D, 1000 -> 100000 elems: 99.5us -> 47.3us (2.11x)
  • axis 0, (200, 64) -> (4000, 64): 214us -> 64.6us (3.31x)

The no-out path is unchanged and the speedup grows with output size (since the removed copy is O(n))

AI Disclosure

Used AI to double check my code + find any edge cases I might've missed :)

@Ijtihed

Ijtihed commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@mattip since you mentioned this I thought you might be interested :)

@ikrommyd ikrommyd 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.

If the consensus is to just let out be modified, that is like basic guarantee in terms of c++ exception safety. I'd be tempted to add an additional message to the raised error in that case. Like if the error is raised and out= is set, you also say in the error message that out may be modified. cc @seberg @mattip what do you think? It can be done using by getting the exception that would be raised which is IndexError: index 4 is out of bounds and adding a note to it using the add_note method they have (numpy is already 3.12+). Something like

if (npy_fasttake(
        dest, src, indices_data, n, m, max_item, nelem, chunk,
        clipmode, itemsize, needs_refcounting, src_descr, dst_descr,
        axis) < 0) {
    if (out != NULL && obj == out) {
        PyObject *exc = PyErr_GetRaisedException();
        if (exc != NULL) {
            PyObject *res = PyObject_CallMethod(exc, "add_note", "s",
                "the output array `out` may have been partially modified");
            if (res == NULL) {
                PyErr_Clear();
            }
            Py_XDECREF(res);
            PyErr_SetRaisedException(exc);
        }
    }
    goto fail;
}

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.

That might be a little bit of an excessive testing for this kind of tiny change IMO.

Comment thread numpy/_core/tests/test_multiarray.py Outdated
@seberg

seberg commented Jul 10, 2026

Copy link
Copy Markdown
Member

I like the add-note idea, although it applies to many operations (i.e. if we do that, I think it should probably be a central helper and maybe added to some other places).

FWIW, it seems OK to me, although I don't care too much either way (mode=... is an existing path to get the no-copy in practice).
But it does need a release note IMO as it relaxes an intentional guarantee.

@Ijtihed Ijtihed force-pushed the perf/take-out-no-copy branch from e87e7ba to d27656a Compare July 10, 2026 07:50
@Ijtihed

Ijtihed commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

thanks both! I added a compatibility release note documenting that out can now be partially modified if an out of bounds index raises (the relaxed guarantee) then dropped the refcount-asserting test and trimmed the rest down to the essentials so there's no getrefcount asserts left. leak coverage can just be left to the sanitized CI

@ikrommyd @seberg I left add_note out for now since i agree it makes more sense as a central helper across the ops that can partially write out :)

@Ijtihed

Ijtihed commented Jul 10, 2026

Copy link
Copy Markdown
Contributor Author

I like the add-note idea, although it applies to many operations (i.e. if we do that, I think it should probably be a central helper and maybe added to some other places).

I'd also be happy to open a pr for this too if you think it makes sense

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.

take slower when out is provided

4 participants