Skip to content

Make svector moves and swap non-allocating - #2949

Open
gouarin wants to merge 1 commit into
xtensor-stack:masterfrom
gouarin:fix-svector-move-noexcept
Open

gouarin wants to merge 1 commit into
xtensor-stack:masterfrom
gouarin:fix-svector-move-noexcept

Conversation

@gouarin

@gouarin gouarin commented Oct 29, 2026

Copy link
Copy Markdown
Member

Checklist

  • The title and commit message(s) are descriptive.
  • Small commits made to fix your PR have been squashed to avoid history pollution.
  • Tests have been added for new features or bug fixes.
  • API of new functions and classes are documented (no public API changes).

Description

The svector move constructor, move assignment and swap are noexcept, but they allocate when the vector holds more than N elements. Both copy into a new heap buffer, so a std::bad_alloc calls std::terminate. Moving an xarray with more than four dimensions goes through this path.

Moves now take over the heap buffer of the source, together with its allocator. When the source uses its inline buffer, its elements are moved into the current storage, which always holds at least N elements. If *this owned a heap buffer that gets replaced, it is freed with its own allocator first. Self-move assignment is a no-op. Swapping two svectors of the same type goes through moves and does not allocate.

Behavior change: a moved-from svector is now always empty. Move assignment used to leave the source untouched.

svector.move checks that the heap buffer is transferred and the source is left empty; on master it fails on the buffer address. I also ran heap and inline moves, self-move and swaps with a stateful tracking allocator under AddressSanitizer and LeakSanitizer: each buffer was freed by the allocator that allocated it, and nothing leaked.

The svector move constructor and move assignment are noexcept, but the
constructor swapped with an empty vector and the assignment copied the
elements. When the source held more than N elements, both allocated a
new heap buffer, so a std::bad_alloc called std::terminate. Every move of
an xarray with more than four dimensions went through that path. The
noexcept free swap also called the member swap, which can resize.

Moving now takes over the heap buffer of the source, together with its
allocator. When the source uses its inline buffer, the elements are moved
into the current storage, which always has room for N elements. The
moved-from vector is left empty. Swapping two svectors of the same type
goes through three moves and does not allocate either.

The noexcept conditions now use is_nothrow_move_assignable, which matches
how the elements are transferred.
@codspeed

codspeed Bot commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will degrade performance by 18.92%

1 warning

⚠️ Unable to measure the execution of 1 benchmark

This benchmark measured no execution time, so its result is not comparable and counts as unchanged. This usually happens when the compiler removes the code under test.

Prevent compiler optimizations

See affected benchmarks

❌ 1 regressed benchmark
✅ 254 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
❌ xshape_access[xt\:\:svector<std\:\:size_t, 4>] 125 ns 154.2 ns -18.92%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing gouarin:fix-svector-move-noexcept (c92fe75) with master (57f3d07)

Open in CodSpeed

@gouarin gouarin mentioned this pull request Oct 29, 2026
3 of 4 tasks

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant