Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions benchmarks/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,5 @@ add_benchmark(vector_bool_copy_n src/vector_bool_copy_n.cpp)
add_benchmark(vector_bool_count src/vector_bool_count.cpp)
add_benchmark(vector_bool_meow_of src/vector_bool_meow_of.cpp)
add_benchmark(vector_bool_move src/vector_bool_move.cpp)
add_benchmark(vector_bool_permute src/vector_bool_permute.cpp)
add_benchmark(vector_bool_transform src/vector_bool_transform.cpp)
79 changes: 79 additions & 0 deletions benchmarks/src/vector_bool_permute.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <benchmark/benchmark.h>
//
#include <algorithm>
#include <cstddef>
#include <random>
#include <vector>

#include "utility.hpp"

using namespace std;

enum class equality { eq, neq };
enum class args { three, four };


template <equality Eq, args Args>
void perm_arr_check(benchmark::State& state) {
const auto size = static_cast<size_t>(state.range(0));
vector<bool> v1 = random_vector<bool>(size);
vector<bool> v2 = v1;
if constexpr (Eq == equality::neq) {
v2[v2.size() / 2].flip();
}

auto a1 = make_unique<bool[]>(size);
auto a2 = make_unique<bool[]>(size);
copy(v1.begin(), v1.end(), a1.get());
copy(v2.begin(), v2.end(), a2.get());

for (auto _ : state) {
benchmark::DoNotOptimize(a1.get());
benchmark::DoNotOptimize(a2.get());
bool r;
if constexpr (Args == args::three) {
r = is_permutation(a1.get(), a1.get() + size, a2.get());
} else {
r = is_permutation(a1.get(), a1.get() + size, a2.get(), a2.get() + size);
}
benchmark::DoNotOptimize(r);
}
}


template <equality Eq, args Args>
void perm_vbool_check(benchmark::State& state) {
const auto size = static_cast<size_t>(state.range(0));
vector<bool> v1 = random_vector<bool>(size);
vector<bool> v2 = v1;
if constexpr (Eq == equality::neq) {
v2[v2.size() / 2].flip();
}

for (auto _ : state) {
benchmark::DoNotOptimize(v1);
benchmark::DoNotOptimize(v2);
bool r;
if constexpr (Args == args::three) {
r = is_permutation(v1.begin(), v1.end(), v2.begin());
} else {
r = is_permutation(v1.begin(), v1.end(), v2.begin(), v2.end());
}
benchmark::DoNotOptimize(r);
}
}

BENCHMARK(perm_arr_check<equality::eq, args::three>)->RangeMultiplier(64)->Range(64, 64 << 10);
BENCHMARK(perm_arr_check<equality::eq, args::four>)->RangeMultiplier(64)->Range(64, 64 << 10);
BENCHMARK(perm_arr_check<equality::neq, args::three>)->RangeMultiplier(64)->Range(64, 64 << 10);
BENCHMARK(perm_arr_check<equality::neq, args::four>)->RangeMultiplier(64)->Range(64, 64 << 10);

BENCHMARK(perm_vbool_check<equality::eq, args::three>)->RangeMultiplier(64)->Range(64, 64 << 10);
BENCHMARK(perm_vbool_check<equality::eq, args::four>)->RangeMultiplier(64)->Range(64, 64 << 10);
BENCHMARK(perm_vbool_check<equality::neq, args::three>)->RangeMultiplier(64)->Range(64, 64 << 10);
BENCHMARK(perm_vbool_check<equality::neq, args::four>)->RangeMultiplier(64)->Range(64, 64 << 10);

BENCHMARK_MAIN();
43 changes: 42 additions & 1 deletion stl/inc/algorithm
Original file line number Diff line number Diff line change
Expand Up @@ -1300,6 +1300,27 @@ namespace ranges {
} // namespace ranges
#endif // _HAS_CXX20

template <class _It>
constexpr bool _Can_optimize_permutation_of_bool_iter =
_Is_vb_iterator<_It>
|| (is_same_v<_Iter_value_t<_It>, bool> && is_same_v<_Remove_cvref_t<_Iter_ref_t<_It>>, bool>
&& _Is_ranges_random_iter_v<_It>);

template <class _FwdIt1, class _FwdIt2>
_NODISCARD _CONSTEXPR20 bool _Is_permutation_of_bool(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2) {
Comment on lines +1309 to +1310

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

if constexpr (!_Is_vb_iterator<_FwdIt1> && !_Is_vb_iterator<_FwdIt2>) {
auto _Pair = _STD mismatch(_First1, _Last1, _First2);
Comment on lines +1311 to +1312

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

_First1 = _STD move(_Pair.first);
_First2 = _STD move(_Pair.second);

if (_First1 == _Last1) {
return true;
}
}

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?

}

_EXPORT_STD template <class _FwdIt1, class _FwdIt2, class _Pr>
_NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _Pr _Pred) {
// test if [_First1, _Last1) == permuted [_First2, ...)
Expand All @@ -1308,6 +1329,16 @@ _NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _Fw
const auto _ULast1 = _STD _Get_unwrapped(_Last1);
auto _UFirst2 = _STD _Get_unwrapped_n(_First2, _STD _Idl_distance<_FwdIt1>(_UFirst1, _ULast1));

if constexpr (_Can_optimize_permutation_of_bool_iter<decltype(_UFirst1)>
&& _Can_optimize_permutation_of_bool_iter<decltype(_UFirst2)>
&& _Is_any_of_v<_Pr,
#if _HAS_CXX20
_RANGES equal_to,
#endif
equal_to<>, equal_to<bool>>) {
return _STD _Is_permutation_of_bool(_UFirst1, _ULast1, _UFirst2, _UFirst2 + (_ULast1 - _UFirst1));
}

for (;; ++_UFirst1, (void) ++_UFirst2) { // trim matching prefix
if (_UFirst1 == _ULast1) { // everything matched
return true;
Expand Down Expand Up @@ -1340,11 +1371,21 @@ _NODISCARD _CONSTEXPR20 bool is_permutation(
auto _ULast1 = _STD _Get_unwrapped(_Last1);
auto _UFirst2 = _STD _Get_unwrapped(_First2);
auto _ULast2 = _STD _Get_unwrapped(_Last2);
if constexpr (_Is_ranges_random_iter_v<_FwdIt1> && _Is_ranges_random_iter_v<_FwdIt2>) {
if constexpr (_Is_ranges_random_iter_v<decltype(_UFirst1)> && _Is_ranges_random_iter_v<decltype(_UFirst2)>) {
if (_ULast1 - _UFirst1 != _ULast2 - _UFirst2) {
return false;
}

if constexpr (_Can_optimize_permutation_of_bool_iter<decltype(_UFirst1)>
&& _Can_optimize_permutation_of_bool_iter<decltype(_UFirst2)>
&& _Is_any_of_v<_Pr,
#if _HAS_CXX20
_RANGES equal_to,
#endif
equal_to<>, equal_to<bool>>) {
return _STD _Is_permutation_of_bool(_UFirst1, _ULast1, _UFirst2, _ULast2);
}

for (; _UFirst1 != _ULast1; ++_UFirst1, (void) ++_UFirst2) { // trim matching prefix
if (!_Pred(*_UFirst1, *_UFirst2)) {
// found first inequality, check match counts in suffix
Expand Down
174 changes: 174 additions & 0 deletions tests/std/tests/GH_000625_vector_bool_optimization/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1518,6 +1518,177 @@ CONSTEXPR20 bool test_copy_part_2() {
return true;
}

CONSTEXPR20 bool test_is_permutation() {
constexpr bool perm_source_raw[] = {
false, true, false, false, false, true, false, true, true, false, false, true, false, false, true, true};
const vector<bool> perm_source(begin(perm_source_raw), end(perm_source_raw));

// Actually a permutation
constexpr bool perm_raw[] = {
true, false, true, false, false, true, false, false, true, false, true, false, true, false, false, true};
const vector<bool> perm(begin(perm_raw), end(perm_raw));
assert(is_permutation(perm_source.begin(), perm_source.end(), perm.begin()));
assert(is_permutation(perm_source.begin(), perm_source.end(), perm.begin(), perm.end()));
assert(is_permutation(begin(perm_source_raw), end(perm_source_raw), perm.begin()));
assert(is_permutation(begin(perm_source_raw), end(perm_source_raw), perm.begin(), perm.end()));
assert(is_permutation(perm_source.begin(), perm_source.end(), begin(perm_raw)));
assert(is_permutation(perm_source.begin(), perm_source.end(), begin(perm_raw), end(perm_raw)));
assert(is_permutation(begin(perm_source_raw), end(perm_source_raw), begin(perm_raw)));
assert(is_permutation(begin(perm_source_raw), end(perm_source_raw), begin(perm_raw), end(perm_raw)));

// One extra true value
constexpr bool extra_true_raw[] = {
true, false, true, false, false, true, false, false, true, true, true, false, true, false, false, true};
const vector<bool> extra_true(begin(extra_true_raw), end(extra_true_raw));
assert(!is_permutation(perm_source.begin(), perm_source.end(), extra_true.begin()));
assert(!is_permutation(perm_source.begin(), perm_source.end(), extra_true.begin(), extra_true.end()));
assert(!is_permutation(begin(perm_source_raw), end(perm_source_raw), extra_true.begin()));
assert(!is_permutation(begin(perm_source_raw), end(perm_source_raw), extra_true.begin(), extra_true.end()));
assert(!is_permutation(perm_source.begin(), perm_source.end(), begin(extra_true_raw)));
assert(!is_permutation(perm_source.begin(), perm_source.end(), begin(extra_true_raw), end(extra_true_raw)));
assert(!is_permutation(begin(perm_source_raw), end(perm_source_raw), begin(extra_true_raw)));
assert(!is_permutation(begin(perm_source_raw), end(perm_source_raw), begin(extra_true_raw), end(extra_true_raw)));

// One element longer
constexpr bool longer_raw[] = {
true, false, true, false, false, true, false, false, true, false, true, false, true, false, false, true, false};
const vector<bool> longer(begin(longer_raw), end(longer_raw));
assert(!is_permutation(perm_source.begin(), perm_source.end(), longer.begin(), longer.end()));
assert(!is_permutation(begin(perm_source_raw), end(perm_source_raw), longer.begin(), longer.end()));
assert(!is_permutation(perm_source.begin(), perm_source.end(), begin(longer_raw), end(longer_raw)));
assert(!is_permutation(begin(perm_source_raw), end(perm_source_raw), begin(longer_raw), end(longer_raw)));

return true;
}

// vector<bool> like proxy reference class with evil behavior
struct vbool_like_reference {
const bool* ptr;

operator bool() const {
return *ptr;
}

// 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-other-proxy comparison; the evil part, not present in vector<bool> proxies
bool operator==(const vector<bool>::reference&) const {
return true; // all proxies are equal
}
};

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;

const bool* ptr;

vbool_like_reference operator*() const {
return {ptr};
}

vbool_like_iterator& operator++() {
++ptr;
return *this;
}

vbool_like_iterator& operator--() {
--ptr;
return *this;
}

vbool_like_iterator operator++(int) {
const vbool_like_iterator result = *this;
++ptr;
return result;
}

vbool_like_iterator operator--(int) {
const vbool_like_iterator result = *this;
--ptr;
return result;
}

vbool_like_iterator& operator+=(const ptrdiff_t d) {
ptr += d;
return *this;
}

vbool_like_iterator& operator-=(const ptrdiff_t d) {
ptr -= d;
return *this;
}

vbool_like_iterator operator+(const ptrdiff_t d) const {
return {ptr + d};
}

vbool_like_iterator operator-(const ptrdiff_t d) const {
return {ptr - d};
}

vbool_like_reference operator[](const ptrdiff_t d) const {
return {ptr + d};
}

ptrdiff_t operator-(const vbool_like_iterator& other) const {
return ptr - other.ptr;
}

bool operator==(const vbool_like_iterator& other) const {
return ptr == other.ptr;
}

bool operator!=(const vbool_like_iterator& other) const {
return ptr != other.ptr;
}

bool operator<(const vbool_like_iterator& other) const {
return ptr < other.ptr;
}

bool operator<=(const vbool_like_iterator& other) const {
return ptr <= other.ptr;
}

bool operator>(const vbool_like_iterator& other) const {
return ptr > other.ptr;
}

bool operator>=(const vbool_like_iterator& other) const {
return ptr >= other.ptr;
}
};

vbool_like_iterator operator+(const ptrdiff_t d, const vbool_like_iterator& i) {
return {d + i.ptr};
}

void test_is_permutation_vbool_like_iterators() {
bool all_true[] = {true, true, true};
vector<bool> some_true{true, false, true};

const vbool_like_iterator all_true_begin{begin(all_true)};
const vbool_like_iterator all_true_end{end(all_true)};

// different true count
assert(count(all_true_begin, all_true_end, true) == 3);
assert(count(some_true.begin(), some_true.end(), true) == 2);

// but still equivalent
assert(equal(all_true_begin, all_true_end, some_true.begin(), some_true.end()));
assert(mismatch(all_true_begin, all_true_end, some_true.begin(), some_true.end()).first == all_true_end);

// now these are permutations due to equivalence
assert(is_permutation(all_true_begin, all_true_end, some_true.begin(), some_true.end()));
}

void initialize_randomness(mt19937_64& gen) {
constexpr size_t n = mt19937_64::state_size;
constexpr size_t w = mt19937_64::word_size;
Expand Down Expand Up @@ -1710,6 +1881,7 @@ static_assert(test_meow_of());
#if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-2574489
static_assert(test_copy_part_1());
static_assert(test_copy_part_2());
static_assert(test_is_permutation());
#endif // ^^^ no workaround ^^^
#endif // _HAS_CXX20

Expand All @@ -1721,6 +1893,8 @@ int main() {
test_meow_of();
test_copy_part_1();
test_copy_part_2();
test_is_permutation();
test_is_permutation_vbool_like_iterators();

test_huge_vector_bool();

Expand Down