From 98e14f9412b0fed3f89110f3de99f376e3cbc2b4 Mon Sep 17 00:00:00 2001 From: kisuhorikka Date: Wed, 30 Jul 2025 20:53:05 +0800 Subject: [PATCH 1/2] [libc++] std::cmp_less and other integer comparison functions could be improved --- libcxx/include/__utility/cmp.h | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/libcxx/include/__utility/cmp.h b/libcxx/include/__utility/cmp.h index 14dc0c154c040..4c29f09628095 100644 --- a/libcxx/include/__utility/cmp.h +++ b/libcxx/include/__utility/cmp.h @@ -28,7 +28,13 @@ _LIBCPP_BEGIN_NAMESPACE_STD template <__signed_or_unsigned_integer _Tp, __signed_or_unsigned_integer _Up> _LIBCPP_HIDE_FROM_ABI constexpr bool cmp_equal(_Tp __t, _Up __u) noexcept { - if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>) + if constexpr (sizeof(_Tp) < sizeof(int) && sizeof(_Up) < sizeof(int)) { + __builtin_assume(__t < numeric_limits::max() && __u < numeric_limits::max()); + return static_cast(__t) == static_cast(__u); + } else if constexpr (sizeof(_Tp) < sizeof(long long) && sizeof(_Up) < sizeof(long long)) { + __builtin_assume(__t < numeric_limits::max() && __u < numeric_limits::max()); + return static_cast(__t) == static_cast(__u); + } else if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>) return __t == __u; else if constexpr (is_signed_v<_Tp>) return __t < 0 ? false : make_unsigned_t<_Tp>(__t) == __u; @@ -43,7 +49,13 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool cmp_not_equal(_Tp __t, _Up __u) noexcept { template <__signed_or_unsigned_integer _Tp, __signed_or_unsigned_integer _Up> _LIBCPP_HIDE_FROM_ABI constexpr bool cmp_less(_Tp __t, _Up __u) noexcept { - if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>) + if constexpr (sizeof(_Tp) < sizeof(int) && sizeof(_Up) < sizeof(int)) { + __builtin_assume(__t < numeric_limits::max() && __u < numeric_limits::max()); + return static_cast(__t) < static_cast(__u); + } else if constexpr (sizeof(_Tp) < sizeof(long long) && sizeof(_Up) < sizeof(long long)) { + __builtin_assume(__t < numeric_limits::max() && __u < numeric_limits::max()); + return static_cast(__t) < static_cast(__u); + } else if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>) return __t < __u; else if constexpr (is_signed_v<_Tp>) return __t < 0 ? true : make_unsigned_t<_Tp>(__t) < __u; From 4afea89b5e367aef3613ace4f40c1fee1b5c142f Mon Sep 17 00:00:00 2001 From: kisuhorikka Date: Thu, 31 Jul 2025 13:08:18 +0800 Subject: [PATCH 2/2] [libc++] std::cmp_less and other integer comparison functions could be improved --- libcxx/include/__utility/cmp.h | 24 ++-- libcxx/test/benchmarks/utility/cmp.bench.cpp | 139 +++++++++++++++++++ 2 files changed, 151 insertions(+), 12 deletions(-) create mode 100644 libcxx/test/benchmarks/utility/cmp.bench.cpp diff --git a/libcxx/include/__utility/cmp.h b/libcxx/include/__utility/cmp.h index 4c29f09628095..68864e23e0397 100644 --- a/libcxx/include/__utility/cmp.h +++ b/libcxx/include/__utility/cmp.h @@ -26,16 +26,18 @@ _LIBCPP_BEGIN_NAMESPACE_STD #if _LIBCPP_STD_VER >= 20 +template +concept __comparison_can_promote_to = + sizeof(_Tp) < sizeof(_Ip) || (sizeof(_Tp) == sizeof(_Ip) && __signed_integer<_Tp>); + template <__signed_or_unsigned_integer _Tp, __signed_or_unsigned_integer _Up> _LIBCPP_HIDE_FROM_ABI constexpr bool cmp_equal(_Tp __t, _Up __u) noexcept { - if constexpr (sizeof(_Tp) < sizeof(int) && sizeof(_Up) < sizeof(int)) { - __builtin_assume(__t < numeric_limits::max() && __u < numeric_limits::max()); + if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>) + return __t == __u; + else if constexpr (__comparison_can_promote_to<_Tp, int> && __comparison_can_promote_to<_Up, int>) return static_cast(__t) == static_cast(__u); - } else if constexpr (sizeof(_Tp) < sizeof(long long) && sizeof(_Up) < sizeof(long long)) { - __builtin_assume(__t < numeric_limits::max() && __u < numeric_limits::max()); + else if constexpr (__comparison_can_promote_to<_Tp, long long> && __comparison_can_promote_to<_Up, long long>) return static_cast(__t) == static_cast(__u); - } else if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>) - return __t == __u; else if constexpr (is_signed_v<_Tp>) return __t < 0 ? false : make_unsigned_t<_Tp>(__t) == __u; else @@ -49,14 +51,12 @@ _LIBCPP_HIDE_FROM_ABI constexpr bool cmp_not_equal(_Tp __t, _Up __u) noexcept { template <__signed_or_unsigned_integer _Tp, __signed_or_unsigned_integer _Up> _LIBCPP_HIDE_FROM_ABI constexpr bool cmp_less(_Tp __t, _Up __u) noexcept { - if constexpr (sizeof(_Tp) < sizeof(int) && sizeof(_Up) < sizeof(int)) { - __builtin_assume(__t < numeric_limits::max() && __u < numeric_limits::max()); + if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>) + return __t < __u; + else if constexpr (__comparison_can_promote_to<_Tp, int> && __comparison_can_promote_to<_Up, int>) return static_cast(__t) < static_cast(__u); - } else if constexpr (sizeof(_Tp) < sizeof(long long) && sizeof(_Up) < sizeof(long long)) { - __builtin_assume(__t < numeric_limits::max() && __u < numeric_limits::max()); + else if constexpr (__comparison_can_promote_to<_Tp, long long> && __comparison_can_promote_to<_Up, long long>) return static_cast(__t) < static_cast(__u); - } else if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>) - return __t < __u; else if constexpr (is_signed_v<_Tp>) return __t < 0 ? true : make_unsigned_t<_Tp>(__t) < __u; else diff --git a/libcxx/test/benchmarks/utility/cmp.bench.cpp b/libcxx/test/benchmarks/utility/cmp.bench.cpp new file mode 100644 index 0000000000000..1ed179ac4e38c --- /dev/null +++ b/libcxx/test/benchmarks/utility/cmp.bench.cpp @@ -0,0 +1,139 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: c++03, c++11, c++14, c++17 + +#include +#include "../CartesianBenchmarks.h" +#include "benchmark/benchmark.h" + +namespace { + +enum ValueType : size_t { + SChar, + UChar, + Short, + UShort, + Int, + UInt, + Long, + ULong, + LongLong, + ULongLong, +#ifndef TEST_HAS_NO_INT128 + Int128, + UInt128, +#endif +}; + +struct AllValueTypes : EnumValuesAsTuple { + static constexpr const char* Names[] = { + "schar", + "uchar", + "short", + "ushort", + "int", + "uint", + "long", + "ulong", + "longlong", + "ulonglong", +#ifndef TEST_HAS_NO_INT128 + "int128", + "uint128" +#endif + }; +}; + +using TestType = + std::tuple< signed char, + unsigned char, + short, + unsigned short, + int, + unsigned int, + long, + unsigned long, + long long, + unsigned long long +#ifndef TEST_HAS_NO_INT128 + , + __int128_t, + __uint128_t +#endif + >; + +template +struct CmpEqual { + static void run(benchmark::State& state) { + using T = std::tuple_element_t; + using U = std::tuple_element_t; + + T x1 = T{127}, x2 = T{111}; + U y1 = U{123}, y2 = U{1}; + for (auto _ : state) { + benchmark::DoNotOptimize(x1); + benchmark::DoNotOptimize(x2); + benchmark::DoNotOptimize(y1); + benchmark::DoNotOptimize(y2); + benchmark::DoNotOptimize(std::cmp_equal(x1, y1)); + benchmark::DoNotOptimize(std::cmp_equal(y1, x1)); + benchmark::DoNotOptimize(std::cmp_equal(x1, x1)); + benchmark::DoNotOptimize(std::cmp_equal(y1, y1)); + + benchmark::DoNotOptimize(std::cmp_equal(x2, y2)); + benchmark::DoNotOptimize(std::cmp_equal(y2, x2)); + benchmark::DoNotOptimize(std::cmp_equal(x2, x2)); + benchmark::DoNotOptimize(std::cmp_equal(y2, y2)); + } + } + + static std::string name() { return "BM_CmpEqual" + TType::name() + UType::name(); } +}; + +template +struct CmpLess { + static void run(benchmark::State& state) { + using T = std::tuple_element_t; + using U = std::tuple_element_t; + + T x1 = T{127}, x2 = T{111}; + U y1 = U{123}, y2 = U{1}; + for (auto _ : state) { + benchmark::DoNotOptimize(x1); + benchmark::DoNotOptimize(x2); + benchmark::DoNotOptimize(y1); + benchmark::DoNotOptimize(y2); + benchmark::DoNotOptimize(std::cmp_less(x1, y1)); + benchmark::DoNotOptimize(std::cmp_less(y1, x1)); + benchmark::DoNotOptimize(std::cmp_less(x1, x1)); + benchmark::DoNotOptimize(std::cmp_less(y1, y1)); + + benchmark::DoNotOptimize(std::cmp_less(x2, y2)); + benchmark::DoNotOptimize(std::cmp_less(y2, x2)); + benchmark::DoNotOptimize(std::cmp_less(x2, x2)); + benchmark::DoNotOptimize(std::cmp_less(y2, y2)); + } + } + + static std::string name() { return "BM_CmpLess" + TType::name() + UType::name(); } +}; + +} // namespace + +int main(int argc, char** argv) { + benchmark::Initialize(&argc, argv); + if (benchmark::ReportUnrecognizedArguments(argc, argv)) + return 1; + + makeCartesianProductBenchmark(); + makeCartesianProductBenchmark(); + benchmark::RunSpecifiedBenchmarks(); + + return 0; +}