Skip to content

Optimize is_permutation for vector<bool> - #6148

Open
Alex Guteniev (AlexGuteniev) wants to merge 23 commits into
microsoft:mainfrom
AlexGuteniev:order-mutant-vector-bool
Open

Optimize is_permutation for vector<bool>#6148
Alex Guteniev (AlexGuteniev) wants to merge 23 commits into
microsoft:mainfrom
AlexGuteniev:order-mutant-vector-bool

Conversation

@AlexGuteniev

@AlexGuteniev Alex Guteniev (AlexGuteniev) commented Mar 7, 2026

Copy link
Copy Markdown
Contributor

I was looking into bit tricks for next_permutation / prev_permutation.
Seems like that there will be no impressive results, as these operations perform already at ~15 ns order of magnitude.
I may further look into these though.

But let's go for the easiest optimization here that gains 100x and more!

The optimized algorithm does mismatch, and then counts true values in each of the remaining ranges.

Mismatch

The mismatch part serves to save the equality case optimization that is implied by the standard, asking for N**2 comparisons generally, but just N if the ranges are equal. If none of the ranges is vector<bool> then we have these cases:

  • Both contiguous ranges. mismatch turns to vector mismatch, and count to vector count
    • The reason to use the algorithm, and not inline implementation is specifically vectorization
    • For equal case vectorized mismatch is slightly faster
    • Inequal case is likely to be slightly slower, due to wasted mismatch call that will only likely misalign the input for the count call due to advancing few elements
  • Both noncontiguous ranges. With mismatch the number of comparison is twice smaller for equal cases.
  • One contiguous range, one noncontiguous range. Similar to the case above, except that one of counts is vectorized, so mismatch is not that much faster

So overall mismatch should be there.

One vector<bool> or both of them flip it:

  • mismatch for vector<bool> is not optimized currently
  • If we optimize it, we are not likely to optimize it for misaligned cases
  • Even if misaligned cases are optimized, they will not perform as well as normal ones
  • and there are mixed cases, which we even less likely to optimize
  • in contrast, count is optimized equally well for aligned and misaligned cases

Benchmark results

Interim version is without the initial mismatch. Its timings are not used is speedup calculation.

Array:

Benchmark Before Interim After Speedup
perm_arr_check<equality::eq, args::three>/64 25.8 ns 11.3 ns 2.59 ns 9.96
perm_arr_check<equality::eq, args::three>/4096 994 ns 104 ns 65.6 ns 15.15
perm_arr_check<equality::eq, args::three>/65536 15380 ns 1985 ns 1343 ns 11.45
perm_arr_check<equality::eq, args::four>/64 24.8 ns 11.5 ns 3.51 ns 7.07
perm_arr_check<equality::eq, args::four>/4096 981 ns 100 ns 63.7 ns 15.40
perm_arr_check<equality::eq, args::four>/65536 15451 ns 1915 ns 1341 ns 11.52
perm_arr_check<equality::neq, args::three>/64 20.3 ns 11.4 ns 11.5 ns 1.77
perm_arr_check<equality::neq, args::three>/4096 835 ns 105 ns 78.2 ns 10.68
perm_arr_check<equality::neq, args::three>/65536 13984 ns 1777 ns 1573 ns 8.89
perm_arr_check<equality::neq, args::four>/64 21.4 ns 11.2 ns 12.2 ns 1.75
perm_arr_check<equality::neq, args::four>/4096 848 ns 103 ns 84.1 ns 10.08
perm_arr_check<equality::neq, args::four>/65536 14167 ns 1851 ns 1567 ns 9.04

vector<bool>, Interim column is irrelevant, it does not show any data different from After.

Benchmark Before After Speedup
perm_vbool_check<equality::eq, args::three>/64 105 ns 10.9 ns 9.63
perm_vbool_check<equality::eq, args::three>/4096 6668 ns 80.0 ns 83.35
perm_vbool_check<equality::eq, args::three>/65536 104979 ns 1092 ns 96.13
perm_vbool_check<equality::eq, args::four>/64 115 ns 10.6 ns 10.85
perm_vbool_check<equality::eq, args::four>/4096 6523 ns 80.2 ns 81.33
perm_vbool_check<equality::eq, args::four>/65536 104817 ns 1007 ns 104.0
perm_vbool_check<equality::neq, args::three>/64 94.5 ns 10.3 ns 9.17
perm_vbool_check<equality::neq, args::three>/4096 6443 ns 82.0 ns 78.57
perm_vbool_check<equality::neq, args::three>/65536 100706 ns 1019 ns 98.83
perm_vbool_check<equality::neq, args::four>/64 101 ns 9.65 ns 10.47
perm_vbool_check<equality::neq, args::four>/4096 6279 ns 81.8 ns 76.76
perm_vbool_check<equality::neq, args::four>/65536 101654 ns 1033 ns 98.41

@StephanTLavavej

Copy link
Copy Markdown
Member

I believe this could be generalized further. For any ranges where the value types are bool and the predicate is equality, you should be able to count true. This could even benefit mixed comparisons of vector<bool> versus array<bool, N>, for example.

@AlexGuteniev

Copy link
Copy Markdown
Contributor Author

Generalized, benchmark results are about the same

Copilot AI 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.

Pull request overview

This PR optimizes std::is_permutation when operating on bool ranges (notably vector<bool> iterators) by replacing the general-purpose O(N²) match-counting approach with a linear-time mismatch + count(true) strategy.

Changes:

  • Added an internal helper (_Is_permutation_of_bool) and fast-paths in is_permutation overloads when the predicate is an equality predicate and both ranges are bool.
  • Added tests validating is_permutation behavior for vector<bool> and mixed vector<bool>/raw-bool[] iterator combinations.
  • Added a new microbenchmark to measure is_permutation performance on vector<bool> and on raw bool[].
Show a summary per file
File Description
stl/inc/algorithm Introduces bool-specific is_permutation fast-paths using count(true) (and mismatch when neither iterator is vector<bool>).
tests/std/tests/GH_000625_vector_bool_optimization/test.cpp Adds constexpr/runtime coverage for is_permutation with vector<bool> and bool[].
benchmarks/src/vector_bool_permute.cpp Adds benchmarks for is_permutation on vector<bool> and bool[].
benchmarks/CMakeLists.txt Registers the new vector_bool_permute benchmark target.

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment thread benchmarks/src/vector_bool_permute.cpp Outdated
Copilot AI review requested due to automatic review settings May 30, 2026 16:43

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

Comment thread stl/inc/algorithm
Comment on lines +1273 to +1274
template <class _FwdIt1, class _FwdIt2>
_NODISCARD _CONSTEXPR20 bool _Is_permutation_of_bool(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2) {

@AlexGuteniev Alex Guteniev (AlexGuteniev) May 30, 2026

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 think this one is premature. I don't attempt to handle std::ranges::is_permutation yet, so there's no distinct sentinel type. When we handle it, we'll see how it should be done.

Comment thread stl/inc/algorithm Outdated
Comment on lines +1296 to +1299
if constexpr (is_same_v<_Iter_value_t<decltype(_UFirst1)>, bool>
&& is_same_v<_Iter_value_t<decltype(_UFirst2)>, bool> //
&& _Is_ranges_random_iter_v<decltype(_UFirst1)> //
&& _Is_ranges_random_iter_v<decltype(_UFirst2)>

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.

The 4-arg version checks the wrapped iterator instead. The check it pre-existing.
Maybe I need to change it to check the unwrapped iterators too though?

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.

Checking the unwrapped iterators.

Comment thread stl/inc/algorithm
Comment on lines +1275 to +1276
if constexpr (!_Is_vb_iterator<_FwdIt1> && !_Is_vb_iterator<_FwdIt2>) {
auto _Pair = _STD mismatch(_First1, _Last1, _First2);

@AlexGuteniev Alex Guteniev (AlexGuteniev) May 30, 2026

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.

No.

This is intentional optimization decision. It is explained in the PR description.

mismatch is good for non-vb iterators (either vectorized, or better than count), but for just one vb iterator it flips as count is SWAR with popcount and mismatch is individual bit matching.

Copilot AI review requested due to automatic review settings July 26, 2026 14:58

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread stl/inc/algorithm
}
}

return _STD count(_First1, _Last1, true) == _STD count(_First2, _Last2, true);

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.

I believe we should guard this optimization with _Is_vb_iterator, as we've done with others, since reasoning about wacky iterators whose value type is bool but reference type is somebody else's proxy, is too difficult.

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.

(From Discord:) Ok, I suggest the following: optimize for iterators that are either _Is_vb_iterator, or actually have reference types that are bool after remove_cv_ref. That gets you any mix of real bool and vector<bool>, but never wacky bool-oids.

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.

  • Added check for _Iter_ref_t
  • Added a test that breaks without the _Iter_ref_t change
  • Ran benchmark to make sure the optimization is still in

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.

Copilot could you please verify the fix?

@StephanTLavavej Stephan T. Lavavej (StephanTLavavej) moved this from Initial Review to Work In Progress in STL Code Reviews Jul 28, 2026
Copilot AI review requested due to automatic review settings August 11, 2026 09:14

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Suppressed comments (1)

tests/std/tests/GH_000625_vector_bool_optimization/test.cpp:1589

  • vbool_like_iterator advertises random_access_iterator_tag but omits required operations such as postfix ++/--, n + iterator, and operator[]. It therefore does not satisfy the LegacyForwardIterator/LegacyRandomAccessIterator requirements of the algorithms exercised below, so the test invokes them outside their contract and does not validate the proxy-reference guard with a conforming iterator. Please complete the random-access iterator interface (or use the repository's iterator test support).
    struct vbool_like_iterator {
        using iterator_category = random_access_iterator_tag;
        using value_type        = bool;
        using difference_type   = ptrdiff_t;
        using pointer           = bool*;
        using reference         = vbool_like_reference;

Comment on lines +1573 to +1576
// proxy-to-proxy comparison; the evil part, not present in vector<bool> proxies
bool operator==(const vbool_like_reference&) const {
return true; // all proxies are equal
}

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.

Would it make more sense to just make this overload deleted, which should ensure that we don't use it even in non-executed branches? Ditto below.

Suggested change
// proxy-to-proxy comparison; the evil part, not present in vector<bool> proxies
bool operator==(const vbool_like_reference&) const {
return true; // all proxies are equal
}
// proxy-to-proxy comparison; the evil part, not present in vector<bool> proxies
bool operator==(const vbool_like_reference&) const = delete;

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.

No. These are used.

The point of this problem is that is_permutation/mismatch/equal will use these operators instead of implicit value conversion and the results may differ. The test deliberately makes equal/is_permutation true for any inputs of equal lengths.

Comment thread benchmarks/src/vector_bool_permute.cpp Outdated
@StephanTLavavej Stephan T. Lavavej (StephanTLavavej) moved this from Work In Progress to Initial Review in STL Code Reviews Aug 15, 2026
Copilot AI review requested due to automatic review settings August 29, 2026 11:35

Copilot AI 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.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings September 8, 2026 06:05

Copilot AI 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.

🟡 Changes recommended

The invalid postfix iterator implementations must be corrected before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

tests/std/tests/GH_000625_vector_bool_optimization/test.cpp:1615

  • This postfix decrement has the same invalid-return behavior: it returns an iterator with an indeterminate ptr, not the position before decrementing. Copy *this before updating ptr.
    vbool_like_iterator operator--(int) {
        vbool_like_iterator result;
        --ptr;
        return result;

tests/std/tests/GH_000625_vector_bool_optimization/test.cpp:1638

  • A random-access iterator's i[d] must be equivalent to *(i + d), but this implementation subtracts d. Positive indexes therefore access the wrong element and can move before the range.
    vbool_like_reference operator[](const ptrdiff_t d) const {
        return {ptr - d};
    }
  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread tests/std/tests/GH_000625_vector_bool_optimization/test.cpp
Copilot AI review requested due to automatic review settings September 8, 2026 06:17

Copilot AI 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.

🔵 Needs a closer look

The regression-test iterator has incorrect indexing semantics that must be fixed before approval.

Review details

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

tests/std/tests/GH_000625_vector_bool_optimization/test.cpp:1638

  • operator[] moves backward for a positive index, contradicting both operator+ and the random-access iterator requirement that i[n] be equivalent to *(i + n). This makes the regression-test iterator malformed and can hide failures in code paths that use indexing.
  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Copilot AI review requested due to automatic review settings September 8, 2026 06:25

Copilot AI 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.

🟢 Approval recommended

The implementation, tests, and benchmarks were fully reviewed with no unresolved issues.

Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

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

Labels

performance Must go faster

Projects

Status: Initial Review

Development

Successfully merging this pull request may close these issues.

4 participants