From 9ad049cf81c08e942f3168c33a60e106ae1255bb Mon Sep 17 00:00:00 2001 From: Ian Bearman Date: Thu, 6 Aug 2026 08:23:11 -0700 Subject: [PATCH 1/2] Optimize small array three-way comparisons Dispatch eligible small byte-array comparisons to memcmp at runtime while preserving constexpr behavior. Add correctness coverage and a Google Benchmark target. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ed8264c6-e80f-4097-9c56-71caeceb7a19 --- benchmarks/CMakeLists.txt | 1 + benchmarks/src/array_compare.cpp | 89 +++++++++++++++++++ stl/inc/array | 9 ++ stl/inc/xutility | 12 ++- .../test.cpp | 76 ++++++++++++++++ 5 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 benchmarks/src/array_compare.cpp diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index dcbe625d816..8b0c4e1e8a6 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -103,6 +103,7 @@ endfunction() add_benchmark(adjacent_difference src/adjacent_difference.cpp) add_benchmark(adjacent_find src/adjacent_find.cpp) add_benchmark(any_swap src/any_swap.cpp) +add_benchmark(array_compare src/array_compare.cpp CXX_STANDARD 20) add_benchmark(bitset_from_string src/bitset_from_string.cpp) add_benchmark(bitset_to_string src/bitset_to_string.cpp) add_benchmark(charconv_floats src/charconv_floats.cpp) diff --git a/benchmarks/src/array_compare.cpp b/benchmarks/src/array_compare.cpp new file mode 100644 index 00000000000..4fb883f7dc8 --- /dev/null +++ b/benchmarks/src/array_compare.cpp @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +#include +#include +#include +#include +#include + +using namespace std; + +enum class comparison { + spaceship_less, + memcmp_less, + spaceship_equal, + memcmp_equal, +}; + +enum class scenario { + equal, + different_first, + different_last, +}; + +template +void bm(benchmark::State& state) { + array left{}; + array right{}; + + for (size_t index = 0; index < Size; ++index) { + left[index] = static_cast(index * 13 + 17); + right[index] = left[index]; + } + + if constexpr (Scenario == scenario::different_first) { + left.front() = 0x40; + right.front() = 0x80; + } else if constexpr (Scenario == scenario::different_last) { + left.back() = 0x40; + right.back() = 0x80; + } + + benchmark::DoNotOptimize(left); + benchmark::DoNotOptimize(right); + + for (auto _ : state) { + if constexpr (Comparison == comparison::spaceship_less) { + benchmark::DoNotOptimize((left <=> right) < 0); + } else if constexpr (Comparison == comparison::memcmp_less) { + benchmark::DoNotOptimize(memcmp(left.data(), right.data(), Size) < 0); + } else if constexpr (Comparison == comparison::spaceship_equal) { + benchmark::DoNotOptimize((left <=> right) == 0); + } else { + benchmark::DoNotOptimize(memcmp(left.data(), right.data(), Size) == 0); + } + } +} + +#define BENCHMARK_SCENARIOS(Size, Comparison) \ + BENCHMARK(bm); \ + BENCHMARK(bm); \ + BENCHMARK(bm) + +#define BENCHMARK_COMPARISONS(Size) \ + BENCHMARK_SCENARIOS(Size, spaceship_less); \ + BENCHMARK_SCENARIOS(Size, memcmp_less); \ + BENCHMARK_SCENARIOS(Size, spaceship_equal); \ + BENCHMARK_SCENARIOS(Size, memcmp_equal) + +BENCHMARK_COMPARISONS(1); +BENCHMARK_COMPARISONS(2); +BENCHMARK_COMPARISONS(3); +BENCHMARK_COMPARISONS(4); +BENCHMARK_COMPARISONS(7); +BENCHMARK_COMPARISONS(8); +BENCHMARK_COMPARISONS(15); +BENCHMARK_COMPARISONS(16); +BENCHMARK_COMPARISONS(31); +BENCHMARK_COMPARISONS(32); +BENCHMARK_COMPARISONS(63); +BENCHMARK_COMPARISONS(64); +BENCHMARK_COMPARISONS(127); +BENCHMARK_COMPARISONS(128); +BENCHMARK_COMPARISONS(255); +BENCHMARK_COMPARISONS(256); + +BENCHMARK_MAIN(); diff --git a/stl/inc/array b/stl/inc/array index 37bbd98d3f5..a26555aee95 100644 --- a/stl/inc/array +++ b/stl/inc/array @@ -769,9 +769,18 @@ _NODISCARD _CONSTEXPR20 bool operator==(const array<_Ty, _Size>& _Left, const ar } #if _HAS_CXX20 +inline constexpr size_t _Array_compare_memcmp_max_count = 64; + _EXPORT_STD template _NODISCARD constexpr _Synth_three_way_result<_Ty> operator<=>( const array<_Ty, _Size>& _Left, const array<_Ty, _Size>& _Right) { + if constexpr (_Size != 0 && _Size <= _Array_compare_memcmp_max_count + && _Lex_compare_memcmp_is_safe_elements<_Ty, _Ty>) { + if (!_STD is_constant_evaluated()) { + return _CSTD memcmp(_Left.data(), _Right.data(), _Size) <=> 0; + } + } + return _STD lexicographical_compare_three_way( _Left.data(), _Left.data() + _Size, _Right.data(), _Right.data() + _Size, _Synth_three_way{}); } diff --git a/stl/inc/xutility b/stl/inc/xutility index 637aa0b3d99..166e5fd91c3 100644 --- a/stl/inc/xutility +++ b/stl/inc/xutility @@ -6098,17 +6098,25 @@ namespace ranges { } // namespace ranges #endif // _HAS_CXX20 +template +constexpr bool _Lex_compare_memcmp_is_safe_elements = + sizeof(_Elem1) == 1 && sizeof(_Elem2) == 1 + && conjunction_v<_Is_character_or_bool<_Elem1>, _Is_character_or_bool<_Elem2>, is_unsigned<_Elem1>, + is_unsigned<_Elem2>>; + template constexpr bool _Lex_compare_memcmp_classify_elements = #if _VECTORIZED_MISMATCH is_integral_v<_Elem1> && is_integral_v<_Elem2> && sizeof(_Elem1) == sizeof(_Elem2) && is_unsigned_v<_Elem1> == is_unsigned_v<_Elem2>; #else // ^^^ _VECTORIZED_MISMATCH / !_VECTORIZED_MISMATCH vvv - conjunction_v<_Is_character_or_bool<_Elem1>, _Is_character_or_bool<_Elem2>, is_unsigned<_Elem1>, - is_unsigned<_Elem2>>; + _Lex_compare_memcmp_is_safe_elements<_Elem1, _Elem2>; #endif // ^^^ !_VECTORIZED_MISMATCH ^^^ #ifdef __cpp_lib_byte +template <> +inline constexpr bool _Lex_compare_memcmp_is_safe_elements = true; + template <> inline constexpr bool _Lex_compare_memcmp_classify_elements = true; #endif // defined(__cpp_lib_byte) diff --git a/tests/std/tests/GH_004040_container_nonmember_functions/test.cpp b/tests/std/tests/GH_004040_container_nonmember_functions/test.cpp index 1ec2c22408d..35a65f72dfd 100644 --- a/tests/std/tests/GH_004040_container_nonmember_functions/test.cpp +++ b/tests/std/tests/GH_004040_container_nonmember_functions/test.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -304,11 +305,86 @@ CONSTEXPR20 bool test_array_comparison() { return true; } +#if _HAS_CXX20 +template +constexpr bool test_array_byte_comparison(const Ty low, const Ty high) { + array left{}; + array right{}; + + assert(left <=> right == strong_ordering::equal); + + right.front() = high; + assert(left <=> right == strong_ordering::less); + assert(right <=> left == strong_ordering::greater); + + right.front() = low; + left.back() = high; + right.back() = low; + assert(left <=> right == strong_ordering::greater); + assert(right <=> left == strong_ordering::less); + + return true; +} + +void test_array_byte_comparison_exhaustive() { + for (int left_value = 0; left_value <= UCHAR_MAX; ++left_value) { + for (int right_value = 0; right_value <= UCHAR_MAX; ++right_value) { + const array left{static_cast(left_value)}; + const array right{static_cast(right_value)}; + const auto expected = left_value < right_value ? strong_ordering::less + : left_value > right_value ? strong_ordering::greater + : strong_ordering::equal; + assert((left <=> right) == expected); + } + } +} + +template +void test_array_byte_mismatch_positions() { + array left{}; + array right{}; + + for (size_t position = 0; position < Size; ++position) { + right[position] = 1; + assert((left <=> right) == strong_ordering::less); + assert((right <=> left) == strong_ordering::greater); + right[position] = 0; + } +} +#endif // _HAS_CXX20 + int main() { test_array_get(); STATIC_ASSERT(test_array_get()); test_array_comparison(); #if _HAS_CXX20 static_assert(test_array_comparison()); + assert((array{} <=> array{}) == strong_ordering::equal); + test_array_byte_comparison(0, 1); + test_array_byte_comparison(0, 1); + test_array_byte_comparison(0, 1); + test_array_byte_comparison(false, true); + test_array_byte_comparison(byte{0}, byte{1}); +#if CHAR_MIN == 0 + test_array_byte_comparison(char{0}, char{1}); +#endif // CHAR_MIN == 0 + test_array_byte_comparison_exhaustive(); + test_array_byte_mismatch_positions<64>(); + test_array_byte_mismatch_positions<65>(); +#ifdef __cpp_char8_t + test_array_byte_comparison(char8_t{0}, char8_t{1}); +#endif // defined(__cpp_char8_t) + static_assert((array{} <=> array{}) == strong_ordering::equal); + static_assert(test_array_byte_comparison(0, 1)); + static_assert(test_array_byte_comparison(0, 1)); + static_assert(test_array_byte_comparison(0, 1)); + static_assert(test_array_byte_comparison(false, true)); + static_assert(test_array_byte_comparison(byte{0}, byte{1})); +#if CHAR_MIN == 0 + static_assert(test_array_byte_comparison(char{0}, char{1})); +#endif // CHAR_MIN == 0 +#ifdef __cpp_char8_t + static_assert(test_array_byte_comparison(char8_t{0}, char8_t{1})); +#endif // defined(__cpp_char8_t) #endif // _HAS_CXX20 } From fe212add112428610ac687b6caa6af7016daa440 Mon Sep 17 00:00:00 2001 From: ian Bearman Date: Thu, 6 Aug 2026 08:35:28 -0700 Subject: [PATCH 2/2] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- stl/inc/array | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/array b/stl/inc/array index a26555aee95..5b56688bf97 100644 --- a/stl/inc/array +++ b/stl/inc/array @@ -775,7 +775,7 @@ _EXPORT_STD template _NODISCARD constexpr _Synth_three_way_result<_Ty> operator<=>( const array<_Ty, _Size>& _Left, const array<_Ty, _Size>& _Right) { if constexpr (_Size != 0 && _Size <= _Array_compare_memcmp_max_count - && _Lex_compare_memcmp_is_safe_elements<_Ty, _Ty>) { + && _Lex_compare_memcmp_is_safe_elements, remove_const_t<_Ty>>) { if (!_STD is_constant_evaluated()) { return _CSTD memcmp(_Left.data(), _Right.data(), _Size) <=> 0; }