From 2bc2e656d2dec5eba3a5e8efd6043f5a4f4630cf Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 24 Sep 2026 16:48:01 +1000 Subject: [PATCH 01/13] style(stlsoft): normalise bit-function header section banners Replace the short separator above each namespace close with the long section banner, and drop the short separator that precedes the include-guard close: - Refresh **Updated:** to 24th September 2026 and extend the Synesis Information Systems copyright through 2026; - Bump each header **EDIT** counter; --- include/stlsoft/util/bits/count_functions.h | 28 ++++++++++----------- include/stlsoft/util/bits/test_functions.h | 23 ++++++++--------- include/stlsoft/util/bits/xor_functions.h | 23 ++++++++--------- 3 files changed, 36 insertions(+), 38 deletions(-) diff --git a/include/stlsoft/util/bits/count_functions.h b/include/stlsoft/util/bits/count_functions.h index 8e0ada2c..6a8c83cd 100644 --- a/include/stlsoft/util/bits/count_functions.h +++ b/include/stlsoft/util/bits/count_functions.h @@ -1,14 +1,14 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: stlsoft/util/bits/count_functions.h + * File: stlsoft/util/bits/count_functions.h * - * Purpose: Bit count functions. + * Purpose: Bit count functions. * - * Created: 2nd June 2010 - * Updated: 11th March 2024 + * Created: 2nd June 2010 + * Updated: 24th September 2026 * - * Home: http://stlsoft.org/ + * Home: http://stlsoft.org/ * - * Copyright (c) 2019-2024, Matthew Wilson and Synesis Information Systems + * Copyright (c) 2019-2026, Matthew Wilson and Synesis Information Systems * Copyright (c) 2010-2019, Matthew Wilson and Synesis Software * All rights reserved. * @@ -51,10 +51,10 @@ #define STLSOFT_INCL_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION -# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_MAJOR 1 -# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_MINOR 3 -# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_REVISION 1 -# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_EDIT 19 +# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_MAJOR 1 +# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_MINOR 3 +# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_REVISION 1 +# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_EDIT 20 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -364,7 +364,6 @@ namespace ximpl_bit_functions return &s_table; } - # ifdef __cplusplus } /* namespace ximpl_bit_functions */ # endif /* __cplusplus */ @@ -612,7 +611,10 @@ count_bits( } #endif /* __cplusplus */ -/* ////////////////////////////////////////////////////////////////////// */ + +/* ///////////////////////////////////////////////////////////////////////// + * namespace + */ #ifndef STLSOFT_NO_NAMESPACE } /* namespace stlsoft */ @@ -627,8 +629,6 @@ count_bits( # pragma once #endif /* STLSOFT_CF_PRAGMA_ONCE_SUPPORT */ -/* ////////////////////////////////////////////////////////////////////// */ - #endif /* !STLSOFT_INCL_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS */ /* ///////////////////////////// end of file //////////////////////////// */ diff --git a/include/stlsoft/util/bits/test_functions.h b/include/stlsoft/util/bits/test_functions.h index 8823df45..75db13fb 100644 --- a/include/stlsoft/util/bits/test_functions.h +++ b/include/stlsoft/util/bits/test_functions.h @@ -1,14 +1,14 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: stlsoft/util/bits/test_functions.h + * File: stlsoft/util/bits/test_functions.h * - * Purpose: Bit test functions + * Purpose: Bit test functions * - * Created: 2nd June 2010 - * Updated: 11th March 2024 + * Created: 2nd June 2010 + * Updated: 24th September 2026 * - * Home: http://stlsoft.org/ + * Home: http://stlsoft.org/ * - * Copyright (c) 2019-2024, Matthew Wilson and Synesis Information Systems + * Copyright (c) 2019-2026, Matthew Wilson and Synesis Information Systems * Copyright (c) 2010-2019, Matthew Wilson and Synesis Software * All rights reserved. * @@ -54,7 +54,7 @@ # define STLSOFT_VER_STLSOFT_UTIL_BITS_H_TEST_FUNCTIONS_MAJOR 1 # define STLSOFT_VER_STLSOFT_UTIL_BITS_H_TEST_FUNCTIONS_MINOR 0 # define STLSOFT_VER_STLSOFT_UTIL_BITS_H_TEST_FUNCTIONS_REVISION 3 -# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_TEST_FUNCTIONS_EDIT 12 +# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_TEST_FUNCTIONS_EDIT 13 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -231,11 +231,12 @@ find_highest_bit( { return stlsoft_C_find_highest_bit_in_8bit_unsigned_integer(v); } - - #endif /* __cplusplus */ -/* ////////////////////////////////////////////////////////////////////// */ + +/* ///////////////////////////////////////////////////////////////////////// + * namespace + */ #ifndef STLSOFT_NO_NAMESPACE } /* namespace stlsoft */ @@ -250,8 +251,6 @@ find_highest_bit( # pragma once #endif /* STLSOFT_CF_PRAGMA_ONCE_SUPPORT */ -/* ////////////////////////////////////////////////////////////////////// */ - #endif /* !STLSOFT_INCL_STLSOFT_UTIL_BITS_H_TEST_FUNCTIONS */ /* ///////////////////////////// end of file //////////////////////////// */ diff --git a/include/stlsoft/util/bits/xor_functions.h b/include/stlsoft/util/bits/xor_functions.h index 289447d3..27d6040c 100644 --- a/include/stlsoft/util/bits/xor_functions.h +++ b/include/stlsoft/util/bits/xor_functions.h @@ -1,14 +1,14 @@ /* ///////////////////////////////////////////////////////////////////////// - * File: stlsoft/util/bits/xor_functions.h + * File: stlsoft/util/bits/xor_functions.h * - * Purpose: Bit XOR functions + * Purpose: Bit XOR functions * - * Created: 2nd June 2010 - * Updated: 11th March 2024 + * Created: 2nd June 2010 + * Updated: 24th September 2026 * - * Home: http://stlsoft.org/ + * Home: http://stlsoft.org/ * - * Copyright (c) 2019-2024, Matthew Wilson and Synesis Information Systems + * Copyright (c) 2019-2026, Matthew Wilson and Synesis Information Systems * Copyright (c) 2010-2019, Matthew Wilson and Synesis Software * All rights reserved. * @@ -54,7 +54,7 @@ # define STLSOFT_VER_STLSOFT_UTIL_BITS_H_XOR_FUNCTIONS_MAJOR 1 # define STLSOFT_VER_STLSOFT_UTIL_BITS_H_XOR_FUNCTIONS_MINOR 2 # define STLSOFT_VER_STLSOFT_UTIL_BITS_H_XOR_FUNCTIONS_REVISION 1 -# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_XOR_FUNCTIONS_EDIT 14 +# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_XOR_FUNCTIONS_EDIT 15 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -253,11 +253,12 @@ calculate_xor_over_range( { return stlsoft_C_calculate_xor_over_8bit_unsigned_range(p, n); } - - #endif /* __cplusplus */ -/* ////////////////////////////////////////////////////////////////////// */ + +/* ///////////////////////////////////////////////////////////////////////// + * namespace + */ #ifndef STLSOFT_NO_NAMESPACE } /* namespace stlsoft */ @@ -272,8 +273,6 @@ calculate_xor_over_range( # pragma once #endif /* STLSOFT_CF_PRAGMA_ONCE_SUPPORT */ -/* ////////////////////////////////////////////////////////////////////// */ - #endif /* !STLSOFT_INCL_STLSOFT_UTIL_BITS_H_XOR_FUNCTIONS */ /* ///////////////////////////// end of file //////////////////////////// */ From bfb43cbef721b93fb2223105211769944c5f51c2 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 24 Sep 2026 16:55:27 +1000 Subject: [PATCH 02/13] chore(tests): canonicalisation of test function names --- .../entry.cpp | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/test/unit/stlsoft/util/test.unit.stlsoft.util.bit_functions/entry.cpp b/test/unit/stlsoft/util/test.unit.stlsoft.util.bit_functions/entry.cpp index 97e2584f..ce70ce7b 100644 --- a/test/unit/stlsoft/util/test.unit.stlsoft.util.bit_functions/entry.cpp +++ b/test/unit/stlsoft/util/test.unit.stlsoft.util.bit_functions/entry.cpp @@ -4,7 +4,7 @@ * Purpose: Unit-tests for `stlsoft::bit_functions`. * * Created: 19th March 2010 - * Updated: 9th August 2026 + * Updated: 24th September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -44,11 +44,11 @@ namespace { - static void test_count_bits_Kernighan(); - static void test_count_bits_8bit_table(); - static void test_find_high_bit_32(); - static void test_find_high_bit_64(); - static void test_calculate_xor_over_range_8_bit(); + static void TEST_count_bits_Kernighan_method(); + static void TEST_count_bits_8bit_table(); + static void TEST_find_high_bit_FOR_32(); + static void TEST_find_high_bit_FOR_64(); + static void TEST_calculate_xor_over_range_FOR_8_BIT(); } // anonymous namespace @@ -65,11 +65,11 @@ int main(int argc, char **argv) if (XTESTS_START_RUNNER("test.unit.stlsoft.util.bit_functions", verbosity)) { - XTESTS_RUN_CASE(test_count_bits_Kernighan); - XTESTS_RUN_CASE(test_count_bits_8bit_table); - XTESTS_RUN_CASE(test_find_high_bit_32); - XTESTS_RUN_CASE(test_find_high_bit_64); - XTESTS_RUN_CASE(test_calculate_xor_over_range_8_bit); + XTESTS_RUN_CASE(TEST_count_bits_Kernighan_method); + XTESTS_RUN_CASE(TEST_count_bits_8bit_table); + XTESTS_RUN_CASE(TEST_find_high_bit_FOR_32); + XTESTS_RUN_CASE(TEST_find_high_bit_FOR_64); + XTESTS_RUN_CASE(TEST_calculate_xor_over_range_FOR_8_BIT); XTESTS_PRINT_RESULTS(); @@ -97,7 +97,8 @@ namespace { using platformstl::stopwatch; -static void test_count_bits_Kernighan() + +static void TEST_count_bits_Kernighan_method() { stopwatch sw; @@ -159,7 +160,7 @@ static void test_count_bits_Kernighan() fprintf(stdout, "t: %luns\n", static_cast(sw.get_nanoseconds())); } -static void test_count_bits_8bit_table() +static void TEST_count_bits_8bit_table() { stopwatch sw; @@ -221,7 +222,7 @@ static void test_count_bits_8bit_table() fprintf(stdout, "t: %luns\n", static_cast(sw.get_nanoseconds())); } -static void test_find_high_bit_32() +static void TEST_find_high_bit_FOR_32() { TEST_INT_EQ(0u, stlsoft::find_highest_bit(uint32_t(0))); @@ -243,7 +244,7 @@ static void test_find_high_bit_32() TEST_INT_EQ(32u, stlsoft::find_highest_bit(uint32_t(0x80000000))); } -static void test_find_high_bit_64() +static void TEST_find_high_bit_FOR_64() { TEST_INT_EQ(0u, stlsoft::find_highest_bit(uint64_t(0))); @@ -274,7 +275,7 @@ static void test_find_high_bit_64() TEST_INT_EQ(45u, stlsoft::find_highest_bit(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x1c3c3c3c3c3c)))); } -static void test_calculate_xor_over_range_8_bit() +static void TEST_calculate_xor_over_range_FOR_8_BIT() { uint8_t const elements[] = { From f88a10b0847e917475c30c71cebf1c395c567fb6 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 24 Sep 2026 17:09:29 +1000 Subject: [PATCH 03/13] test(stlsoft): cover all bit-function overloads by width Expand **test.unit.stlsoft.util.bit_functions** so every public C++ overload is called for each supported width, plus `count_bits(int)` for signedness; - Add Kernighan and 8-bit-table cases for **uint64_t**; - Add **count_bits** for **uint32_t**, **uint64_t**, and **int**; - Add **find_highest_bit** for **uint8_t** and **uint16_t**; - Add **calculate_xor_over_range** for 16-, 32-, and 64-bit ranges; - Remove in-unit stopwatch timing; --- .../entry.cpp | 318 +++++++++++++++--- 1 file changed, 273 insertions(+), 45 deletions(-) diff --git a/test/unit/stlsoft/util/test.unit.stlsoft.util.bit_functions/entry.cpp b/test/unit/stlsoft/util/test.unit.stlsoft.util.bit_functions/entry.cpp index ce70ce7b..16166f3b 100644 --- a/test/unit/stlsoft/util/test.unit.stlsoft.util.bit_functions/entry.cpp +++ b/test/unit/stlsoft/util/test.unit.stlsoft.util.bit_functions/entry.cpp @@ -29,12 +29,10 @@ #include /* STLSoft header files */ -#include /* Standard C++ header files */ /* Standard C header files */ -#include #include @@ -44,11 +42,21 @@ namespace { - static void TEST_count_bits_Kernighan_method(); - static void TEST_count_bits_8bit_table(); + static void TEST_count_bits_Kernighan_method_FOR_32(); + static void TEST_count_bits_Kernighan_method_FOR_64(); + static void TEST_count_bits_8bit_table_FOR_32(); + static void TEST_count_bits_8bit_table_FOR_64(); + static void TEST_count_bits_FOR_32(); + static void TEST_count_bits_FOR_64(); + static void TEST_count_bits_FOR_int(); + static void TEST_find_high_bit_FOR_8(); + static void TEST_find_high_bit_FOR_16(); static void TEST_find_high_bit_FOR_32(); static void TEST_find_high_bit_FOR_64(); static void TEST_calculate_xor_over_range_FOR_8_BIT(); + static void TEST_calculate_xor_over_range_FOR_16_BIT(); + static void TEST_calculate_xor_over_range_FOR_32_BIT(); + static void TEST_calculate_xor_over_range_FOR_64_BIT(); } // anonymous namespace @@ -56,7 +64,7 @@ namespace { * main */ -int main(int argc, char **argv) +int main(int argc, char* argv[]) { int retCode = EXIT_SUCCESS; int verbosity = 2; @@ -65,11 +73,21 @@ int main(int argc, char **argv) if (XTESTS_START_RUNNER("test.unit.stlsoft.util.bit_functions", verbosity)) { - XTESTS_RUN_CASE(TEST_count_bits_Kernighan_method); - XTESTS_RUN_CASE(TEST_count_bits_8bit_table); + XTESTS_RUN_CASE(TEST_count_bits_Kernighan_method_FOR_32); + XTESTS_RUN_CASE(TEST_count_bits_Kernighan_method_FOR_64); + XTESTS_RUN_CASE(TEST_count_bits_8bit_table_FOR_32); + XTESTS_RUN_CASE(TEST_count_bits_8bit_table_FOR_64); + XTESTS_RUN_CASE(TEST_count_bits_FOR_32); + XTESTS_RUN_CASE(TEST_count_bits_FOR_64); + XTESTS_RUN_CASE(TEST_count_bits_FOR_int); + XTESTS_RUN_CASE(TEST_find_high_bit_FOR_8); + XTESTS_RUN_CASE(TEST_find_high_bit_FOR_16); XTESTS_RUN_CASE(TEST_find_high_bit_FOR_32); XTESTS_RUN_CASE(TEST_find_high_bit_FOR_64); XTESTS_RUN_CASE(TEST_calculate_xor_over_range_FOR_8_BIT); + XTESTS_RUN_CASE(TEST_calculate_xor_over_range_FOR_16_BIT); + XTESTS_RUN_CASE(TEST_calculate_xor_over_range_FOR_32_BIT); + XTESTS_RUN_CASE(TEST_calculate_xor_over_range_FOR_64_BIT); XTESTS_PRINT_RESULTS(); @@ -86,24 +104,14 @@ int main(int argc, char **argv) namespace { - using stlsoft::sint8_t; using stlsoft::uint8_t; - using stlsoft::sint16_t; using stlsoft::uint16_t; - using stlsoft::sint32_t; using stlsoft::uint32_t; - using stlsoft::sint64_t; using stlsoft::uint64_t; - using platformstl::stopwatch; - -static void TEST_count_bits_Kernighan_method() +static void TEST_count_bits_Kernighan_method_FOR_32() { - stopwatch sw; - - sw.start(); - TEST_INT_EQ(0u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0))); TEST_INT_EQ(0u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x00000000))); @@ -137,14 +145,6 @@ static void TEST_count_bits_Kernighan_method() TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x00200000))); TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x02000000))); TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x20000000))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x00000002))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x00000020))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x00000200))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x00002000))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x00020000))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x00200000))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x02000000))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x20000000))); TEST_INT_EQ(2u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x00001001))); TEST_INT_EQ(2u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x00001010))); @@ -152,20 +152,35 @@ static void TEST_count_bits_Kernighan_method() TEST_INT_EQ(2u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x01001000))); TEST_INT_EQ(2u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x00011000))); TEST_INT_EQ(2u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x00101000))); - TEST_INT_EQ(2u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x01001000))); TEST_INT_EQ(2u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x10001000))); - - sw.stop(); - - fprintf(stdout, "t: %luns\n", static_cast(sw.get_nanoseconds())); } -static void TEST_count_bits_8bit_table() +static void TEST_count_bits_Kernighan_method_FOR_64() { - stopwatch sw; + TEST_INT_EQ(0u, stlsoft::count_bits_by_Kernighan_method(uint64_t(0))); + TEST_INT_EQ(0u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000000)))); + + TEST_INT_EQ(64u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0xffffffffffffffff)))); - sw.start(); + TEST_INT_EQ(32u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x3333333333333333)))); + TEST_INT_EQ(32u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x5555555555555555)))); + TEST_INT_EQ(32u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0xaaaaaaaaaaaaaaaa)))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x00000000000000ff)))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x00000000ff000000)))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x000000ff00000000)))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0xff00000000000000)))); + + TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000001)))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x0000000100000000)))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x8000000000000000)))); + + TEST_INT_EQ(2u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x0000000100000001)))); + TEST_INT_EQ(2u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x8000000000000001)))); +} + +static void TEST_count_bits_8bit_table_FOR_32() +{ TEST_INT_EQ(0u, stlsoft::count_bits_by_8bit_table(uint32_t(0))); TEST_INT_EQ(0u, stlsoft::count_bits_by_8bit_table(uint32_t(0x00000000))); @@ -199,14 +214,6 @@ static void TEST_count_bits_8bit_table() TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint32_t(0x00200000))); TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint32_t(0x02000000))); TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint32_t(0x20000000))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint32_t(0x00000002))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint32_t(0x00000020))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint32_t(0x00000200))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint32_t(0x00002000))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint32_t(0x00020000))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint32_t(0x00200000))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint32_t(0x02000000))); - TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint32_t(0x20000000))); TEST_INT_EQ(2u, stlsoft::count_bits_by_8bit_table(uint32_t(0x00001001))); TEST_INT_EQ(2u, stlsoft::count_bits_by_8bit_table(uint32_t(0x00001010))); @@ -214,12 +221,103 @@ static void TEST_count_bits_8bit_table() TEST_INT_EQ(2u, stlsoft::count_bits_by_8bit_table(uint32_t(0x01001000))); TEST_INT_EQ(2u, stlsoft::count_bits_by_8bit_table(uint32_t(0x00011000))); TEST_INT_EQ(2u, stlsoft::count_bits_by_8bit_table(uint32_t(0x00101000))); - TEST_INT_EQ(2u, stlsoft::count_bits_by_8bit_table(uint32_t(0x01001000))); TEST_INT_EQ(2u, stlsoft::count_bits_by_8bit_table(uint32_t(0x10001000))); +} + +static void TEST_count_bits_8bit_table_FOR_64() +{ + TEST_INT_EQ(0u, stlsoft::count_bits_by_8bit_table(uint64_t(0))); + TEST_INT_EQ(0u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000000)))); + + TEST_INT_EQ(64u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0xffffffffffffffff)))); + + TEST_INT_EQ(32u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x3333333333333333)))); + TEST_INT_EQ(32u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x5555555555555555)))); + TEST_INT_EQ(32u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0xaaaaaaaaaaaaaaaa)))); + + TEST_INT_EQ(8u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x00000000000000ff)))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x00000000ff000000)))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x000000ff00000000)))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0xff00000000000000)))); - sw.stop(); + TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000001)))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x0000000100000000)))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x8000000000000000)))); - fprintf(stdout, "t: %luns\n", static_cast(sw.get_nanoseconds())); + TEST_INT_EQ(2u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x0000000100000001)))); + TEST_INT_EQ(2u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x8000000000000001)))); +} + +static void TEST_count_bits_FOR_32() +{ + TEST_INT_EQ(0u, stlsoft::count_bits(uint32_t(0))); + TEST_INT_EQ(32u, stlsoft::count_bits(uint32_t(0xffffffff))); + TEST_INT_EQ(16u, stlsoft::count_bits(uint32_t(0x55555555))); + TEST_INT_EQ(1u, stlsoft::count_bits(uint32_t(0x80000000))); + TEST_INT_EQ(2u, stlsoft::count_bits(uint32_t(0x80000001))); +} + +static void TEST_count_bits_FOR_64() +{ + TEST_INT_EQ(0u, stlsoft::count_bits(uint64_t(0))); + TEST_INT_EQ(64u, stlsoft::count_bits(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0xffffffffffffffff)))); + TEST_INT_EQ(32u, stlsoft::count_bits(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x5555555555555555)))); + TEST_INT_EQ(1u, stlsoft::count_bits(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x8000000000000000)))); + TEST_INT_EQ(2u, stlsoft::count_bits(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x8000000000000001)))); +} + +static void TEST_count_bits_FOR_int() +{ + TEST_INT_EQ(0u, stlsoft::count_bits(0)); + TEST_INT_EQ(1u, stlsoft::count_bits(1)); + TEST_INT_EQ(1u, stlsoft::count_bits(2)); + TEST_INT_EQ(2u, stlsoft::count_bits(3)); + + /* Negative values are counted via their two's-complement bit pattern. */ + TEST_INT_EQ(sizeof(int) * 8u, stlsoft::count_bits(-1)); + TEST_INT_EQ(1u, stlsoft::count_bits(static_cast(1u << (sizeof(int) * 8u - 1u)))); +} + +static void TEST_find_high_bit_FOR_8() +{ + TEST_INT_EQ(0u, stlsoft::find_highest_bit(uint8_t(0))); + + TEST_INT_EQ(1u, stlsoft::find_highest_bit(uint8_t(1))); + + TEST_INT_EQ(2u, stlsoft::find_highest_bit(uint8_t(2))); + TEST_INT_EQ(2u, stlsoft::find_highest_bit(uint8_t(3))); + + TEST_INT_EQ(3u, stlsoft::find_highest_bit(uint8_t(4))); + TEST_INT_EQ(3u, stlsoft::find_highest_bit(uint8_t(5))); + TEST_INT_EQ(3u, stlsoft::find_highest_bit(uint8_t(6))); + TEST_INT_EQ(3u, stlsoft::find_highest_bit(uint8_t(7))); + + TEST_INT_EQ(5u, stlsoft::find_highest_bit(uint8_t(0x10))); + TEST_INT_EQ(5u, stlsoft::find_highest_bit(uint8_t(0x1f))); + + TEST_INT_EQ(8u, stlsoft::find_highest_bit(uint8_t(0x80))); + TEST_INT_EQ(8u, stlsoft::find_highest_bit(uint8_t(0xff))); +} + +static void TEST_find_high_bit_FOR_16() +{ + TEST_INT_EQ(0u, stlsoft::find_highest_bit(uint16_t(0))); + + TEST_INT_EQ(1u, stlsoft::find_highest_bit(uint16_t(1))); + + TEST_INT_EQ(2u, stlsoft::find_highest_bit(uint16_t(2))); + TEST_INT_EQ(2u, stlsoft::find_highest_bit(uint16_t(3))); + + TEST_INT_EQ(3u, stlsoft::find_highest_bit(uint16_t(4))); + TEST_INT_EQ(3u, stlsoft::find_highest_bit(uint16_t(5))); + TEST_INT_EQ(3u, stlsoft::find_highest_bit(uint16_t(6))); + TEST_INT_EQ(3u, stlsoft::find_highest_bit(uint16_t(7))); + + TEST_INT_EQ(9u, stlsoft::find_highest_bit(uint16_t(0x0100))); + TEST_INT_EQ(9u, stlsoft::find_highest_bit(uint16_t(0x01ff))); + + TEST_INT_EQ(16u, stlsoft::find_highest_bit(uint16_t(0x8000))); + TEST_INT_EQ(16u, stlsoft::find_highest_bit(uint16_t(0xffff))); } static void TEST_find_high_bit_FOR_32() @@ -316,7 +414,137 @@ static void TEST_calculate_xor_over_range_FOR_8_BIT() TEST_INT_EQ(0xd0, stlsoft::calculate_xor_over_range(elements, 12u)); TEST_INT_EQ(0xf0, stlsoft::calculate_xor_over_range(elements, 13u)); } + +static void TEST_calculate_xor_over_range_FOR_16_BIT() +{ + uint16_t const elements[] = + { + 0x0000 + + , 0x0001 + , 0x0002 + , 0x0004 + , 0x0008 + + , 0x0001 + , 0x0002 + , 0x0004 + , 0x0008 + + , 0x0040 + , 0x0080 + , 0x0010 + , 0x0020 + + , 0x0100 + , 0x8000 + }; + + TEST_INT_EQ(0x0000, stlsoft::calculate_xor_over_range(elements, 0u)); + TEST_INT_EQ(0x0000, stlsoft::calculate_xor_over_range(elements, 1u)); + TEST_INT_EQ(0x0001, stlsoft::calculate_xor_over_range(elements, 2u)); + TEST_INT_EQ(0x0003, stlsoft::calculate_xor_over_range(elements, 3u)); + TEST_INT_EQ(0x0007, stlsoft::calculate_xor_over_range(elements, 4u)); + TEST_INT_EQ(0x000f, stlsoft::calculate_xor_over_range(elements, 5u)); + TEST_INT_EQ(0x000e, stlsoft::calculate_xor_over_range(elements, 6u)); + TEST_INT_EQ(0x000c, stlsoft::calculate_xor_over_range(elements, 7u)); + TEST_INT_EQ(0x0008, stlsoft::calculate_xor_over_range(elements, 8u)); + TEST_INT_EQ(0x0000, stlsoft::calculate_xor_over_range(elements, 9u)); + TEST_INT_EQ(0x0040, stlsoft::calculate_xor_over_range(elements, 10u)); + TEST_INT_EQ(0x00c0, stlsoft::calculate_xor_over_range(elements, 11u)); + TEST_INT_EQ(0x00d0, stlsoft::calculate_xor_over_range(elements, 12u)); + TEST_INT_EQ(0x00f0, stlsoft::calculate_xor_over_range(elements, 13u)); + TEST_INT_EQ(0x01f0, stlsoft::calculate_xor_over_range(elements, 14u)); + TEST_INT_EQ(0x81f0, stlsoft::calculate_xor_over_range(elements, 15u)); +} + +static void TEST_calculate_xor_over_range_FOR_32_BIT() +{ + uint32_t const elements[] = + { + 0x00000000u + + , 0x00000001u + , 0x00000002u + , 0x00000004u + , 0x00000008u + + , 0x00000001u + , 0x00000002u + , 0x00000004u + , 0x00000008u + + , 0x00000040u + , 0x00000080u + , 0x00000010u + , 0x00000020u + + , 0x00000100u + , 0x80000000u + }; + + TEST_INT_EQ(0x00000000u, stlsoft::calculate_xor_over_range(elements, 0u)); + TEST_INT_EQ(0x00000000u, stlsoft::calculate_xor_over_range(elements, 1u)); + TEST_INT_EQ(0x00000001u, stlsoft::calculate_xor_over_range(elements, 2u)); + TEST_INT_EQ(0x00000003u, stlsoft::calculate_xor_over_range(elements, 3u)); + TEST_INT_EQ(0x00000007u, stlsoft::calculate_xor_over_range(elements, 4u)); + TEST_INT_EQ(0x0000000fu, stlsoft::calculate_xor_over_range(elements, 5u)); + TEST_INT_EQ(0x0000000eu, stlsoft::calculate_xor_over_range(elements, 6u)); + TEST_INT_EQ(0x0000000cu, stlsoft::calculate_xor_over_range(elements, 7u)); + TEST_INT_EQ(0x00000008u, stlsoft::calculate_xor_over_range(elements, 8u)); + TEST_INT_EQ(0x00000000u, stlsoft::calculate_xor_over_range(elements, 9u)); + TEST_INT_EQ(0x00000040u, stlsoft::calculate_xor_over_range(elements, 10u)); + TEST_INT_EQ(0x000000c0u, stlsoft::calculate_xor_over_range(elements, 11u)); + TEST_INT_EQ(0x000000d0u, stlsoft::calculate_xor_over_range(elements, 12u)); + TEST_INT_EQ(0x000000f0u, stlsoft::calculate_xor_over_range(elements, 13u)); + TEST_INT_EQ(0x000001f0u, stlsoft::calculate_xor_over_range(elements, 14u)); + TEST_INT_EQ(0x800001f0u, stlsoft::calculate_xor_over_range(elements, 15u)); +} + +static void TEST_calculate_xor_over_range_FOR_64_BIT() +{ + uint64_t const elements[] = + { + STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000000) + + , STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000001) + , STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000002) + , STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000004) + , STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000008) + + , STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000001) + , STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000002) + , STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000004) + , STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000008) + + , STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000040) + , STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000080) + , STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000010) + , STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000020) + + , STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000100) + , STLSOFT_GEN_UINT64_SUFFIX(0x8000000000000000) + }; + + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000000), stlsoft::calculate_xor_over_range(elements, 0u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000000), stlsoft::calculate_xor_over_range(elements, 1u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000001), stlsoft::calculate_xor_over_range(elements, 2u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000003), stlsoft::calculate_xor_over_range(elements, 3u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000007), stlsoft::calculate_xor_over_range(elements, 4u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x000000000000000f), stlsoft::calculate_xor_over_range(elements, 5u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x000000000000000e), stlsoft::calculate_xor_over_range(elements, 6u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x000000000000000c), stlsoft::calculate_xor_over_range(elements, 7u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000008), stlsoft::calculate_xor_over_range(elements, 8u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000000), stlsoft::calculate_xor_over_range(elements, 9u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000040), stlsoft::calculate_xor_over_range(elements, 10u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x00000000000000c0), stlsoft::calculate_xor_over_range(elements, 11u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x00000000000000d0), stlsoft::calculate_xor_over_range(elements, 12u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x00000000000000f0), stlsoft::calculate_xor_over_range(elements, 13u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x00000000000001f0), stlsoft::calculate_xor_over_range(elements, 14u)); + TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x80000000000001f0), stlsoft::calculate_xor_over_range(elements, 15u)); +} } // anonymous namespace /* ///////////////////////////// end of file //////////////////////////// */ + From 654c3d0c9de6560c44f8e93635595a5864e991b5 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 24 Sep 2026 17:22:36 +1000 Subject: [PATCH 04/13] test(stlsoft): add bit-functions performance suite MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add **test.performance.stlsoft.bit_functions** under a nested **stlsoft/** performance tree, with an aligned width × function timing table covering every public bit-function overload; - Wire **test/performance/CMakeLists.txt** → **stlsoft** → **bit_functions**; - Time Kernighan and 8-bit-table popcount, **count_bits**, **find_highest_bit**, and **calculate_xor_over_range**; --- test/performance/CMakeLists.txt | 2 + test/performance/stlsoft/CMakeLists.txt | 2 + .../stlsoft/bit_functions/CMakeLists.txt | 2 + .../stlsoft/bit_functions/main.cpp | 522 ++++++++++++++++++ 4 files changed, 528 insertions(+) create mode 100644 test/performance/stlsoft/CMakeLists.txt create mode 100644 test/performance/stlsoft/bit_functions/CMakeLists.txt create mode 100644 test/performance/stlsoft/bit_functions/main.cpp diff --git a/test/performance/CMakeLists.txt b/test/performance/CMakeLists.txt index 1e025c0e..e65c7bf9 100644 --- a/test/performance/CMakeLists.txt +++ b/test/performance/CMakeLists.txt @@ -1,4 +1,6 @@ +add_subdirectory(stlsoft) + if(X_CMAKE_CXX_FULLSTANDARD GREATER_EQUAL 2011) add_subdirectory(test.performance.platformstl.stopwatch) diff --git a/test/performance/stlsoft/CMakeLists.txt b/test/performance/stlsoft/CMakeLists.txt new file mode 100644 index 00000000..c14ae17f --- /dev/null +++ b/test/performance/stlsoft/CMakeLists.txt @@ -0,0 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +add_subdirectory(bit_functions) diff --git a/test/performance/stlsoft/bit_functions/CMakeLists.txt b/test/performance/stlsoft/bit_functions/CMakeLists.txt new file mode 100644 index 00000000..96b868bb --- /dev/null +++ b/test/performance/stlsoft/bit_functions/CMakeLists.txt @@ -0,0 +1,2 @@ +# SIS:AUTO_GENERATED: Remove this line if you edit the file, otherwise it will be overwritten +define_example_program(test.performance.stlsoft.bit_functions main.cpp) diff --git a/test/performance/stlsoft/bit_functions/main.cpp b/test/performance/stlsoft/bit_functions/main.cpp new file mode 100644 index 00000000..49983d23 --- /dev/null +++ b/test/performance/stlsoft/bit_functions/main.cpp @@ -0,0 +1,522 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: test.performance.stlsoft.bit_functions/main.cpp + * + * Purpose: Perf-test for bit functions. + * + * Created: 24th September 2026 + * Updated: 24th September 2026 + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* ///////////////////////////////////////////////////////////////////////// + * compatibility + */ + +#if !defined(__cplusplus) || \ + __cplusplus < 201103L +# error Requires C++11 or later +#endif + + +/* ///////////////////////////////////////////////////////////////////////// + * feature control + */ + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#include + +#if __cplusplus >= 201103L +# include +#else +# include +#endif /* C++11+ */ +#include + +#include +#include +#include + +#include + + +/* ///////////////////////////////////////////////////////////////////////// + * types + */ + +#if __cplusplus >= 201103L + +typedef stlsoft::std_chrono_hrc_stopwatch stopwatch_t; +#else + +typedef platformstl::stopwatch stopwatch_t; +#endif /* C++11+ */ +typedef stopwatch_t::interval_type interval_t; + +using stlsoft::ss_size_t; +using stlsoft::uint8_t; +using stlsoft::uint16_t; +using stlsoft::uint32_t; +using stlsoft::uint64_t; + + +/* ///////////////////////////////////////////////////////////////////////// + * constants + */ + +namespace { + + ss_size_t const NUM_ITERATIONS = 20000000; + ss_size_t const XOR_RANGE_LEN = 64; + int const FN_COL_WIDTH = 36; + int const METRIC_COL_WIDTH = 16; +} // anonymous namespace + + +/* ///////////////////////////////////////////////////////////////////////// + * functions + */ + +template +std::string +thousands( + T_integer const& v +) +{ + char dest[41]; + size_t const n = stlsoft::format_thousands(dest, STLSOFT_NUM_ELEMENTS(dest), "3;0", v); + + return std::string(dest, n); +} + +void +emit_metric_cell( + char const* s +) +{ + std::cout + << '\t' + << std::setw(METRIC_COL_WIDTH) << std::right << s; +} + +void +emit_metric_cell( + interval_t const& v +) +{ + emit_metric_cell(thousands(v).c_str()); +} + +void +emit_absent_metric_cell() +{ + emit_metric_cell("-"); +} + +void +emit_header_row() +{ + std::cout + << std::setw(FN_COL_WIDTH) << std::left << "" + ; + emit_metric_cell("uint8_t"); + emit_metric_cell("uint16_t"); + emit_metric_cell("uint32_t"); + emit_metric_cell("uint64_t"); + emit_metric_cell("int"); + emit_metric_cell("anchor"); + std::cout << std::endl; +} + +void +emit_result_row( + char const* function_name +, interval_t const* uint8_ns +, interval_t const* uint16_ns +, interval_t const* uint32_ns +, interval_t const* uint64_ns +, interval_t const* int_ns +, int anchor_value +) +{ + std::cout + << std::setw(FN_COL_WIDTH) << std::left << function_name + ; + + if (NULL != uint8_ns) + { + emit_metric_cell(*uint8_ns); + } + else + { + emit_absent_metric_cell(); + } + + if (NULL != uint16_ns) + { + emit_metric_cell(*uint16_ns); + } + else + { + emit_absent_metric_cell(); + } + + if (NULL != uint32_ns) + { + emit_metric_cell(*uint32_ns); + } + else + { + emit_absent_metric_cell(); + } + + if (NULL != uint64_ns) + { + emit_metric_cell(*uint64_ns); + } + else + { + emit_absent_metric_cell(); + } + + if (NULL != int_ns) + { + emit_metric_cell(*int_ns); + } + else + { + emit_absent_metric_cell(); + } + + emit_metric_cell(thousands(anchor_value).c_str()); + std::cout << std::endl; +} + + +/* ///////////////////////////////////////////////////////////////////////// + * main() + */ + +int main(int /*argc*/, char* /*argv*/[]) +{ + uint8_t xor8[XOR_RANGE_LEN]; + uint16_t xor16[XOR_RANGE_LEN]; + uint32_t xor32[XOR_RANGE_LEN]; + uint64_t xor64[XOR_RANGE_LEN]; + + for (ss_size_t i = 0; XOR_RANGE_LEN != i; ++i) + { + xor8[i] = static_cast(i * 3u + 1u); + xor16[i] = static_cast(i * 7u + 1u); + xor32[i] = static_cast(i * 11u + 1u); + xor64[i] = static_cast(i * 13u + 1u); + } + + emit_header_row(); + + + // count_bits_by_Kernighan_method + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint32_t const v = i; + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint64_t const v = i; + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "count_bits_by_Kernighan_method()" + , NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + } + + + // count_bits_by_8bit_table + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint32_t const v = i; + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint64_t const v = i; + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "count_bits_by_8bit_table()" + , NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + } + + + // count_bits + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint32_t const v = i; + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint64_t const v = i; + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + int const v = static_cast(i); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_int = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "count_bits()" + , NULL + , NULL + , &int_uint32 + , &int_uint64 + , &int_int + , anchor_value + ); + } + } + + + // find_highest_bit + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint8_t const v = static_cast(i & 0xff); + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint16_t const v = static_cast(i & 0xffff); + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint32_t const v = i; + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint64_t const v = i; + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "find_highest_bit()" + , &int_uint8 + , &int_uint16 + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + } + + + // calculate_xor_over_range + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_size_t const n = (i % XOR_RANGE_LEN) + 1u; + + anchor_value += stlsoft::calculate_xor_over_range(xor8, n); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_size_t const n = (i % XOR_RANGE_LEN) + 1u; + + anchor_value += stlsoft::calculate_xor_over_range(xor16, n); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_size_t const n = (i % XOR_RANGE_LEN) + 1u; + + anchor_value += stlsoft::calculate_xor_over_range(xor32, n); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_size_t const n = (i % XOR_RANGE_LEN) + 1u; + + anchor_value += stlsoft::calculate_xor_over_range(xor64, n); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "calculate_xor_over_range()" + , &int_uint8 + , &int_uint16 + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + } + + + return EXIT_SUCCESS; +} + + +/* ///////////////////////////// end of file //////////////////////////// */ + From 8d74276c897bfb60a1d9b9946eea724b8d23e213 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 24 Sep 2026 17:38:47 +1000 Subject: [PATCH 05/13] fix: `count_bits()` overloads were broken when `STLSOFT_BIT_COUNT_BY_Kernighan` is defined --- include/stlsoft/util/bits/count_functions.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/include/stlsoft/util/bits/count_functions.h b/include/stlsoft/util/bits/count_functions.h index 6a8c83cd..8d77a842 100644 --- a/include/stlsoft/util/bits/count_functions.h +++ b/include/stlsoft/util/bits/count_functions.h @@ -53,8 +53,8 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_MAJOR 1 # define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_MINOR 3 -# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_REVISION 1 -# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_EDIT 20 +# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_REVISION 2 +# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_EDIT 21 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -539,8 +539,9 @@ count_bits( ss_uint32_t v ) STLSOFT_NOEXCEPT { -# if defined(STLSOFT_BIT_COUNT_BY_Kernighan) - return count_bits_by_Kernighan(v); +# if 0 +# elif defined(STLSOFT_BIT_COUNT_BY_Kernighan) + return count_bits_by_Kernighan_method(v); # else return count_bits_by_8bit_table(v); # endif @@ -558,8 +559,9 @@ count_bits( ss_uint64_t v ) STLSOFT_NOEXCEPT { -# if defined(STLSOFT_BIT_COUNT_BY_Kernighan) - return count_bits_by_Kernighan(v); +# if 0 +# elif defined(STLSOFT_BIT_COUNT_BY_Kernighan) + return count_bits_by_Kernighan_method(v); # else return count_bits_by_8bit_table(v); # endif From e8168845179f7ce1a8336fdc6251b459be6c4f7b Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 24 Sep 2026 17:46:35 +1000 Subject: [PATCH 06/13] test(stlsoft): vary popcount density in bit-functions perf Extend **test.performance.stlsoft.bit_functions** with denser popcount workloads so Kernighan vs 8-bit-table can be compared beyond the sparse sequential-i series; - Add /~i (bitwise-not), /ones (volatile all-ones), and /ones^1 (all-ones with one rotating bit cleared); - Widen the function-name column for the longer row labels; --- .../stlsoft/bit_functions/main.cpp | 487 +++++++++++++++++- 1 file changed, 486 insertions(+), 1 deletion(-) diff --git a/test/performance/stlsoft/bit_functions/main.cpp b/test/performance/stlsoft/bit_functions/main.cpp index 49983d23..08a4c814 100644 --- a/test/performance/stlsoft/bit_functions/main.cpp +++ b/test/performance/stlsoft/bit_functions/main.cpp @@ -72,7 +72,7 @@ namespace { ss_size_t const NUM_ITERATIONS = 20000000; ss_size_t const XOR_RANGE_LEN = 64; - int const FN_COL_WIDTH = 36; + int const FN_COL_WIDTH = 42; int const METRIC_COL_WIDTH = 16; } // anonymous namespace @@ -372,6 +372,491 @@ int main(int /*argc*/, char* /*argv*/[]) } + // ------------------------------------------------------------------ + // Dense inputs (~i): high popcount — favourable to the table path + // ------------------------------------------------------------------ + + // count_bits_by_Kernighan_method (dense) + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint32_t const v = ~uint32_t(i); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint64_t const v = ~uint64_t(i); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "count_bits_by_Kernighan_method()/~i" + , NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + } + + + // count_bits_by_8bit_table (dense) + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint32_t const v = ~uint32_t(i); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint64_t const v = ~uint64_t(i); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "count_bits_by_8bit_table()/~i" + , NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + } + + + // count_bits (dense) + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint32_t const v = ~uint32_t(i); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint64_t const v = ~uint64_t(i); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + int const v = static_cast(~uint32_t(i)); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_int = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "count_bits()/~i" + , NULL + , NULL + , &int_uint32 + , &int_uint64 + , &int_int + , anchor_value + ); + } + } + + + // ------------------------------------------------------------------ + // Max density (all-ones). Source via volatile so inlining cannot + // constant-fold popcount(~0) to a literal. + // ------------------------------------------------------------------ + + { + uint32_t volatile ones32 = ~uint32_t(0); + uint64_t volatile ones64 = ~uint64_t(0); + + + // count_bits_by_Kernighan_method (max) + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint32_t const v = ones32; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint64_t const v = ones64; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "count_bits_by_Kernighan_method()/ones" + , NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + } + + + // count_bits_by_8bit_table (max) + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint32_t const v = ones32; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint64_t const v = ones64; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "count_bits_by_8bit_table()/ones" + , NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + } + + + // count_bits (max) + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint32_t const v = ones32; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint64_t const v = ones64; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + int const v = static_cast(ones32); + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_int = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "count_bits()/ones" + , NULL + , NULL + , &int_uint32 + , &int_uint64 + , &int_int + , anchor_value + ); + } + } + } + + + // ------------------------------------------------------------------ + // Close-to-max: all-ones with one rotating bit cleared (31 / 63 ones) + // ------------------------------------------------------------------ + + // count_bits_by_Kernighan_method (close-to-max) + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint32_t const v = ~uint32_t(0) ^ (uint32_t(1) << (i % 32u)); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint64_t const v = ~uint64_t(0) ^ (uint64_t(1) << (i % 64u)); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "count_bits_by_Kernighan_method()/ones^1" + , NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + } + + + // count_bits_by_8bit_table (close-to-max) + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint32_t const v = ~uint32_t(0) ^ (uint32_t(1) << (i % 32u)); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint64_t const v = ~uint64_t(0) ^ (uint64_t(1) << (i % 64u)); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "count_bits_by_8bit_table()/ones^1" + , NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + } + + + // count_bits (close-to-max) + for (int W = 2; 0 != W; --W) + { + stopwatch_t sw; + + int anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint32_t const v = ~uint32_t(0) ^ (uint32_t(1) << (i % 32u)); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + uint64_t const v = ~uint64_t(0) ^ (uint64_t(1) << (i % 64u)); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + int const v = static_cast(~uint32_t(0) ^ (uint32_t(1) << (i % 32u))); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_int = sw.get_nanoseconds(); + + + if (1 == W) + { + emit_result_row( + "count_bits()/ones^1" + , NULL + , NULL + , &int_uint32 + , &int_uint64 + , &int_int + , anchor_value + ); + } + } + + // find_highest_bit for (int W = 2; 0 != W; --W) { From 8f8583d545879c0139e782464fac584d67bb2fd5 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 24 Sep 2026 17:50:46 +1000 Subject: [PATCH 07/13] fix(stlsoft): avoid uint64_t clash in bit-functions perf Stop importing stlsoft::uint64_t into the global namespace: it conflicts with ::uint64_t from (pulled in via std_chrono_hrc_stopwatch) and made 64-bit overloads ambiguous on Linux GCC. - Use stlsoft::ss_uintN_t aliases for variables and casts; keep column headers as uintN_t labels; --- .../stlsoft/bit_functions/main.cpp | 88 +++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/test/performance/stlsoft/bit_functions/main.cpp b/test/performance/stlsoft/bit_functions/main.cpp index 08a4c814..06c3eb5e 100644 --- a/test/performance/stlsoft/bit_functions/main.cpp +++ b/test/performance/stlsoft/bit_functions/main.cpp @@ -58,10 +58,10 @@ typedef platformstl::stopwatch stopwatch_t; typedef stopwatch_t::interval_type interval_t; using stlsoft::ss_size_t; -using stlsoft::uint8_t; -using stlsoft::uint16_t; -using stlsoft::uint32_t; -using stlsoft::uint64_t; +using stlsoft::ss_uint8_t; +using stlsoft::ss_uint16_t; +using stlsoft::ss_uint32_t; +using stlsoft::ss_uint64_t; /* ///////////////////////////////////////////////////////////////////////// @@ -203,17 +203,17 @@ emit_result_row( int main(int /*argc*/, char* /*argv*/[]) { - uint8_t xor8[XOR_RANGE_LEN]; - uint16_t xor16[XOR_RANGE_LEN]; - uint32_t xor32[XOR_RANGE_LEN]; - uint64_t xor64[XOR_RANGE_LEN]; + ss_uint8_t xor8[XOR_RANGE_LEN]; + ss_uint16_t xor16[XOR_RANGE_LEN]; + ss_uint32_t xor32[XOR_RANGE_LEN]; + ss_uint64_t xor64[XOR_RANGE_LEN]; for (ss_size_t i = 0; XOR_RANGE_LEN != i; ++i) { - xor8[i] = static_cast(i * 3u + 1u); - xor16[i] = static_cast(i * 7u + 1u); - xor32[i] = static_cast(i * 11u + 1u); - xor64[i] = static_cast(i * 13u + 1u); + xor8[i] = static_cast(i * 3u + 1u); + xor16[i] = static_cast(i * 7u + 1u); + xor32[i] = static_cast(i * 11u + 1u); + xor64[i] = static_cast(i * 13u + 1u); } emit_header_row(); @@ -230,7 +230,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint32_t const v = i; + ss_uint32_t const v = i; anchor_value += stlsoft::count_bits_by_Kernighan_method(v); } @@ -242,7 +242,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint64_t const v = i; + ss_uint64_t const v = i; anchor_value += stlsoft::count_bits_by_Kernighan_method(v); } @@ -277,7 +277,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint32_t const v = i; + ss_uint32_t const v = i; anchor_value += stlsoft::count_bits_by_8bit_table(v); } @@ -289,7 +289,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint64_t const v = i; + ss_uint64_t const v = i; anchor_value += stlsoft::count_bits_by_8bit_table(v); } @@ -324,7 +324,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint32_t const v = i; + ss_uint32_t const v = i; anchor_value += stlsoft::count_bits(v); } @@ -336,7 +336,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint64_t const v = i; + ss_uint64_t const v = i; anchor_value += stlsoft::count_bits(v); } @@ -387,7 +387,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint32_t const v = ~uint32_t(i); + ss_uint32_t const v = ~ss_uint32_t(i); anchor_value += stlsoft::count_bits_by_Kernighan_method(v); } @@ -399,7 +399,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint64_t const v = ~uint64_t(i); + ss_uint64_t const v = ~ss_uint64_t(i); anchor_value += stlsoft::count_bits_by_Kernighan_method(v); } @@ -434,7 +434,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint32_t const v = ~uint32_t(i); + ss_uint32_t const v = ~ss_uint32_t(i); anchor_value += stlsoft::count_bits_by_8bit_table(v); } @@ -446,7 +446,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint64_t const v = ~uint64_t(i); + ss_uint64_t const v = ~ss_uint64_t(i); anchor_value += stlsoft::count_bits_by_8bit_table(v); } @@ -481,7 +481,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint32_t const v = ~uint32_t(i); + ss_uint32_t const v = ~ss_uint32_t(i); anchor_value += stlsoft::count_bits(v); } @@ -493,7 +493,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint64_t const v = ~uint64_t(i); + ss_uint64_t const v = ~ss_uint64_t(i); anchor_value += stlsoft::count_bits(v); } @@ -505,7 +505,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - int const v = static_cast(~uint32_t(i)); + int const v = static_cast(~ss_uint32_t(i)); anchor_value += stlsoft::count_bits(v); } @@ -535,8 +535,8 @@ int main(int /*argc*/, char* /*argv*/[]) // ------------------------------------------------------------------ { - uint32_t volatile ones32 = ~uint32_t(0); - uint64_t volatile ones64 = ~uint64_t(0); + ss_uint32_t volatile ones32 = ~ss_uint32_t(0); + ss_uint64_t volatile ones64 = ~ss_uint64_t(0); // count_bits_by_Kernighan_method (max) @@ -550,7 +550,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint32_t const v = ones32; + ss_uint32_t const v = ones32; STLSOFT_SUPPRESS_UNUSED(i); anchor_value += stlsoft::count_bits_by_Kernighan_method(v); @@ -563,7 +563,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint64_t const v = ones64; + ss_uint64_t const v = ones64; STLSOFT_SUPPRESS_UNUSED(i); anchor_value += stlsoft::count_bits_by_Kernighan_method(v); @@ -599,7 +599,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint32_t const v = ones32; + ss_uint32_t const v = ones32; STLSOFT_SUPPRESS_UNUSED(i); anchor_value += stlsoft::count_bits_by_8bit_table(v); @@ -612,7 +612,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint64_t const v = ones64; + ss_uint64_t const v = ones64; STLSOFT_SUPPRESS_UNUSED(i); anchor_value += stlsoft::count_bits_by_8bit_table(v); @@ -648,7 +648,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint32_t const v = ones32; + ss_uint32_t const v = ones32; STLSOFT_SUPPRESS_UNUSED(i); anchor_value += stlsoft::count_bits(v); @@ -661,7 +661,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint64_t const v = ones64; + ss_uint64_t const v = ones64; STLSOFT_SUPPRESS_UNUSED(i); anchor_value += stlsoft::count_bits(v); @@ -715,7 +715,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint32_t const v = ~uint32_t(0) ^ (uint32_t(1) << (i % 32u)); + ss_uint32_t const v = ~ss_uint32_t(0) ^ (ss_uint32_t(1) << (i % 32u)); anchor_value += stlsoft::count_bits_by_Kernighan_method(v); } @@ -727,7 +727,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint64_t const v = ~uint64_t(0) ^ (uint64_t(1) << (i % 64u)); + ss_uint64_t const v = ~ss_uint64_t(0) ^ (ss_uint64_t(1) << (i % 64u)); anchor_value += stlsoft::count_bits_by_Kernighan_method(v); } @@ -762,7 +762,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint32_t const v = ~uint32_t(0) ^ (uint32_t(1) << (i % 32u)); + ss_uint32_t const v = ~ss_uint32_t(0) ^ (ss_uint32_t(1) << (i % 32u)); anchor_value += stlsoft::count_bits_by_8bit_table(v); } @@ -774,7 +774,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint64_t const v = ~uint64_t(0) ^ (uint64_t(1) << (i % 64u)); + ss_uint64_t const v = ~ss_uint64_t(0) ^ (ss_uint64_t(1) << (i % 64u)); anchor_value += stlsoft::count_bits_by_8bit_table(v); } @@ -809,7 +809,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint32_t const v = ~uint32_t(0) ^ (uint32_t(1) << (i % 32u)); + ss_uint32_t const v = ~ss_uint32_t(0) ^ (ss_uint32_t(1) << (i % 32u)); anchor_value += stlsoft::count_bits(v); } @@ -821,7 +821,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint64_t const v = ~uint64_t(0) ^ (uint64_t(1) << (i % 64u)); + ss_uint64_t const v = ~ss_uint64_t(0) ^ (ss_uint64_t(1) << (i % 64u)); anchor_value += stlsoft::count_bits(v); } @@ -833,7 +833,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - int const v = static_cast(~uint32_t(0) ^ (uint32_t(1) << (i % 32u))); + int const v = static_cast(~ss_uint32_t(0) ^ (ss_uint32_t(1) << (i % 32u))); anchor_value += stlsoft::count_bits(v); } @@ -868,7 +868,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint8_t const v = static_cast(i & 0xff); + ss_uint8_t const v = static_cast(i & 0xff); anchor_value += stlsoft::find_highest_bit(v); } @@ -880,7 +880,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint16_t const v = static_cast(i & 0xffff); + ss_uint16_t const v = static_cast(i & 0xffff); anchor_value += stlsoft::find_highest_bit(v); } @@ -892,7 +892,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint32_t const v = i; + ss_uint32_t const v = i; anchor_value += stlsoft::find_highest_bit(v); } @@ -904,7 +904,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - uint64_t const v = i; + ss_uint64_t const v = i; anchor_value += stlsoft::find_highest_bit(v); } From c3c1c87789257d6ccb499b1fc64a359e2828fef3 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 24 Sep 2026 18:00:14 +1000 Subject: [PATCH 08/13] test(stlsoft.b): report median bit-function timings with a 64-bit anchor Time each row as one discarded warmup plus five samples and print the median. Accumulate the anchor in ss_uint64_t. Print STLSOFT_COMPILER_VERSION_STRING, whether optimisation is size, speed, or off, and whether this is a release or debug-asserts build. --- .../stlsoft/bit_functions/main.cpp | 470 ++++++++++++++---- 1 file changed, 385 insertions(+), 85 deletions(-) diff --git a/test/performance/stlsoft/bit_functions/main.cpp b/test/performance/stlsoft/bit_functions/main.cpp index 06c3eb5e..635161a9 100644 --- a/test/performance/stlsoft/bit_functions/main.cpp +++ b/test/performance/stlsoft/bit_functions/main.cpp @@ -64,6 +64,22 @@ using stlsoft::ss_uint32_t; using stlsoft::ss_uint64_t; +/* ///////////////////////////////////////////////////////////////////////// + * forward declarations + */ + +void +emit_result_row( + char const* function_name +, interval_t const* uint8_ns +, interval_t const* uint16_ns +, interval_t const* uint32_ns +, interval_t const* uint64_ns +, interval_t const* int_ns +, ss_uint64_t anchor_value +); + + /* ///////////////////////////////////////////////////////////////////////// * constants */ @@ -71,9 +87,126 @@ using stlsoft::ss_uint64_t; namespace { ss_size_t const NUM_ITERATIONS = 20000000; + ss_size_t const NUM_SAMPLES = 5; ss_size_t const XOR_RANGE_LEN = 64; int const FN_COL_WIDTH = 42; int const METRIC_COL_WIDTH = 16; + + + interval_t + median_sample( + interval_t const* samples + , ss_size_t n + ) + { + interval_t sorted[NUM_SAMPLES]; + + for (ss_size_t i = 0; n != i; ++i) + { + sorted[i] = samples[i]; + } + + for (ss_size_t i = 1; n != i; ++i) + { + interval_t const v = sorted[i]; + ss_size_t j = i; + + for (; 0 != j && sorted[j - 1] > v; --j) + { + sorted[j] = sorted[j - 1]; + } + + sorted[j] = v; + } + + return sorted[n / 2]; + } + + struct row_samples + { + interval_t u8[NUM_SAMPLES]; + interval_t u16[NUM_SAMPLES]; + interval_t u32[NUM_SAMPLES]; + interval_t u64[NUM_SAMPLES]; + interval_t si[NUM_SAMPLES]; + ss_size_t n; + ss_uint64_t anchor; + bool has_u8; + bool has_u16; + bool has_u32; + bool has_u64; + bool has_si; + + void clear() + { + n = 0; + anchor = 0; + has_u8 = false; + has_u16 = false; + has_u32 = false; + has_u64 = false; + has_si = false; + } + + void push( + interval_t const* uint8_ns + , interval_t const* uint16_ns + , interval_t const* uint32_ns + , interval_t const* uint64_ns + , interval_t const* int_ns + , ss_uint64_t anchor_value + ) + { + has_u8 = NULL != uint8_ns; + has_u16 = NULL != uint16_ns; + has_u32 = NULL != uint32_ns; + has_u64 = NULL != uint64_ns; + has_si = NULL != int_ns; + + if (has_u8) + { + u8[n] = *uint8_ns; + } + if (has_u16) + { + u16[n] = *uint16_ns; + } + if (has_u32) + { + u32[n] = *uint32_ns; + } + if (has_u64) + { + u64[n] = *uint64_ns; + } + if (has_si) + { + si[n] = *int_ns; + } + + anchor = anchor_value; + ++n; + } + + void emit(char const* function_name) const + { + interval_t const med_u8 = has_u8 ? median_sample(u8, n) : 0; + interval_t const med_u16 = has_u16 ? median_sample(u16, n) : 0; + interval_t const med_u32 = has_u32 ? median_sample(u32, n) : 0; + interval_t const med_u64 = has_u64 ? median_sample(u64, n) : 0; + interval_t const med_si = has_si ? median_sample(si, n) : 0; + + emit_result_row( + function_name + , has_u8 ? &med_u8 : NULL + , has_u16 ? &med_u16 : NULL + , has_u32 ? &med_u32 : NULL + , has_u64 ? &med_u64 : NULL + , has_si ? &med_si : NULL + , anchor + ); + } + }; } // anonymous namespace @@ -140,7 +273,7 @@ emit_result_row( , interval_t const* uint32_ns , interval_t const* uint64_ns , interval_t const* int_ns -, int anchor_value +, ss_uint64_t anchor_value ) { std::cout @@ -196,6 +329,44 @@ emit_result_row( std::cout << std::endl; } +void +emit_build_banner() +{ + std::cout << "compiler: " << STLSOFT_COMPILER_VERSION_STRING << std::endl; + std::cout << "optimisation: "; + +#if 0 +#elif defined(__OPTIMIZE_SIZE__) + + std::cout << "size"; +#elif defined(__OPTIMIZE__) + + std::cout << "speed"; +#elif defined(STLSOFT_COMPILER_IS_MSVC) && \ + !defined(STLSOFT_DEBUG) + + std::cout << "speed"; +#else + + std::cout << "off"; +#endif + +#ifdef NDEBUG + + std::cout << ", release"; +#else + + std::cout << ", debug-asserts"; +#endif + + std::cout << std::endl; + std::cout + << "samples: " + << NUM_SAMPLES + << " (median; 1 warmup discarded)" + << std::endl; +} + /* ///////////////////////////////////////////////////////////////////////// * main() @@ -216,15 +387,23 @@ int main(int /*argc*/, char* /*argv*/[]) xor64[i] = static_cast(i * 13u + 1u); } + row_samples rows; + + emit_build_banner(); emit_header_row(); // count_bits_by_Kernighan_method - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -251,11 +430,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_uint64 = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "count_bits_by_Kernighan_method()" - , NULL + rows.push( + NULL , NULL , &int_uint32 , &int_uint64 @@ -263,15 +441,25 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("count_bits_by_Kernighan_method()"); + } } // count_bits_by_8bit_table - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -298,11 +486,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_uint64 = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "count_bits_by_8bit_table()" - , NULL + rows.push( + NULL , NULL , &int_uint32 , &int_uint64 @@ -310,15 +497,25 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("count_bits_by_8bit_table()"); + } } // count_bits - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -357,11 +554,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_int = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "count_bits()" - , NULL + rows.push( + NULL , NULL , &int_uint32 , &int_uint64 @@ -369,6 +565,11 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("count_bits()"); + } } @@ -377,11 +578,16 @@ int main(int /*argc*/, char* /*argv*/[]) // ------------------------------------------------------------------ // count_bits_by_Kernighan_method (dense) - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -408,11 +614,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_uint64 = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "count_bits_by_Kernighan_method()/~i" - , NULL + rows.push( + NULL , NULL , &int_uint32 , &int_uint64 @@ -420,15 +625,25 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("count_bits_by_Kernighan_method()/~i"); + } } // count_bits_by_8bit_table (dense) - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -455,11 +670,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_uint64 = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "count_bits_by_8bit_table()/~i" - , NULL + rows.push( + NULL , NULL , &int_uint32 , &int_uint64 @@ -467,15 +681,25 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("count_bits_by_8bit_table()/~i"); + } } // count_bits (dense) - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -514,11 +738,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_int = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "count_bits()/~i" - , NULL + rows.push( + NULL , NULL , &int_uint32 , &int_uint64 @@ -526,6 +749,11 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("count_bits()/~i"); + } } @@ -540,11 +768,16 @@ int main(int /*argc*/, char* /*argv*/[]) // count_bits_by_Kernighan_method (max) - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -573,11 +806,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_uint64 = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "count_bits_by_Kernighan_method()/ones" - , NULL + rows.push( + NULL , NULL , &int_uint32 , &int_uint64 @@ -585,15 +817,25 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("count_bits_by_Kernighan_method()/ones"); + } } // count_bits_by_8bit_table (max) - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -622,11 +864,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_uint64 = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "count_bits_by_8bit_table()/ones" - , NULL + rows.push( + NULL , NULL , &int_uint32 , &int_uint64 @@ -634,15 +875,25 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("count_bits_by_8bit_table()/ones"); + } } // count_bits (max) - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -684,11 +935,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_int = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "count_bits()/ones" - , NULL + rows.push( + NULL , NULL , &int_uint32 , &int_uint64 @@ -696,6 +946,11 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("count_bits()/ones"); + } } } @@ -705,11 +960,16 @@ int main(int /*argc*/, char* /*argv*/[]) // ------------------------------------------------------------------ // count_bits_by_Kernighan_method (close-to-max) - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -736,11 +996,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_uint64 = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "count_bits_by_Kernighan_method()/ones^1" - , NULL + rows.push( + NULL , NULL , &int_uint32 , &int_uint64 @@ -748,15 +1007,25 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("count_bits_by_Kernighan_method()/ones^1"); + } } // count_bits_by_8bit_table (close-to-max) - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -783,11 +1052,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_uint64 = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "count_bits_by_8bit_table()/ones^1" - , NULL + rows.push( + NULL , NULL , &int_uint32 , &int_uint64 @@ -795,15 +1063,25 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("count_bits_by_8bit_table()/ones^1"); + } } // count_bits (close-to-max) - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -842,11 +1120,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_int = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "count_bits()/ones^1" - , NULL + rows.push( + NULL , NULL , &int_uint32 , &int_uint64 @@ -854,15 +1131,25 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("count_bits()/ones^1"); + } } // find_highest_bit - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -913,11 +1200,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_uint64 = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "find_highest_bit()" - , &int_uint8 + rows.push( + &int_uint8 , &int_uint16 , &int_uint32 , &int_uint64 @@ -925,15 +1211,25 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("find_highest_bit()"); + } } // calculate_xor_over_range - for (int W = 2; 0 != W; --W) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + stopwatch_t sw; - int anchor_value = 0; + ss_uint64_t anchor_value = 0; sw.start(); @@ -984,11 +1280,10 @@ int main(int /*argc*/, char* /*argv*/[]) interval_t const int_uint64 = sw.get_nanoseconds(); - if (1 == W) + if (W <= int(NUM_SAMPLES)) { - emit_result_row( - "calculate_xor_over_range()" - , &int_uint8 + rows.push( + &int_uint8 , &int_uint16 , &int_uint32 , &int_uint64 @@ -996,6 +1291,11 @@ int main(int /*argc*/, char* /*argv*/[]) , anchor_value ); } + + if (1 == W) + { + rows.emit("calculate_xor_over_range()"); + } } From 74b74407875100e0f1b6f5f7658a714c9a8d57b3 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 24 Sep 2026 18:04:53 +1000 Subject: [PATCH 09/13] fix(stlsoft): silence maybe-uninitialized in bit_functions median Zero-initialise the local sorted[] buffer in median_sample, guard empty n, and clamp n to NUM_SAMPLES so GCC -Werror=maybe-uninitialized no longer fails the performance harness on CI. --- test/performance/stlsoft/bit_functions/main.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/performance/stlsoft/bit_functions/main.cpp b/test/performance/stlsoft/bit_functions/main.cpp index 635161a9..e7b9ec9a 100644 --- a/test/performance/stlsoft/bit_functions/main.cpp +++ b/test/performance/stlsoft/bit_functions/main.cpp @@ -99,7 +99,17 @@ namespace { , ss_size_t n ) { - interval_t sorted[NUM_SAMPLES]; + interval_t sorted[NUM_SAMPLES] = {}; + + if (0 == n) + { + return 0; + } + + if (n > NUM_SAMPLES) + { + n = NUM_SAMPLES; + } for (ss_size_t i = 0; n != i; ++i) { From cbfe7bd15073e9c9507010bc6e253bf5baa2637a Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 24 Sep 2026 18:22:06 +1000 Subject: [PATCH 10/13] test(stlsoft.b): time more bit patterns and print per-call rates Add volatile-zero, rotating-bit, and both-halves-live rows for popcount and find_highest_bit, so the 64-bit high half is exercised. Print ns/call on the same line as the loop time, ns/element for XOR, and the anchor last. Honor SIS_PERFTESTS_GROUPGAPS with a blank line between operation and input-pattern groups. --- .../stlsoft/bit_functions/main.cpp | 1074 ++++++++++++++++- 1 file changed, 1022 insertions(+), 52 deletions(-) diff --git a/test/performance/stlsoft/bit_functions/main.cpp b/test/performance/stlsoft/bit_functions/main.cpp index e7b9ec9a..62f28045 100644 --- a/test/performance/stlsoft/bit_functions/main.cpp +++ b/test/performance/stlsoft/bit_functions/main.cpp @@ -41,7 +41,9 @@ #include #include +#include #include +#include /* ///////////////////////////////////////////////////////////////////////// @@ -77,6 +79,7 @@ emit_result_row( , interval_t const* uint64_ns , interval_t const* int_ns , ss_uint64_t anchor_value +, bool report_per_element ); @@ -198,7 +201,10 @@ namespace { ++n; } - void emit(char const* function_name) const + void emit( + char const* function_name + , bool report_per_element = false + ) const { interval_t const med_u8 = has_u8 ? median_sample(u8, n) : 0; interval_t const med_u16 = has_u16 ? median_sample(u16, n) : 0; @@ -214,6 +220,7 @@ namespace { , has_u64 ? &med_u64 : NULL , has_si ? &med_si : NULL , anchor + , report_per_element ); } }; @@ -260,6 +267,12 @@ emit_absent_metric_cell() emit_metric_cell("-"); } +void +emit_rate_cell( + interval_t const* total_ns +, double divisor +); + void emit_header_row() { @@ -271,10 +284,92 @@ emit_header_row() emit_metric_cell("uint32_t"); emit_metric_cell("uint64_t"); emit_metric_cell("int"); + emit_metric_cell("uint8_t ns"); + emit_metric_cell("uint16_t ns"); + emit_metric_cell("uint32_t ns"); + emit_metric_cell("uint64_t ns"); + emit_metric_cell("int ns"); + emit_metric_cell("uint8_t el"); + emit_metric_cell("uint16_t el"); + emit_metric_cell("uint32_t el"); + emit_metric_cell("uint64_t el"); emit_metric_cell("anchor"); std::cout << std::endl; } +bool +env_is_truey( + char const* name +) +{ + char const* const env = ::getenv(name); + + if (NULL == env || '\0' == *env) + { + return false; + } + + if (0 == ::strcmp(env, "1") || + 0 == ::strcmp(env, "ok") || + 0 == ::strcmp(env, "on") || + 0 == ::strcmp(env, "true") || + 0 == ::strcmp(env, "yes") || + 0 == ::strcmp(env, "y") || + 0 == ::strcmp(env, "OK") || + 0 == ::strcmp(env, "ON") || + 0 == ::strcmp(env, "TRUE") || + 0 == ::strcmp(env, "YES") || + 0 == ::strcmp(env, "Y")) + { + return true; + } + + return false; +} + +void +maybe_emit_group_gap( + char const* function_name +) +{ + static bool have_prev = false; + static char prev_key[64] = ""; + + if (!env_is_truey("SIS_PERFTESTS_GROUPGAPS")) + { + return; + } + + char const* const slash = ::strrchr(function_name, '/'); + char const* const pattern = (NULL == slash) ? "" : slash; + char const* family = "other"; + + if (0 == ::strncmp(function_name, "count_bits", 10)) + { + family = "count"; + } + else if (0 == ::strncmp(function_name, "find_highest_bit", 16)) + { + family = "find"; + } + else if (0 == ::strncmp(function_name, "calculate_xor", 13)) + { + family = "xor"; + } + + char key[64]; + + snprintf(key, sizeof(key), "%s%s", family, pattern); + + if (have_prev && 0 != ::strcmp(prev_key, key)) + { + std::cout << std::endl; + } + + snprintf(prev_key, sizeof(prev_key), "%s", key); + have_prev = true; +} + void emit_result_row( char const* function_name @@ -284,8 +379,11 @@ emit_result_row( , interval_t const* uint64_ns , interval_t const* int_ns , ss_uint64_t anchor_value +, bool report_per_element ) { + maybe_emit_group_gap(function_name); + std::cout << std::setw(FN_COL_WIDTH) << std::left << function_name ; @@ -335,10 +433,55 @@ emit_result_row( emit_absent_metric_cell(); } + double const per_call = double(NUM_ITERATIONS); + double const per_element = double(NUM_ITERATIONS) * double(XOR_RANGE_LEN + 1) / 2.0; + + emit_rate_cell(uint8_ns, per_call); + emit_rate_cell(uint16_ns, per_call); + emit_rate_cell(uint32_ns, per_call); + emit_rate_cell(uint64_ns, per_call); + emit_rate_cell(int_ns, per_call); + + if (report_per_element) + { + emit_rate_cell(uint8_ns, per_element); + emit_rate_cell(uint16_ns, per_element); + emit_rate_cell(uint32_ns, per_element); + emit_rate_cell(uint64_ns, per_element); + } + else + { + emit_absent_metric_cell(); + emit_absent_metric_cell(); + emit_absent_metric_cell(); + emit_absent_metric_cell(); + } + emit_metric_cell(thousands(anchor_value).c_str()); std::cout << std::endl; } +void +emit_rate_cell( + interval_t const* total_ns +, double divisor +) +{ + if (NULL == total_ns) + { + emit_absent_metric_cell(); + + return; + } + + char buf[32]; + double const rate = (0.0 == divisor) ? 0.0 : (double(*total_ns) / divisor); + + snprintf(buf, sizeof(buf), "%.3f", rate); + + emit_metric_cell(buf); +} + void emit_build_banner() { @@ -375,6 +518,12 @@ emit_build_banner() << NUM_SAMPLES << " (median; 1 warmup discarded)" << std::endl; + std::cout + << "width columns are loop ns; ns columns are ns/call; el columns are ns/element" + << std::endl; + std::cout + << "Env: SIS_PERFTESTS_GROUPGAPS." + << std::endl; } @@ -1149,82 +1298,903 @@ int main(int /*argc*/, char* /*argv*/[]) } - // find_highest_bit - for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + // ------------------------------------------------------------------ + // All-zero. Volatile so the call is not constant-folded to 0. + // ------------------------------------------------------------------ + { - if (int(NUM_SAMPLES) + 1 == W) + ss_uint32_t volatile zero32 = 0; + ss_uint64_t volatile zero64 = 0; + + + // count_bits_by_Kernighan_method (zero) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { - rows.clear(); - } + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } - stopwatch_t sw; + stopwatch_t sw; - ss_uint64_t anchor_value = 0; + ss_uint64_t anchor_value = 0; - sw.start(); - for (unsigned i = 0; NUM_ITERATIONS != i; ++i) - { - ss_uint8_t const v = static_cast(i & 0xff); + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = zero32; - anchor_value += stlsoft::find_highest_bit(v); - } - sw.stop(); + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); - interval_t const int_uint8 = sw.get_nanoseconds(); + interval_t const int_uint32 = sw.get_nanoseconds(); - sw.start(); - for (unsigned i = 0; NUM_ITERATIONS != i; ++i) - { - ss_uint16_t const v = static_cast(i & 0xffff); + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = zero64; - anchor_value += stlsoft::find_highest_bit(v); - } - sw.stop(); + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); - interval_t const int_uint16 = sw.get_nanoseconds(); + interval_t const int_uint64 = sw.get_nanoseconds(); - sw.start(); - for (unsigned i = 0; NUM_ITERATIONS != i; ++i) - { - ss_uint32_t const v = i; + if (W <= int(NUM_SAMPLES)) + { + rows.push( + NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } - anchor_value += stlsoft::find_highest_bit(v); + if (1 == W) + { + rows.emit("count_bits_by_Kernighan_method()/0"); + } } - sw.stop(); - - interval_t const int_uint32 = sw.get_nanoseconds(); - sw.start(); - for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + // count_bits_by_8bit_table (zero) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { - ss_uint64_t const v = i; + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } - anchor_value += stlsoft::find_highest_bit(v); - } - sw.stop(); + stopwatch_t sw; - interval_t const int_uint64 = sw.get_nanoseconds(); + ss_uint64_t anchor_value = 0; - if (W <= int(NUM_SAMPLES)) - { - rows.push( - &int_uint8 - , &int_uint16 - , &int_uint32 - , &int_uint64 - , NULL - , anchor_value - ); + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = zero32; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = zero64; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits_by_8bit_table()/0"); + } } - if (1 == W) + + // count_bits (zero) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { - rows.emit("find_highest_bit()"); + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = zero32; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = zero64; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + int const v = static_cast(zero32); + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_int = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + NULL + , NULL + , &int_uint32 + , &int_uint64 + , &int_int + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits()/0"); + } + } + } + + + // ------------------------------------------------------------------ + // Single rotating bit. The 64-bit shift reaches bits 32..63. + // ------------------------------------------------------------------ + + // count_bits_by_Kernighan_method (bit) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = ss_uint32_t(1) << (i % 32u); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = ss_uint64_t(1) << (i % 64u); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits_by_Kernighan_method()/bit"); + } + } + + + // count_bits_by_8bit_table (bit) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = ss_uint32_t(1) << (i % 32u); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = ss_uint64_t(1) << (i % 64u); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits_by_8bit_table()/bit"); + } + } + + + // count_bits (bit) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = ss_uint32_t(1) << (i % 32u); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = ss_uint64_t(1) << (i % 64u); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + int const v = static_cast(ss_uint32_t(1) << (i % 32u)); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_int = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + NULL + , NULL + , &int_uint32 + , &int_uint64 + , &int_int + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits()/bit"); + } + } + + + // ------------------------------------------------------------------ + // Both halves live. uint32 high 16 bits vary; uint64 high half is i. + // ------------------------------------------------------------------ + + // count_bits_by_Kernighan_method (wide) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = (ss_uint32_t(i) << 16) | ss_uint32_t(i & 0xffffu); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = (ss_uint64_t(i) << 32) | ss_uint64_t(i); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits_by_Kernighan_method()/wide"); + } + } + + + // count_bits_by_8bit_table (wide) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = (ss_uint32_t(i) << 16) | ss_uint32_t(i & 0xffffu); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = (ss_uint64_t(i) << 32) | ss_uint64_t(i); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + NULL + , NULL + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits_by_8bit_table()/wide"); + } + } + + + // count_bits (wide) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = (ss_uint32_t(i) << 16) | ss_uint32_t(i & 0xffffu); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = (ss_uint64_t(i) << 32) | ss_uint64_t(i); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + int const v = static_cast((ss_uint32_t(i) << 16) | ss_uint32_t(i & 0xffffu)); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_int = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + NULL + , NULL + , &int_uint32 + , &int_uint64 + , &int_int + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits()/wide"); + } + } + + + // find_highest_bit + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = static_cast(i & 0xff); + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = static_cast(i & 0xffff); + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = i; + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = i; + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("find_highest_bit()"); + } + } + + + // ------------------------------------------------------------------ + // find_highest_bit: zero, one rotating bit, both halves live. + // ------------------------------------------------------------------ + + { + ss_uint8_t volatile zero8 = 0; + ss_uint16_t volatile zero16 = 0; + ss_uint32_t volatile zero32 = 0; + ss_uint64_t volatile zero64 = 0; + + + // find_highest_bit (zero) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = zero8; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = zero16; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = zero32; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = zero64; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("find_highest_bit()/0"); + } + } + } + + + // find_highest_bit (bit) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = static_cast(1u << (i % 8u)); + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = static_cast(1u << (i % 16u)); + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = ss_uint32_t(1) << (i % 32u); + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = ss_uint64_t(1) << (i % 64u); + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("find_highest_bit()/bit"); + } + } + + + // find_highest_bit (wide) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = static_cast(i & 0xff); + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = static_cast(i & 0xffff); + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = (ss_uint32_t(i) << 16) | ss_uint32_t(i & 0xffffu); + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = (ss_uint64_t(i) << 32) | ss_uint64_t(i); + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("find_highest_bit()/wide"); } } @@ -1304,7 +2274,7 @@ int main(int /*argc*/, char* /*argv*/[]) if (1 == W) { - rows.emit("calculate_xor_over_range()"); + rows.emit("calculate_xor_over_range()", true); } } From aeab8dbfc81dedf977fb5ae85eaaa95c549c527b Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Thu, 24 Sep 2026 18:38:05 +1000 Subject: [PATCH 11/13] docs(stlsoft.b): record bit-function timings and the default choice Keep the 8-bit table as the default for count_bits(). Apple Clang rewrites Kernighan into a hardware popcount; Linux and Windows compilers execute the source loop and the table is faster there. --- strategy/BIT_FUNCTIONS.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 strategy/BIT_FUNCTIONS.md diff --git a/strategy/BIT_FUNCTIONS.md b/strategy/BIT_FUNCTIONS.md new file mode 100644 index 00000000..e69de29b From 286f88deb9fe62a45f38042727ff4c77fb6594c5 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Fri, 25 Sep 2026 11:57:35 +1000 Subject: [PATCH 12/13] feat(stlsoft): bit-function intrinsics for 1.11.1-rc7 Add unselected `count_bits_by_intrinsic()` / `find_highest_bit_by_intrinsic()`, with external/internal bitfns detection (GCC/Clang builtins preferred over MSVC), 8/16-bit count overloads, Clang `STLSOFT_CLANG_VER`, unit and performance coverage, and strategy notes that keep the 8-bit table as the `count_bits()` default; --- CHANGES.md | 11 + NEWS.md | 1 + include/stlsoft/api/external/bitfns.h | 467 +++++ include/stlsoft/api/internal/bitfns.h | 170 ++ include/stlsoft/internal/cccap/clang.h | 48 +- include/stlsoft/internal/cccap/gcc.h | 9 + include/stlsoft/stlsoft.h | 9 +- include/stlsoft/util/bits/count_functions.h | 288 ++- include/stlsoft/util/bits/test_functions.h | 218 +- strategy/BIT_FUNCTIONS.md | 96 + .../stlsoft/bit_functions/main.cpp | 1791 +++++++++++++++-- .../entry.cpp | 264 ++- test/unit/test.unit.versions/entry.cpp | 4 +- 13 files changed, 3200 insertions(+), 176 deletions(-) create mode 100644 include/stlsoft/api/external/bitfns.h create mode 100644 include/stlsoft/api/internal/bitfns.h diff --git a/CHANGES.md b/CHANGES.md index edead870..7a74078a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,17 @@ # STLSoft - CHANGES +## 1.11.1-rc7 - 25th September 2026 + +* Added `count_bits_by_intrinsic()` and `find_highest_bit_by_intrinsic()` (unselected by default; `count_bits()` keeps the 8-bit table; `find_highest_bit()` keeps the scan); +* Added **stlsoft/api/external/bitfns.h** and **stlsoft/api/internal/bitfns.h** — detection and adaptations for `popcount` / `clz` / `_BitScanReverse` intrinsics (GCC/Clang builtins preferred over MSVC when both are visible); +* Clang cccap — `STLSOFT_CLANG_VER`; documented shared `STLSOFT_GCC_VER` (also noted in GCC cccap); +* Fixed `count_bits()` overloads when `STLSOFT_BIT_COUNT_BY_Kernighan` is defined; +* Unit tests for intrinsic popcount / highest-bit helpers; broader width coverage for bit-function overloads; +* Performance suite **test/performance/stlsoft/bit_functions** (density patterns, intrinsic rows, median / ns-per-call reporting); +* Strategy note **strategy/BIT_FUNCTIONS.md** — measurements and default choice (table for `count_bits()`; keep intrinsics unselected pending cross-toolchain evidence); + + ## 1.11.1-rc6 - 21st September 2026 * CMake/CI — optional **ACE** discovery (**cmake/FindACE.cmake**, **NO_ACE** / **prepare_cmake.sh --no-ace**); dedicated **cell-ace** job (**with-ace**); diff --git a/NEWS.md b/NEWS.md index 4c133f6c..911b3a39 100644 --- a/NEWS.md +++ b/NEWS.md @@ -3,6 +3,7 @@ | Date | News Item | Details | | ------------------- | ----------------------------------- | ------- | +| 25th September 2026 | Release of [STLSoft 1.11.1-rc7](https://github.com/synesissoftware/STLSoft/releases/tag/1.11.1-rc7) | bit-function intrinsics; table default for `count_bits()` | | 21st September 2026 | Release of [STLSoft 1.11.1-rc6](https://github.com/synesissoftware/STLSoft/releases/tag/1.11.1-rc6) | ACE/ATL/MFC CI; Clang/macOS `stricmp` aliases | | 4th August 2026 | Release of [STLSoft 1.11.1-rc5](https://github.com/synesissoftware/STLSoft/releases/tag/1.11.1-rc5) | `basic_simple_string<>#resize()`; **CHANGES.md**; docs | | 29th July 2026 | Release of [STLSoft 1.11.1-rc4](https://github.com/synesissoftware/STLSoft/releases/tag/1.11.1-rc4) | GitHub Actions CI; VC++ 18.x; MinGW | diff --git a/include/stlsoft/api/external/bitfns.h b/include/stlsoft/api/external/bitfns.h new file mode 100644 index 00000000..5a43fecc --- /dev/null +++ b/include/stlsoft/api/external/bitfns.h @@ -0,0 +1,467 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: stlsoft/api/external/bitfns.h + * + * Purpose: External adaptations for bit functions. + * + * Created: 24th September 2026 + * Updated: 25th September 2026 + * + * Home: http://stlsoft.org/ + * + * Copyright (c) 2026, Matthew Wilson and Synesis Information Systems + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name(s) of Matthew Wilson and Synesis Information Systems + * nor the names of any contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* WARNING: this file contains undocumented external features that are + * subject to change at any time, so if you use them it is at your own risk. + */ + +#ifndef STLSOFT_INCL_STLSOFT_API_external_h_bitfns +#define STLSOFT_INCL_STLSOFT_API_external_h_bitfns + + +/* ///////////////////////////////////////////////////////////////////////// + * includes - 1 + */ + +#ifndef STLSOFT_INCL_STLSOFT_H_STLSOFT +# include +#endif /* !STLSOFT_INCL_STLSOFT_H_STLSOFT */ +#ifdef STLSOFT_TRACE_INCLUDE +# pragma message(__FILE__) +#endif /* STLSOFT_TRACE_INCLUDE */ + + +/* ///////////////////////////////////////////////////////////////////////// + * intrinsics support + * + * Detection follows p99/src/p99_portable.h. GCC and Clang builtins are + * preferred over the MSVC intrinsics when both are visible (clang-cl). + * + * Detected features/macros: + * + * 1. "Count leading zeros" + * - discriminated symbols: + * - STLSOFT_CF__BitScanReverse_SUPPORT + * - STLSOFT_CF__BitScanReverse64_SUPPORT + * - STLSOFT_CF__builtin_clz_SUPPORT + * - STLSOFT_CF__builtin_clzll_SUPPORT + * + * - Clang/GCC may provide: + * - int __builtin_clz(unsigned int) + * - int __builtin_clzll(unsigned long long) + * + * - VC++ may provide: + * - unsigned char _BitScanReverse(unsigned long*, unsigned long) + * - unsigned char _BitScanReverse64(unsigned long*, unsigned __int64) + * + * NOTE: _BitScanReverse(64)() have different semantics to clz(ll) in + * that the former find the index of the most significant bit and the + * latter find the number of leading zeros. + * + * 2. "Population count" + * + * - discriminated symbols: + * - STLSOFT_CF__builtin_popcount_SUPPORT + * - STLSOFT_CF__builtin_popcountll_SUPPORT + * - STLSOFT_CF__popcnt16_SUPPORT + * - STLSOFT_CF__popcnt_SUPPORT + * - STLSOFT_CF__popcnt64_SUPPORT + * + * - Clang/GCC may provide: + * - int __builtin_popcount(unsigned int) + * - int __builtin_popcountll(unsigned long long) + * + * - VC++ may provide: + * - unsigned short __popcnt16(unsigned short) + * - unsigned int __popcnt(unsigned int) + * - unsigned __int64 __popcnt64(unsigned __int64) + */ + + +/* ///////////////////////////////////////////////////////// + * reject any other definition of the symbols under purview + */ + +#ifdef STLSOFT_CF__BitScanReverse_SUPPORT +# error STLSOFT_CF__BitScanReverse_SUPPORT may not be defined +#endif /* STLSOFT_CF__BitScanReverse_SUPPORT */ +#ifdef STLSOFT_CF__BitScanReverse64_SUPPORT +# error STLSOFT_CF__BitScanReverse64_SUPPORT may not be defined +#endif /* STLSOFT_CF__BitScanReverse64_SUPPORT */ + +#ifdef STLSOFT_CF__builtin_clz_SUPPORT +# error STLSOFT_CF__builtin_clz_SUPPORT may not be defined +#endif /* STLSOFT_CF__builtin_clz_SUPPORT */ +#ifdef STLSOFT_CF__builtin_clzll_SUPPORT +# error STLSOFT_CF__builtin_clzll_SUPPORT may not be defined +#endif /* STLSOFT_CF__builtin_clzll_SUPPORT */ + +#ifdef STLSOFT_CF__builtin_popcount_SUPPORT +# error STLSOFT_CF__builtin_popcount_SUPPORT may not be defined +#endif /* STLSOFT_CF__builtin_popcount_SUPPORT */ +#ifdef STLSOFT_CF__builtin_popcountll_SUPPORT +# error STLSOFT_CF__builtin_popcountll_SUPPORT may not be defined +#endif /* STLSOFT_CF__builtin_popcountll_SUPPORT */ + +#ifdef STLSOFT_CF__popcnt16_SUPPORT +# error STLSOFT_CF__popcnt16_SUPPORT may not be defined +#endif /* STLSOFT_CF__popcnt16_SUPPORT */ +#ifdef STLSOFT_CF__popcnt_SUPPORT +# error STLSOFT_CF__popcnt_SUPPORT may not be defined +#endif /* STLSOFT_CF__popcnt_SUPPORT */ +#ifdef STLSOFT_CF__popcnt64_SUPPORT +# error STLSOFT_CF__popcnt64_SUPPORT may not be defined +#endif /* STLSOFT_CF__popcnt64_SUPPORT */ + + +/* ///////////////////////////////////////////////////////// + * "Count leading zeros" + * + * - STLSOFT_CF__BitScanReverse_SUPPORT + * - STLSOFT_CF__BitScanReverse64_SUPPORT + * - STLSOFT_CF__builtin_clz_SUPPORT + * - STLSOFT_CF__builtin_clzll_SUPPORT + */ + + +/* STLSOFT_CF__BitScanReverse_SUPPORT, STLSOFT_CF__BitScanReverse64_SUPPORT */ + +#if 0 +#elif defined(_MSC_VER) &&\ + _MSC_VER >= 1400 + +# define STLSOFT_CF__BitScanReverse_SUPPORT +# if 0 ||\ + defined(_M_ARM64) ||\ + defined(_M_ARM64EC) ||\ + defined(_M_X64) ||\ + 0 + +# define STLSOFT_CF__BitScanReverse64_SUPPORT +# endif +#endif + + +/* STLSOFT_CF__builtin_clz_SUPPORT, STLSOFT_CF__builtin_clzll_SUPPORT */ + +#if 0 +#elif defined(__has_builtin) + +# if __has_builtin(__builtin_clz) + +# define STLSOFT_CF__builtin_clz_SUPPORT +# endif + +# if __has_builtin(__builtin_clzll) + +# define STLSOFT_CF__builtin_clzll_SUPPORT +# endif +#endif + +#ifndef STLSOFT_CF__builtin_clz_SUPPORT + +# if 0 +# elif defined(STLSOFT_CLANG_VER) &&\ + STLSOFT_CLANG_VER >= 40000 + +# define STLSOFT_CF__builtin_clz_SUPPORT +# elif defined(STLSOFT_GCC_VER) &&\ + STLSOFT_GCC_VER >= 30400 + +# define STLSOFT_CF__builtin_clz_SUPPORT +# endif +#endif + +#ifndef STLSOFT_CF__builtin_clzll_SUPPORT + +# if 0 +# elif defined(STLSOFT_CLANG_VER) &&\ + STLSOFT_CLANG_VER >= 40000 + +# define STLSOFT_CF__builtin_clzll_SUPPORT +# elif defined(STLSOFT_GCC_VER) &&\ + STLSOFT_GCC_VER >= 30400 + +# define STLSOFT_CF__builtin_clzll_SUPPORT +# endif +#endif + + +/* ///////////////////////////////////////////////////////// + * "Population count" + * + * - STLSOFT_CF__builtin_popcount_SUPPORT + * - STLSOFT_CF__builtin_popcountll_SUPPORT + * - STLSOFT_CF__popcnt16_SUPPORT + * - STLSOFT_CF__popcnt_SUPPORT + * - STLSOFT_CF__popcnt64_SUPPORT + */ + +/* STLSOFT_CF__builtin_popcount_SUPPORT, STLSOFT_CF__builtin_popcountll_SUPPORT */ + +#if 0 +#elif defined(__has_builtin) + +# if __has_builtin(__builtin_popcount) + +# define STLSOFT_CF__builtin_popcount_SUPPORT +# endif + +# if __has_builtin(__builtin_popcountll) + +# define STLSOFT_CF__builtin_popcountll_SUPPORT +# endif +#endif + +#ifndef STLSOFT_CF__builtin_popcount_SUPPORT + +# if 0 +# elif defined(STLSOFT_CLANG_VER) &&\ + STLSOFT_CLANG_VER >= 40000 + +# define STLSOFT_CF__builtin_popcount_SUPPORT +# elif defined(STLSOFT_GCC_VER) &&\ + STLSOFT_GCC_VER >= 30400 + +# define STLSOFT_CF__builtin_popcount_SUPPORT +# endif +#endif + +#ifndef STLSOFT_CF__builtin_popcountll_SUPPORT + +# if 0 +# elif defined(STLSOFT_CLANG_VER) &&\ + STLSOFT_CLANG_VER >= 40000 + +# define STLSOFT_CF__builtin_popcountll_SUPPORT +# elif defined(STLSOFT_GCC_VER) &&\ + STLSOFT_GCC_VER >= 30400 + +# define STLSOFT_CF__builtin_popcountll_SUPPORT +# endif +#endif + + +/* STLSOFT_CF__popcnt16_SUPPORT, STLSOFT_CF__popcnt_SUPPORT + * + * x86 and x64 from MSVC 15.00. ARM64 from Visual Studio 2022 17.11 + * (_MSC_VER 1941). Not 32-bit ARM. Not ARM64EC: those two intrinsics + * are declared for x86, x64, and ARM64 only. + */ + +#if 0 +#elif 1 &&\ + defined(_MSC_VER) &&\ + _MSC_VER >= 1500 &&\ + ( 0 ||\ + defined(_M_IX86) ||\ + defined(_M_X64) ||\ + 0) &&\ + 1 + +# define STLSOFT_CF__popcnt16_SUPPORT +# define STLSOFT_CF__popcnt_SUPPORT +#elif 1 &&\ + defined(_MSC_VER) &&\ + _MSC_VER >= 1941 &&\ + defined(_M_ARM64) &&\ + 1 + +# define STLSOFT_CF__popcnt16_SUPPORT +# define STLSOFT_CF__popcnt_SUPPORT +#endif + + +/* STLSOFT_CF__popcnt64_SUPPORT + * + * x64 from MSVC 15.00. ARM64 and ARM64EC from Visual Studio 2022 17.11 + * (_MSC_VER 1941). + */ + +#if 0 +#elif defined(_MSC_VER) &&\ + _MSC_VER >= 1500 &&\ + defined(_M_X64) + +# define STLSOFT_CF__popcnt64_SUPPORT +#elif defined(_MSC_VER) &&\ + _MSC_VER >= 1941 &&\ + ( 0 ||\ + defined(_M_ARM64) ||\ + defined(_M_ARM64EC) ||\ + 0) + +# define STLSOFT_CF__popcnt64_SUPPORT +#endif + + +/* ///////////////////////////////////////////////////////////////////////// + * includes - 2 + */ + +#if 0 +#elif 0 ||\ + defined(STLSOFT_CF__popcnt16_SUPPORT) ||\ + defined(STLSOFT_CF__popcnt_SUPPORT) ||\ + defined(STLSOFT_CF__popcnt64_SUPPORT) ||\ + defined(STLSOFT_CF__BitScanReverse_SUPPORT) ||\ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) ||\ + 0 + +# include +#endif + + +/* ///////////////////////////////////////////////////////////////////////// + * bit functions + */ + +#ifndef STLSOFT_API_EXTERNAL_bitfns_BitScanReverse_uint32 + +# if 0 +# elif defined(STLSOFT_CF__BitScanReverse_SUPPORT) + +# define STLSOFT_API_EXTERNAL_bitfns_BitScanReverse_uint32 _BitScanReverse +# else + +# endif +#endif /* !STLSOFT_API_EXTERNAL_bitfns_BitScanReverse_uint32 */ + + +#ifndef STLSOFT_API_EXTERNAL_bitfns_BitScanReverse_uint64 + +# if 0 +# elif defined(STLSOFT_CF__BitScanReverse64_SUPPORT) + +# define STLSOFT_API_EXTERNAL_bitfns_BitScanReverse_uint64 _BitScanReverse64 +# else + +# endif +#endif /* !STLSOFT_API_EXTERNAL_bitfns_BitScanReverse_uint64 */ + + +#ifndef STLSOFT_API_EXTERNAL_bitfns_clz_uint + +# if 0 +# elif defined(STLSOFT_CF__builtin_clz_SUPPORT) + +# define STLSOFT_API_EXTERNAL_bitfns_clz_uint __builtin_clz +# else + +# endif +#endif /* !STLSOFT_API_EXTERNAL_bitfns_clz_uint */ + + +#ifndef STLSOFT_API_EXTERNAL_bitfns_clz_ullong + +# if 0 +# elif defined(STLSOFT_CF__builtin_clzll_SUPPORT) + +# define STLSOFT_API_EXTERNAL_bitfns_clz_ullong __builtin_clzll +# else + +# endif +#endif /* !STLSOFT_API_EXTERNAL_bitfns_clz_ullong */ + + +#ifndef STLSOFT_API_EXTERNAL_bitfns_popcount_uint + +# if 0 +# elif defined(STLSOFT_CF__builtin_popcount_SUPPORT) + +# define STLSOFT_API_EXTERNAL_bitfns_popcount_uint __builtin_popcount +# else + +# endif +#endif /* !STLSOFT_API_EXTERNAL_bitfns_popcount_uint */ + + +#ifndef STLSOFT_API_EXTERNAL_bitfns_popcount_ullong + +# if 0 +# elif defined(STLSOFT_CF__builtin_popcountll_SUPPORT) + +# define STLSOFT_API_EXTERNAL_bitfns_popcount_ullong __builtin_popcountll +# else + +# endif +#endif /* !STLSOFT_API_EXTERNAL_bitfns_popcount_ullong */ + + +#ifndef STLSOFT_API_EXTERNAL_bitfns_popcnt_uint16 + +# if 0 +# elif defined(STLSOFT_CF__popcnt16_SUPPORT) + +# define STLSOFT_API_EXTERNAL_bitfns_popcnt_uint16 __popcnt16 +# else + +# endif +#endif /* !STLSOFT_API_EXTERNAL_bitfns_popcnt_uint16 */ + + +#ifndef STLSOFT_API_EXTERNAL_bitfns_popcnt_uint32 + +# if 0 +# elif defined(STLSOFT_CF__popcnt_SUPPORT) + +# define STLSOFT_API_EXTERNAL_bitfns_popcnt_uint32 __popcnt +# else + +# endif +#endif /* !STLSOFT_API_EXTERNAL_bitfns_popcnt_uint32 */ + + +#ifndef STLSOFT_API_EXTERNAL_bitfns_popcnt_uint64 + +# if 0 +# elif defined(STLSOFT_CF__popcnt64_SUPPORT) + +# define STLSOFT_API_EXTERNAL_bitfns_popcnt_uint64 __popcnt64 +# else + +# endif +#endif /* !STLSOFT_API_EXTERNAL_bitfns_popcnt_uint64 */ + + +/* ///////////////////////////////////////////////////////////////////////// + * inclusion control + */ + +#ifdef STLSOFT_CF_PRAGMA_ONCE_SUPPORT +# pragma once +#endif /* STLSOFT_CF_PRAGMA_ONCE_SUPPORT */ + +#endif /* !STLSOFT_INCL_STLSOFT_API_external_h_bitfns */ + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/include/stlsoft/api/internal/bitfns.h b/include/stlsoft/api/internal/bitfns.h new file mode 100644 index 00000000..bb90c9f4 --- /dev/null +++ b/include/stlsoft/api/internal/bitfns.h @@ -0,0 +1,170 @@ +/* ///////////////////////////////////////////////////////////////////////// + * File: stlsoft/api/internal/bitfns.h + * + * Purpose: Internal adaptations for bit functions. + * + * Created: 24th September 2026 + * Updated: 25th September 2026 + * + * Home: http://stlsoft.org/ + * + * Copyright (c) 2026, Matthew Wilson and Synesis Information Systems + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * - Neither the name(s) of Matthew Wilson and Synesis Information Systems + * nor the names of any contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ////////////////////////////////////////////////////////////////////// */ + + +/* WARNING: this file contains undocumented internal features that are + * subject to change at any time, so if you use them it is at your own risk. + */ + +#ifndef STLSOFT_INCL_STLSOFT_API_internal_h_bitfns +#define STLSOFT_INCL_STLSOFT_API_internal_h_bitfns + + +/* ///////////////////////////////////////////////////////////////////////// + * includes + */ + +#ifndef STLSOFT_INCL_STLSOFT_H_STLSOFT +# include +#endif /* !STLSOFT_INCL_STLSOFT_H_STLSOFT */ +#ifdef STLSOFT_TRACE_INCLUDE +# pragma message(__FILE__) +#endif /* STLSOFT_TRACE_INCLUDE */ + +#ifndef STLSOFT_INCL_STLSOFT_API_external_h_bitfns +# include +#endif /* !STLSOFT_INCL_STLSOFT_API_external_h_bitfns */ + + +/* ///////////////////////////////////////////////////////////////////////// + * bit functions + */ + +#ifndef STLSOFT_API_INTERNAL_bitfns_popcount_uint16 + +# if 0 ||\ + defined (STLSOFT_API_EXTERNAL_bitfns_popcount_uint) ||\ + defined (STLSOFT_API_EXTERNAL_bitfns_popcnt_uint16) ||\ + 0 + +STLSOFT_INLINE +int +STLSOFT_API_INTERNAL_bitfns_popcount_uint16( + STLSOFT_NS_QUAL(ss_uint16_t) v +) +{ +# if 0 +# elif defined(STLSOFT_API_EXTERNAL_bitfns_popcount_uint) + + return STLSOFT_API_EXTERNAL_bitfns_popcount_uint(STLSOFT_C_CAST(unsigned int, v)); +# elif defined(STLSOFT_API_EXTERNAL_bitfns_popcnt_uint16) + + return STLSOFT_API_EXTERNAL_bitfns_popcnt_uint16(STLSOFT_C_CAST(unsigned short, v)); +# else + +# error Unexpected +# endif +} +# define STLSOFT_API_INTERNAL_bitfns_popcount_uint16 STLSOFT_API_INTERNAL_bitfns_popcount_uint16 +# endif +#endif + +#ifndef STLSOFT_API_INTERNAL_bitfns_popcount_uint32 + +# if 0 ||\ + defined (STLSOFT_API_EXTERNAL_bitfns_popcount_uint) ||\ + defined (STLSOFT_API_EXTERNAL_bitfns_popcnt_uint32) ||\ + 0 + +STLSOFT_INLINE +int +STLSOFT_API_INTERNAL_bitfns_popcount_uint32( + STLSOFT_NS_QUAL(ss_uint32_t) v +) +{ +# if 0 +# elif defined(STLSOFT_API_EXTERNAL_bitfns_popcount_uint) + + return STLSOFT_API_EXTERNAL_bitfns_popcount_uint(STLSOFT_C_CAST(unsigned int, v)); +# elif defined(STLSOFT_API_EXTERNAL_bitfns_popcnt_uint32) + + return STLSOFT_API_EXTERNAL_bitfns_popcnt_uint32(STLSOFT_C_CAST(unsigned int, v)); +# else + +# error Unexpected +# endif +} +# define STLSOFT_API_INTERNAL_bitfns_popcount_uint32 STLSOFT_API_INTERNAL_bitfns_popcount_uint32 +# endif +#endif + +#ifndef STLSOFT_API_INTERNAL_bitfns_popcount_uint64 + +# if 0 ||\ + defined (STLSOFT_API_EXTERNAL_bitfns_popcount_ullong) ||\ + defined (STLSOFT_API_EXTERNAL_bitfns_popcnt_uint64) ||\ + 0 + +STLSOFT_INLINE +int +STLSOFT_API_INTERNAL_bitfns_popcount_uint64( + STLSOFT_NS_QUAL(ss_uint64_t) v +) +{ +# if 0 +# elif defined(STLSOFT_API_EXTERNAL_bitfns_popcount_ullong) + + return STLSOFT_API_EXTERNAL_bitfns_popcount_ullong(STLSOFT_C_CAST(unsigned long long, v)); +# elif defined(STLSOFT_API_EXTERNAL_bitfns_popcnt_uint64) + + return STLSOFT_API_EXTERNAL_bitfns_popcnt_uint64(STLSOFT_C_CAST(unsigned __int64, v)); +# else + +# error Unexpected +# endif +} +# define STLSOFT_API_INTERNAL_bitfns_popcount_uint64 STLSOFT_API_INTERNAL_bitfns_popcount_uint64 +# endif +#endif + + +/* ///////////////////////////////////////////////////////////////////////// + * inclusion control + */ + +#ifdef STLSOFT_CF_PRAGMA_ONCE_SUPPORT +# pragma once +#endif /* STLSOFT_CF_PRAGMA_ONCE_SUPPORT */ + +#endif /* !STLSOFT_INCL_STLSOFT_API_internal_h_bitfns */ + +/* ///////////////////////////// end of file //////////////////////////// */ + diff --git a/include/stlsoft/internal/cccap/clang.h b/include/stlsoft/internal/cccap/clang.h index 0e049b4a..bbf78d32 100644 --- a/include/stlsoft/internal/cccap/clang.h +++ b/include/stlsoft/internal/cccap/clang.h @@ -4,7 +4,7 @@ * Purpose: Compiler feature discrimination for Clang C/C++. * * Created: 14th March 2015 - * Updated: 20th September 2026 + * Updated: 25th September 2026 * * Home: http://stlsoft.org/ * @@ -59,9 +59,9 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define STLSOFT_VER_H_STLSOFT_CCCAP_CLANG_MAJOR 1 -# define STLSOFT_VER_H_STLSOFT_CCCAP_CLANG_MINOR 11 -# define STLSOFT_VER_H_STLSOFT_CCCAP_CLANG_REVISION 2 -# define STLSOFT_VER_H_STLSOFT_CCCAP_CLANG_EDIT 33 +# define STLSOFT_VER_H_STLSOFT_CCCAP_CLANG_MINOR 12 +# define STLSOFT_VER_H_STLSOFT_CCCAP_CLANG_REVISION 1 +# define STLSOFT_VER_H_STLSOFT_CCCAP_CLANG_EDIT 34 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -101,6 +101,46 @@ * custom macros */ +#define STLSOFT_CLANG_VER \ + \ + ( \ + (__clang_major__ * 10000) \ + + \ + (__clang_minor__ * 100) \ + + \ + (__clang_patchlevel__ * 1) \ + ) + +#ifdef __GNUC__ + +# ifdef __GNUC_PATCHLEVEL__ + +# define STLSOFT_INTERNAL_GCC_PATCHLEVEL_ __GNUC_PATCHLEVEL__ +# else /* ? __GNUC_PATCHLEVEL__ */ + +# define STLSOFT_INTERNAL_GCC_PATCHLEVEL_ (0) +# endif /* __GNUC_PATCHLEVEL__ */ + +/** \def STLSOFT_GCC_VER A composite version object-like macro comprising + * the major*10000 + minor*100 + patch, e.g. 30401 (3.4.1). + * + * \note Because several compilers (e.g. Clang) may offer GCC compatibility, + * the presence of this symbol does NOT, in and of itself, mean that the + * compiler is actually GCC. Rather, code must first check for definition + * of STLSOFT_COMPILER_IS_GCC. + * + */ +# define STLSOFT_GCC_VER \ + \ + ( \ + (__GNUC__ * 10000) \ + + \ + (__GNUC_MINOR__ * 100) \ + + \ + (STLSOFT_INTERNAL_GCC_PATCHLEVEL_ * 1) \ + ) +#endif /* __GNUC__ */ + /* ///////////////////////////////////////////////////////////////////////// * preprocessor features diff --git a/include/stlsoft/internal/cccap/gcc.h b/include/stlsoft/internal/cccap/gcc.h index 06b90aee..3e6e759f 100644 --- a/include/stlsoft/internal/cccap/gcc.h +++ b/include/stlsoft/internal/cccap/gcc.h @@ -110,6 +110,15 @@ # define STLSOFT_INTERNAL_GCC_PATCHLEVEL_ (0) #endif /* __GNUC_PATCHLEVEL__ */ +/** \def STLSOFT_GCC_VER A composite version object-like macro comprising + * the major*10000 + minor*100 + patch, e.g. 30401 (3.4.1). + * + * \note Because several compilers (e.g. Clang) may offer GCC compatibility, + * the presence of this symbol does NOT, in and of itself, mean that the + * compiler is actually GCC. Rather, code must first check for definition + * of STLSOFT_COMPILER_IS_GCC. + * + */ #define STLSOFT_GCC_VER \ \ ( \ diff --git a/include/stlsoft/stlsoft.h b/include/stlsoft/stlsoft.h index 1d7130cb..80b0b806 100644 --- a/include/stlsoft/stlsoft.h +++ b/include/stlsoft/stlsoft.h @@ -5,7 +5,7 @@ * and platform discriminations, and definitions of types. * * Created: 15th January 2002 - * Updated: 21st September 2026 + * Updated: 25th September 2026 * * Home: http://stlsoft.org/ * @@ -55,8 +55,8 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define STLSOFT_VER_STLSOFT_H_STLSOFT_MAJOR 3 # define STLSOFT_VER_STLSOFT_H_STLSOFT_MINOR 57 -# define STLSOFT_VER_STLSOFT_H_STLSOFT_REVISION 18 -# define STLSOFT_VER_STLSOFT_H_STLSOFT_EDIT 606 +# define STLSOFT_VER_STLSOFT_H_STLSOFT_REVISION 19 +# define STLSOFT_VER_STLSOFT_H_STLSOFT_EDIT 607 #else /* ? STLSOFT_DOCUMENTATION_SKIP_SECTION */ /* # include "./internal/doxygen_defs.h" */ #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -421,12 +421,13 @@ # define _STLSOFT_VER_1_11_1_RC4 0x010b01c4 /*!< Version 1.11.1 rc 4 (1st July 2026) */ # define _STLSOFT_VER_1_11_1_RC5 0x010b01c5 /*!< Version 1.11.1 rc 5 (4th August 2026) */ # define _STLSOFT_VER_1_11_1_RC6 0x010b01c6 /*!< Version 1.11.1 rc 6 (21st September 2026) */ +# define _STLSOFT_VER_1_11_1_RC7 0x010b01c7 /*!< Version 1.11.1 rc 7 (25th September 2026) */ #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ #define _STLSOFT_VER_MAJOR 1 #define _STLSOFT_VER_MINOR 11 #define _STLSOFT_VER_PATCH 1 -#define _STLSOFT_VER_ALPHABETA 0xc6 +#define _STLSOFT_VER_ALPHABETA 0xc7 #define _STLSOFT_VER \ (0\ diff --git a/include/stlsoft/util/bits/count_functions.h b/include/stlsoft/util/bits/count_functions.h index 8d77a842..90e022a9 100644 --- a/include/stlsoft/util/bits/count_functions.h +++ b/include/stlsoft/util/bits/count_functions.h @@ -4,7 +4,7 @@ * Purpose: Bit count functions. * * Created: 2nd June 2010 - * Updated: 24th September 2026 + * Updated: 25th September 2026 * * Home: http://stlsoft.org/ * @@ -53,8 +53,8 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_MAJOR 1 # define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_MINOR 3 -# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_REVISION 2 -# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_EDIT 21 +# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_REVISION 3 +# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_COUNT_FUNCTIONS_EDIT 23 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -76,6 +76,13 @@ # include #endif /* !STLSOFT_INCL_STLSOFT_QUALITY_H_COVER */ +#ifndef STLSOFT_INCL_STLSOFT_API_external_h_bitfns +# include +#endif /* !STLSOFT_INCL_STLSOFT_API_external_h_bitfns */ +#ifndef STLSOFT_INCL_STLSOFT_API_internal_h_bitfns +# include +#endif /* !STLSOFT_INCL_STLSOFT_API_internal_h_bitfns */ + /* ///////////////////////////////////////////////////////////////////////// * namespace @@ -403,6 +410,38 @@ stlsoft_C_count_bits_in_32bit_unsigned_integer_by_Kernighan_method( return n; } +/** Counts the number of bits in an 8-bit unsigned integer, using Brian + * Kernighan's method (see TCPL). + * + * \param v The number whose bits are to be counted + * + * \return The number of bits in \c v + */ +STLSOFT_INLINE +unsigned +stlsoft_C_count_bits_in_8bit_unsigned_integer_by_Kernighan_method( + ss_uint8_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_count_bits_in_32bit_unsigned_integer_by_Kernighan_method(STLSOFT_STATIC_CAST(ss_uint32_t, v)); +} + +/** Counts the number of bits in a 16-bit unsigned integer, using Brian + * Kernighan's method (see TCPL). + * + * \param v The number whose bits are to be counted + * + * \return The number of bits in \c v + */ +STLSOFT_INLINE +unsigned +stlsoft_C_count_bits_in_16bit_unsigned_integer_by_Kernighan_method( + ss_uint16_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_count_bits_in_32bit_unsigned_integer_by_Kernighan_method(STLSOFT_STATIC_CAST(ss_uint32_t, v)); +} + /** Counts the number of bits in a 64-bit unsigned integer, using Brian * Kernighan's method (see TCPL). * @@ -448,6 +487,38 @@ stlsoft_C_count_bits_in_32bit_unsigned_integer_by_8bit_table( return (*ptr)[0[py]] + (*ptr)[1[py]] + (*ptr)[2[py]] + (*ptr)[3[py]]; } +/** Counts the number of bits in an 8-bit unsigned integer, using an 8-bit + * lookup table. + * + * \param v The number whose bits are to be counted + * + * \return The number of bits in \c v + */ +STLSOFT_INLINE +unsigned +stlsoft_C_count_bits_in_8bit_unsigned_integer_by_8bit_table( + ss_uint8_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_count_bits_in_32bit_unsigned_integer_by_8bit_table(STLSOFT_STATIC_CAST(ss_uint32_t, v)); +} + +/** Counts the number of bits in a 16-bit unsigned integer, using an 8-bit + * lookup table. + * + * \param v The number whose bits are to be counted + * + * \return The number of bits in \c v + */ +STLSOFT_INLINE +unsigned +stlsoft_C_count_bits_in_16bit_unsigned_integer_by_8bit_table( + ss_uint16_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_count_bits_in_32bit_unsigned_integer_by_8bit_table(STLSOFT_STATIC_CAST(ss_uint32_t, v)); +} + /** Counts the number of bits in a 64-bit unsigned integer, using an 8-bit. * lookup table. * @@ -466,6 +537,64 @@ stlsoft_C_count_bits_in_64bit_unsigned_integer_by_8bit_table( return n_high + n_low; } +#if defined(STLSOFT_API_INTERNAL_bitfns_popcount_uint16) || \ + defined(STLSOFT_DOCUMENTATION_SKIP_SECTION) + +/** Counts the number of bits in a 16-bit unsigned integer, using a + * compiler intrinsic. Not selected by count_bits(). + * + * \param v The number whose bits are to be counted + * + * \return The number of bits in \c v + */ +STLSOFT_INLINE +unsigned +stlsoft_C_count_bits_in_16bit_unsigned_integer_by_intrinsic( + ss_uint16_t v +) STLSOFT_NOEXCEPT +{ + return STLSOFT_C_CAST(unsigned, STLSOFT_API_INTERNAL_bitfns_popcount_uint16(v)); +} +#endif /* STLSOFT_API_INTERNAL_bitfns_popcount_uint16 */ +#if defined(STLSOFT_API_INTERNAL_bitfns_popcount_uint32) || \ + defined(STLSOFT_DOCUMENTATION_SKIP_SECTION) + +/** Counts the number of bits in a 32-bit unsigned integer, using a + * compiler intrinsic. Not selected by count_bits(). + * + * \param v The number whose bits are to be counted + * + * \return The number of bits in \c v + */ +STLSOFT_INLINE +unsigned +stlsoft_C_count_bits_in_32bit_unsigned_integer_by_intrinsic( + ss_uint32_t v +) STLSOFT_NOEXCEPT +{ + return STLSOFT_C_CAST(unsigned, STLSOFT_API_INTERNAL_bitfns_popcount_uint32(v)); +} +#endif /* STLSOFT_API_INTERNAL_bitfns_popcount_uint32 */ +#if defined(STLSOFT_API_INTERNAL_bitfns_popcount_uint64) || \ + defined(STLSOFT_DOCUMENTATION_SKIP_SECTION) + +/** Counts the number of bits in a 64-bit unsigned integer, using a + * compiler intrinsic. One instruction for the full width, not two + * 32-bit calls. Not selected by count_bits(). + * + * \param v The number whose bits are to be counted + * + * \return The number of bits in \c v + */ +STLSOFT_INLINE +unsigned +stlsoft_C_count_bits_in_64bit_unsigned_integer_by_intrinsic( + ss_uint64_t v +) STLSOFT_NOEXCEPT +{ + return STLSOFT_C_CAST(unsigned, STLSOFT_API_INTERNAL_bitfns_popcount_uint64(v)); +} +#endif /* STLSOFT_API_INTERNAL_bitfns_popcount_uint64 */ /* ///////////////////////////////////////////////////////////////////////// @@ -474,6 +603,32 @@ stlsoft_C_count_bits_in_64bit_unsigned_integer_by_8bit_table( #ifdef __cplusplus +/** + * + * \see stlsoft_C_count_bits_in_8bit_unsigned_integer_by_Kernighan_method + */ +inline +unsigned +count_bits_by_Kernighan_method( + ss_uint8_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_count_bits_in_8bit_unsigned_integer_by_Kernighan_method(v); +} + +/** + * + * \see stlsoft_C_count_bits_in_16bit_unsigned_integer_by_Kernighan_method + */ +inline +unsigned +count_bits_by_Kernighan_method( + ss_uint16_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_count_bits_in_16bit_unsigned_integer_by_Kernighan_method(v); +} + /** * * \see stlsoft_C_count_bits_in_32bit_unsigned_integer_by_Kernighan_method @@ -500,6 +655,32 @@ count_bits_by_Kernighan_method( return stlsoft_C_count_bits_in_64bit_unsigned_integer_by_Kernighan_method(v); } +/** + * + * \see stlsoft_C_count_bits_in_8bit_unsigned_integer_by_8bit_table + */ +inline +unsigned +count_bits_by_8bit_table( + ss_uint8_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_count_bits_in_8bit_unsigned_integer_by_8bit_table(v); +} + +/** + * + * \see stlsoft_C_count_bits_in_16bit_unsigned_integer_by_8bit_table + */ +inline +unsigned +count_bits_by_8bit_table( + ss_uint16_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_count_bits_in_16bit_unsigned_integer_by_8bit_table(v); +} + /** * * \see stlsoft_C_count_bits_in_32bit_unsigned_integer_by_8bit_table @@ -525,7 +706,108 @@ count_bits_by_8bit_table( { return stlsoft_C_count_bits_in_64bit_unsigned_integer_by_8bit_table(v); } +#if defined(STLSOFT_API_INTERNAL_bitfns_popcount_uint16) || \ + defined(STLSOFT_DOCUMENTATION_SKIP_SECTION) +/** + * + * \see stlsoft_C_count_bits_in_16bit_unsigned_integer_by_intrinsic + */ +inline +unsigned +count_bits_by_intrinsic( + ss_uint16_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_count_bits_in_16bit_unsigned_integer_by_intrinsic(v); +} + +/** + * + * \see stlsoft_C_count_bits_in_16bit_unsigned_integer_by_intrinsic + */ +inline +unsigned +count_bits_by_intrinsic( + ss_uint8_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_count_bits_in_16bit_unsigned_integer_by_intrinsic(STLSOFT_STATIC_CAST(ss_uint16_t, v)); +} +#endif +#if defined(STLSOFT_API_INTERNAL_bitfns_popcount_uint32) || \ + defined(STLSOFT_DOCUMENTATION_SKIP_SECTION) + +/** + * + * \see stlsoft_C_count_bits_in_32bit_unsigned_integer_by_intrinsic + */ +inline +unsigned +count_bits_by_intrinsic( + ss_uint32_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_count_bits_in_32bit_unsigned_integer_by_intrinsic(v); +} +#endif +#if defined(STLSOFT_API_INTERNAL_bitfns_popcount_uint64) || \ + defined(STLSOFT_DOCUMENTATION_SKIP_SECTION) + +/** + * + * \see stlsoft_C_count_bits_in_64bit_unsigned_integer_by_intrinsic + */ +inline +unsigned +count_bits_by_intrinsic( + ss_uint64_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_count_bits_in_64bit_unsigned_integer_by_intrinsic(v); +} +#endif + + +/** Counts the number of bits in an 8-bit unsigned integer + * + * \param v The number whose bits are to be counted + * + * \return The number of bits in \c v + */ +inline +unsigned +count_bits( + ss_uint8_t v +) STLSOFT_NOEXCEPT +{ +# if 0 +# elif defined(STLSOFT_BIT_COUNT_BY_Kernighan) + return count_bits_by_Kernighan_method(v); +# else + return count_bits_by_8bit_table(v); +# endif +} + +/** Counts the number of bits in a 16-bit unsigned integer + * + * \param v The number whose bits are to be counted + * + * \return The number of bits in \c v + */ +inline +unsigned +count_bits( + ss_uint16_t v +) STLSOFT_NOEXCEPT +{ +# if 0 +# elif defined(STLSOFT_BIT_COUNT_BY_Kernighan) + return count_bits_by_Kernighan_method(v); +# else + return count_bits_by_8bit_table(v); +# endif +} /** Counts the number of bits in a 32-bit unsigned integer * diff --git a/include/stlsoft/util/bits/test_functions.h b/include/stlsoft/util/bits/test_functions.h index 75db13fb..ac681bdc 100644 --- a/include/stlsoft/util/bits/test_functions.h +++ b/include/stlsoft/util/bits/test_functions.h @@ -4,7 +4,7 @@ * Purpose: Bit test functions * * Created: 2nd June 2010 - * Updated: 24th September 2026 + * Updated: 25th September 2026 * * Home: http://stlsoft.org/ * @@ -53,8 +53,8 @@ #ifndef STLSOFT_DOCUMENTATION_SKIP_SECTION # define STLSOFT_VER_STLSOFT_UTIL_BITS_H_TEST_FUNCTIONS_MAJOR 1 # define STLSOFT_VER_STLSOFT_UTIL_BITS_H_TEST_FUNCTIONS_MINOR 0 -# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_TEST_FUNCTIONS_REVISION 3 -# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_TEST_FUNCTIONS_EDIT 13 +# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_TEST_FUNCTIONS_REVISION 5 +# define STLSOFT_VER_STLSOFT_UTIL_BITS_H_TEST_FUNCTIONS_EDIT 15 #endif /* !STLSOFT_DOCUMENTATION_SKIP_SECTION */ @@ -76,6 +76,10 @@ # include #endif /* !STLSOFT_INCL_STLSOFT_QUALITY_H_COVER */ +#ifndef STLSOFT_INCL_STLSOFT_API_external_h_bitfns +# include +#endif /* !STLSOFT_INCL_STLSOFT_API_external_h_bitfns */ + /* ///////////////////////////////////////////////////////////////////////// * namespace @@ -173,6 +177,152 @@ stlsoft_C_find_highest_bit_in_64bit_unsigned_integer( return stlsoft_C_find_highest_bit_in_32bit_unsigned_integer(STLSOFT_STATIC_CAST(ss_uint32_t, v)); } +#if 0 +#elif defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse_SUPPORT) + +/** Find the 1-based index of the highest set bit, using a compiler + * intrinsic. Returns 0 when \c v is 0. Not selected by find_highest_bit(). + */ +STLSOFT_INLINE +unsigned +stlsoft_C_find_highest_bit_in_8bit_unsigned_integer_by_intrinsic( + ss_uint8_t v +) STLSOFT_NOEXCEPT +{ +# if 0 +# elif defined(STLSOFT_CF__builtin_clz_SUPPORT) + + if (0u == v) + { + return 0u; + } + + return 32u - STLSOFT_STATIC_CAST(unsigned, __builtin_clz(STLSOFT_STATIC_CAST(unsigned, v))); +# elif defined(STLSOFT_CF__BitScanReverse_SUPPORT) + + unsigned long index; + + if (0 == _BitScanReverse(&index, STLSOFT_STATIC_CAST(unsigned long, v))) + { + return 0u; + } + + return STLSOFT_STATIC_CAST(unsigned, index) + 1u; +# else + +# error Unexpected +# endif +} + +/** Find the 1-based index of the highest set bit, using a compiler + * intrinsic. Returns 0 when \c v is 0. Not selected by find_highest_bit(). + */ +STLSOFT_INLINE +unsigned +stlsoft_C_find_highest_bit_in_16bit_unsigned_integer_by_intrinsic( + ss_uint16_t v +) STLSOFT_NOEXCEPT +{ +# if 0 +# elif defined(STLSOFT_CF__builtin_clz_SUPPORT) + + if (0u == v) + { + return 0u; + } + + return 32u - STLSOFT_STATIC_CAST(unsigned, __builtin_clz(STLSOFT_STATIC_CAST(unsigned, v))); +# elif defined(STLSOFT_CF__BitScanReverse_SUPPORT) + + unsigned long index; + + if (0 == _BitScanReverse(&index, STLSOFT_STATIC_CAST(unsigned long, v))) + { + return 0u; + } + + return STLSOFT_STATIC_CAST(unsigned, index) + 1u; +# else + +# error Unexpected +# endif +} + +/** Find the 1-based index of the highest set bit, using a compiler + * intrinsic. Returns 0 when \c v is 0. Not selected by find_highest_bit(). + */ +STLSOFT_INLINE +unsigned +stlsoft_C_find_highest_bit_in_32bit_unsigned_integer_by_intrinsic( + ss_uint32_t v +) STLSOFT_NOEXCEPT +{ +# if 0 +# elif defined(STLSOFT_CF__builtin_clz_SUPPORT) + + if (0u == v) + { + return 0u; + } + + return 32u - STLSOFT_STATIC_CAST(unsigned, __builtin_clz(v)); +# elif defined(STLSOFT_CF__BitScanReverse_SUPPORT) + + unsigned long index; + + if (0 == _BitScanReverse(&index, STLSOFT_STATIC_CAST(unsigned long, v))) + { + return 0u; + } + + return STLSOFT_STATIC_CAST(unsigned, index) + 1u; +# else + +# error Unexpected +# endif +} +#endif + +#if 0 +#elif defined(STLSOFT_CF__builtin_clzll_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) + +/** Find the 1-based index of the highest set bit, using a compiler + * intrinsic. Returns 0 when \c v is 0. Not selected by find_highest_bit(). + */ +STLSOFT_INLINE +unsigned +stlsoft_C_find_highest_bit_in_64bit_unsigned_integer_by_intrinsic( + ss_uint64_t v +) STLSOFT_NOEXCEPT +{ +# if 0 +# elif defined(STLSOFT_CF__builtin_clzll_SUPPORT) + + if (0u == v) + { + return 0u; + } + + return 64u - STLSOFT_STATIC_CAST(unsigned, __builtin_clzll(v)); +# elif defined(STLSOFT_CF__BitScanReverse64_SUPPORT) + + unsigned long index; + + if (0 == _BitScanReverse64(&index, v)) + { + return 0u; + } + + return STLSOFT_STATIC_CAST(unsigned, index) + 1u; +# else + +# error Unexpected +# endif +} +#endif + /* ///////////////////////////////////////////////////////////////////////// * C++ @@ -231,6 +381,68 @@ find_highest_bit( { return stlsoft_C_find_highest_bit_in_8bit_unsigned_integer(v); } + +#if 0 +#elif defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) + +/** + * + * \see stlsoft_C_find_highest_bit_in_64bit_unsigned_integer_by_intrinsic + */ +inline +unsigned +find_highest_bit_by_intrinsic( + ss_uint64_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_find_highest_bit_in_64bit_unsigned_integer_by_intrinsic(v); +} +#endif + +#if 0 +#elif defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse_SUPPORT) + +/** + * + * \see stlsoft_C_find_highest_bit_in_32bit_unsigned_integer_by_intrinsic + */ +inline +unsigned +find_highest_bit_by_intrinsic( + ss_uint32_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_find_highest_bit_in_32bit_unsigned_integer_by_intrinsic(v); +} + +/** + * + * \see stlsoft_C_find_highest_bit_in_16bit_unsigned_integer_by_intrinsic + */ +inline +unsigned +find_highest_bit_by_intrinsic( + ss_uint16_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_find_highest_bit_in_16bit_unsigned_integer_by_intrinsic(v); +} + +/** + * + * \see stlsoft_C_find_highest_bit_in_8bit_unsigned_integer_by_intrinsic + */ +inline +unsigned +find_highest_bit_by_intrinsic( + ss_uint8_t v +) STLSOFT_NOEXCEPT +{ + return stlsoft_C_find_highest_bit_in_8bit_unsigned_integer_by_intrinsic(v); +} +#endif #endif /* __cplusplus */ diff --git a/strategy/BIT_FUNCTIONS.md b/strategy/BIT_FUNCTIONS.md index e69de29b..4931c442 100644 --- a/strategy/BIT_FUNCTIONS.md +++ b/strategy/BIT_FUNCTIONS.md @@ -0,0 +1,96 @@ +# STLSoft.b - Bit Functions + +Strategy note for **STLSoft.b** bit utilities. Measurements are of the performance program **test/performance/stlsoft/bit_functions/main.cpp**. Library sources are **include/stlsoft/util/bits/count_functions.h**, **include/stlsoft/util/bits/test_functions.h**, and **include/stlsoft/util/bits/xor_functions.h**. Intrinsic detection and naming live in **include/stlsoft/api/external/bitfns.h** and **include/stlsoft/api/internal/bitfns.h**. + + +## Question + +If intrinsics are never adopted, should `count_bits()` use Kernighan on Apple and the 8-bit table everywhere else? + +No. The fast Kernighan numbers are Apple Clang rewriting the loop into a hardware popcount. They are not a property of Apple hardware. On every other toolchain measured here, that rewrite did not happen, and the table is several times faster. A default keyed on Apple, or on ARM, would be right only while the compiler keeps doing that rewrite, and badly wrong when it does not. + +The cost of leaving the table in place on Apple Clang is a fraction of a nanosecond per call. The cost of leaving Kernighan in place on a compiler that executes the source loop is about 12 ns (`uint32_t`, all ones) and about 24 ns (`uint64_t`, all ones), against about 1 ns and 2 ns for the table. + + +## What was measured + +Two bodies of results. Both are optimised release builds (`optimisation: speed`, `NDEBUG`), five-sample medians, 20,000,000 iterations. Anchors match across machines, so the loops did the same work. + +* **Cross-platform, commit `74b74407875100e0f1b6f5f7658a714c9a8d57b3`.** Sequential `i`, dense `~i`, volatile all-ones, and `ones^1`, plus `find_highest_bit` and `calculate_xor_over_range`. Linux Clang 18.1.3, Linux GCC 13.3, two macOS runs, Windows MSVC 18, Windows MinGW GCC 15.2. +* **Later local Apple Clang 21 only.** Adds volatile zero, a rotating one-hot bit, a both-halves-live pattern, and the unselected intrinsic rows, and prints ns/call on the same row. Use it for the one-hot, zero, and intrinsic cases. Do not treat it as a second cross-platform matrix. + +The run labeled macOS GCC printed `compiler: Clang 21.0.0`. Both Mac columns are Apple Clang. There is no GCC result from macOS in this set. + + +### Popcount, cross-platform + +Nanoseconds per call, `uint32_t` / `uint64_t`. Divide the printed loop time by 20,000,000. + +| Row | Linux Clang 18 | Linux GCC 13.3 | macOS Clang 21 | macOS Clang 21 (2nd) | Windows MSVC 18 | MinGW GCC 15.2 | +| --- | --- | --- | --- | --- | --- | --- | +| Kernighan, sequential `i` | 4.9 / 4.9 | 4.8 / 4.8 | 0.21 / 0.20 | 0.18 / 0.18 | 4.9 / 5.3 | 5.3 / 5.5 | +| Kernighan, all-ones | 11.9 / 26.1 | 11.5 / 22.6 | 0.35 / 0.68 | 0.34 / 0.64 | 11.7 / 24.4 | 11.3 / 24.6 | +| 8-bit table, sequential `i` | 0.98 / 0.98 | 0.94 / 0.94 | 1.7 / 1.2 | 1.3 / 0.95 | 1.4 / 1.3 | 0.96 / 0.95 | +| 8-bit table, all-ones | 0.94 / 1.9 | 0.93 / 1.9 | 1.1 / 1.3 | 1.0 / 1.3 | 1.3 / 2.1 | 0.94 / 1.8 | + +`count_bits()` tracks the table on every toolchain, including Apple Clang. One MSVC cell is slower than the named table entry (sequential `uint64_t`, about 3.1 ns against 1.3 ns). The others match. The dispatcher is not the Kernighan path unless `STLSOFT_BIT_COUNT_BY_Kernighan` is defined. + + +### Apple Clang 21, intrinsics included + +Nanoseconds per call. Popcount cells are `uint32_t` / `uint64_t`. `count_bits()` matches the table on every row below, so it is omitted. Anchors match the earlier Apple run, including `/bit` at `40,000,000` and `/0` at `0`. + +| Row | Kernighan | 8-bit table | intrinsic | +| --- | --- | --- | --- | +| sequential `i` | 0.114 / 0.119 | 0.913 / 0.689 | 0.116 / 0.116 | +| `~i` | 0.127 / 0.141 | 0.900 / 0.901 | 0.128 / 0.130 | +| all-ones | 0.248 / 0.347 | 0.681 / 0.735 | 0.246 / 0.226 | +| `ones^1` | 0.200 / 0.359 | 0.929 / 0.788 | 0.199 / 0.236 | +| volatile zero | 0.227 / 0.346 | 0.681 / 0.708 | 0.232 / 0.247 | +| rotating bit | 0 / 0 | 0.915 / 0.723 | 0 / 0 | +| both halves live | 0.141 / 0.142 | 0.326 / 0.421 | 0.145 / 0.176 | + +The rotating-bit times of 0 are deleted loops. The anchor is still `40,000,000`. The table on that row stayed near 0.9 ns. + +`find_highest_bit` cells are `uint8_t` / `uint16_t` / `uint32_t` / `uint64_t`. + +| Row | scan | intrinsic | +| --- | --- | --- | +| sequential `i` | 0.294 / 0.468 / 0.434 / 0.435 | 0.197 / 0.198 / 0.113 / 0.113 | +| volatile zero | 0.236 / 0.423 / 0.504 / 0.827 | 0.224 / 0.227 / 0.234 / 0.223 | +| rotating bit | 0.111 / 0.225 / 0.340 / 0.473 | 0.129 / 0.148 / 0.112 / 0.111 | +| both halves live | 0.297 / 0.473 / 0.378 / 0.465 | 0.197 / 0.197 / 0.199 / 0.232 | + + +## Learnings + +* **Kernighan scales with the number of set bits when the compiler leaves the loop alone.** All-ones is about 32 iterations at ~0.4 ns each for `uint32_t`, and about twice that for `uint64_t`, because the 64-bit function is two 32-bit calls. That is Linux Clang 18, Linux GCC 13.3, MSVC, and MinGW GCC 15.2. +* **Apple Clang rewrites the Kernighan source into the same instruction as the intrinsic.** On 32-bit rows the two times match (all-ones 0.248 ns and 0.246 ns). On 64-bit rows with a live high half the intrinsic is one `__builtin_popcountll` and Kernighan is still two 32-bit popcounts: all-ones 0.226 ns against 0.347 ns, `ones^1` 0.236 ns against 0.359 ns, volatile zero 0.247 ns against 0.346 ns. Sequential `uint64_t` does not show that gap, because its high half is zero. The both-halves-live row goes the other way by a small amount (Kernighan 0.142 ns, intrinsic 0.176 ns): the high half is a copy of a sparse `i`, not a dense word. +* **Generic x86-64 does not emit `POPCNT` for this loop.** The instruction is outside the baseline `-march=x86-64` target. Clang 18, GCC 13, GCC 15, and MSVC all kept the source loop in these release builds. Recognition of the idiom is not enough if the target forbids the instruction. +* **A one-hot input can delete both the Kernighan loop and the popcount intrinsic.** On Apple Clang 21, `count_bits_by_Kernighan_method()/bit` and `count_bits_by_intrinsic()/bit` reported 0 ns while the anchor stayed `40,000,000`. The table on that input stayed near 0.9 ns, and `count_bits()` followed the table. Do not read a zero time as "sparse popcount is free". +* **Sequential `uint64_t` is not a 64-bit input.** `uint64_t v = i` with `i` below 2^25 has a zero high half. Equal `uint32_t` and `uint64_t` times on that row do not mean the wide operation is free. All-ones and `~i` are the rows that exercise the high half. +* **The table is density-insensitive, with two shortcuts.** Four lookups are about 1 ns; eight are about 2 ns when the high half is real. All-zero and all-ones are cheaper when every byte hits the same slot. The both-halves-live row on Apple Clang was cheaper still, because the two halves are copies and the compiler reused lookups. That row is not the cost of arbitrary 64-bit data. +* **`find_highest_bit` is a software scan on every toolchain in the cross-platform matrix.** `uint32_t` and `uint64_t` stay close on the sequential input because the high half is zero. MSVC is the slowest and the one case where that zero high half still costs extra. On Apple Clang 21 the scan still grows with width: zero is 0.236 / 0.423 / 0.504 / 0.827 ns, and a rotating bit is 0.111 / 0.225 / 0.340 / 0.473 ns. +* **The `clz` intrinsic is flat on that same Apple Clang run.** Zero is about 0.22–0.23 ns at every width, which is the explicit zero test (`clz` of zero is undefined), not a descent. A rotating bit is 0.129 / 0.148 / 0.112 / 0.111 ns. The 8-bit scan is still slightly faster on that one-hot row (0.111 ns), because the intrinsic is a 32-bit `clz` of a zero-extended value. From 16 bits upward the intrinsic wins, and the both-halves-live `uint64_t` row is 0.232 ns against 0.465 ns for the scan. +* **XOR is a range scan.** At an average length of 32.5 elements it is several nanoseconds per call. `uint64_t` was the expensive width on Linux and MinGW, and the cheap width on Apple Clang. It is not comparable to the scalar rows, and it does not justify SIMD on this harness (`n` at most 64, data in L1). + + +## Strategy + +* **Keep the 8-bit table as the default** for `count_bits()`, on every architecture. +* **Do not select Kernighan by Apple, by ARM, or by Clang.** The measured switch is "this compiler, on this target, rewrote this loop". Apple Clang 21 did. Linux Clang 18 did not. GCC on Apple Silicon has not been measured. +* **Leave `STLSOFT_BIT_COUNT_BY_Kernighan` as an opt-in** for a build that has been measured. The macro calls `count_bits_by_Kernighan_method`. +* **Keep `count_bits_by_intrinsic()` unselected until it is measured off Apple Clang.** The call goes through `STLSOFT_API_INTERNAL_bitfns_popcount_uint32` / `uint64`, which prefer `__builtin_popcount` / `__builtin_popcountll` and otherwise use `__popcnt` / `__popcnt64`. On Apple Clang 21 that matches rewritten Kernighan for 32-bit values and beats it for a dense 64-bit value, and it beats the table on every row whose loop was not deleted. `__builtin_popcount` without a `POPCNT` target can be a library call. That case is the one that could lose to the table, and it has not been timed. `count_bits()` still uses the table. +* **Keep `find_highest_bit_by_intrinsic()` unselected for the same reason.** It is 1-based and returns 0 for a zero input. GCC and Clang use `__builtin_clz` / `__builtin_clzll`; MSVC uses `_BitScanReverse` / `_BitScanReverse64`, with the index converted to the same 1-based result. The 8-bit and 16-bit forms are a 32-bit operation on a zero-extended value. On Apple Clang 21 it is the faster form from 16 bits up, including zero and a live high half. One Linux or Windows run of the same four rows is enough to decide whether the scan stays the default. `find_highest_bit()` still uses the shift-and-split scan. +* **MSVC popcount is gated by architecture and toolset, in `external/bitfns.h`.** `__popcnt16` and `__popcnt` are x86 and x64 from MSVC 15.00, and ARM64 from Visual Studio 2022 17.11 (`_MSC_VER` 1941). They are not promised for 32-bit ARM or for ARM64EC. `__popcnt64` is x64 from MSVC 15.00, and ARM64 and ARM64EC from 17.11. `_BitScanReverse64` is ARM64, ARM64EC, and x64 from MSVC 14.00. When a GCC/Clang builtin and an MSVC intrinsic are both visible, the builtin is the one the internal popcount function calls. +* **Do not change `calculate_xor_over_range` for performance** on the strength of this harness. + + +## Not yet done + +* Selecting `count_bits_by_intrinsic()` or `find_highest_bit_by_intrinsic()` into the dispatchers. Apple Clang 21 favours both. The missing evidence is the same rows on Linux Clang, Linux GCC, MSVC, and MinGW, including a target where `POPCNT` is not a baseline instruction. +* GCC, or Clang, on Apple Silicon as a compiler that is not Apple Clang. +* Linux or Windows built with `-march=native` or `/arch` that allows `POPCNT`, for the Kernighan idiom and for the explicit intrinsic. + + + diff --git a/test/performance/stlsoft/bit_functions/main.cpp b/test/performance/stlsoft/bit_functions/main.cpp index 62f28045..10aed588 100644 --- a/test/performance/stlsoft/bit_functions/main.cpp +++ b/test/performance/stlsoft/bit_functions/main.cpp @@ -4,7 +4,7 @@ * Purpose: Perf-test for bit functions. * * Created: 24th September 2026 - * Updated: 24th September 2026 + * Updated: 25th September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -487,7 +487,6 @@ emit_build_banner() { std::cout << "compiler: " << STLSOFT_COMPILER_VERSION_STRING << std::endl; std::cout << "optimisation: "; - #if 0 #elif defined(__OPTIMIZE_SIZE__) @@ -503,7 +502,6 @@ emit_build_banner() std::cout << "off"; #endif - #ifdef NDEBUG std::cout << ", release"; @@ -511,7 +509,6 @@ emit_build_banner() std::cout << ", debug-asserts"; #endif - std::cout << std::endl; std::cout << "samples: " @@ -565,6 +562,30 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ss_uint8_t(i); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ss_uint16_t(i); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { @@ -592,8 +613,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , NULL @@ -621,6 +642,30 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ss_uint8_t(i); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ss_uint16_t(i); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { @@ -648,8 +693,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , NULL @@ -677,6 +722,30 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ss_uint8_t(i); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ss_uint16_t(i); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { @@ -716,8 +785,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , &int_int @@ -729,7 +798,98 @@ int main(int /*argc*/, char* /*argv*/[]) { rows.emit("count_bits()"); } + + } + +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt_SUPPORT) + // count_bits_by_intrinsic (sequential) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ss_uint8_t(i); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ss_uint16_t(i); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = i; + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = i; + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); +#endif + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) + , &int_uint64 +#else + , NULL +#endif + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits_by_intrinsic()"); + } } +#endif // ------------------------------------------------------------------ @@ -749,6 +909,30 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ~ss_uint8_t(i); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ~ss_uint16_t(i); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { @@ -776,8 +960,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , NULL @@ -805,6 +989,30 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ~ss_uint8_t(i); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ~ss_uint16_t(i); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { @@ -832,8 +1040,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , NULL @@ -861,6 +1069,30 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ~ss_uint8_t(i); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ~ss_uint16_t(i); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { @@ -900,8 +1132,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , &int_int @@ -913,33 +1145,152 @@ int main(int /*argc*/, char* /*argv*/[]) { rows.emit("count_bits()/~i"); } - } - - // ------------------------------------------------------------------ - // Max density (all-ones). Source via volatile so inlining cannot - // constant-fold popcount(~0) to a literal. - // ------------------------------------------------------------------ + } +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt_SUPPORT) + // count_bits_by_intrinsic (/~i) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { - ss_uint32_t volatile ones32 = ~ss_uint32_t(0); - ss_uint64_t volatile ones64 = ~ss_uint64_t(0); - - - // count_bits_by_Kernighan_method (max) - for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + if (int(NUM_SAMPLES) + 1 == W) { - if (int(NUM_SAMPLES) + 1 == W) - { - rows.clear(); - } + rows.clear(); + } - stopwatch_t sw; + stopwatch_t sw; - ss_uint64_t anchor_value = 0; + ss_uint64_t anchor_value = 0; - sw.start(); + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ~ss_uint8_t(i); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ~ss_uint16_t(i); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = ~ss_uint32_t(i); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = ~ss_uint64_t(i); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); +#endif + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) + , &int_uint64 +#else + , NULL +#endif + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits_by_intrinsic()/~i"); + } + } +#endif + + + // ------------------------------------------------------------------ + // Max density (all-ones). Source via volatile so inlining cannot + // constant-fold popcount(~0) to a literal. + // ------------------------------------------------------------------ + + { + ss_uint8_t volatile ones8 = ~ss_uint8_t(0); + ss_uint16_t volatile ones16 = ~ss_uint16_t(0); + ss_uint32_t volatile ones32 = ~ss_uint32_t(0); + ss_uint64_t volatile ones64 = ~ss_uint64_t(0); + + + // count_bits_by_Kernighan_method (max) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ones8; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ones16; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { ss_uint32_t const v = ones32; @@ -968,8 +1319,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , NULL @@ -997,6 +1348,32 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ones8; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ones16; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { @@ -1026,8 +1403,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , NULL @@ -1055,6 +1432,32 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ones8; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ones16; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { @@ -1097,8 +1500,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , &int_int @@ -1110,7 +1513,103 @@ int main(int /*argc*/, char* /*argv*/[]) { rows.emit("count_bits()/ones"); } + + } + +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt_SUPPORT) + // count_bits_by_intrinsic (/ones) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ones8; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ones16; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = ones32; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = ones64; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); +#endif + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) + , &int_uint64 +#else + , NULL +#endif + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits_by_intrinsic()/ones"); + } } +#endif + } @@ -1134,19 +1633,43 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint32_t const v = ~ss_uint32_t(0) ^ (ss_uint32_t(1) << (i % 32u)); + ss_uint8_t const v = ~ss_uint8_t(0) ^ (ss_uint8_t(1) << (i % 8u)); anchor_value += stlsoft::count_bits_by_Kernighan_method(v); } sw.stop(); - interval_t const int_uint32 = sw.get_nanoseconds(); + interval_t const int_uint8 = sw.get_nanoseconds(); sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint64_t const v = ~ss_uint64_t(0) ^ (ss_uint64_t(1) << (i % 64u)); + ss_uint16_t const v = ~ss_uint16_t(0) ^ (ss_uint16_t(1) << (i % 16u)); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = ~ss_uint32_t(0) ^ (ss_uint32_t(1) << (i % 32u)); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = ~ss_uint64_t(0) ^ (ss_uint64_t(1) << (i % 64u)); anchor_value += stlsoft::count_bits_by_Kernighan_method(v); } @@ -1158,8 +1681,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , NULL @@ -1187,6 +1710,30 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ~ss_uint8_t(0) ^ (ss_uint8_t(1) << (i % 8u)); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ~ss_uint16_t(0) ^ (ss_uint16_t(1) << (i % 16u)); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { @@ -1214,8 +1761,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , NULL @@ -1243,6 +1790,30 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ~ss_uint8_t(0) ^ (ss_uint8_t(1) << (i % 8u)); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ~ss_uint16_t(0) ^ (ss_uint16_t(1) << (i % 16u)); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { @@ -1282,8 +1853,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , &int_int @@ -1295,7 +1866,98 @@ int main(int /*argc*/, char* /*argv*/[]) { rows.emit("count_bits()/ones^1"); } + + } + +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt_SUPPORT) + // count_bits_by_intrinsic (/ones^1) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ~ss_uint8_t(0) ^ (ss_uint8_t(1) << (i % 8u)); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ~ss_uint16_t(0) ^ (ss_uint16_t(1) << (i % 16u)); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = ~ss_uint32_t(0) ^ (ss_uint32_t(1) << (i % 32u)); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = ~ss_uint64_t(0) ^ (ss_uint64_t(1) << (i % 64u)); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); +#endif + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) + , &int_uint64 +#else + , NULL +#endif + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits_by_intrinsic()/ones^1"); + } } +#endif // ------------------------------------------------------------------ @@ -1303,6 +1965,8 @@ int main(int /*argc*/, char* /*argv*/[]) // ------------------------------------------------------------------ { + ss_uint8_t volatile zero8 = 0; + ss_uint16_t volatile zero16 = 0; ss_uint32_t volatile zero32 = 0; ss_uint64_t volatile zero64 = 0; @@ -1320,6 +1984,32 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = zero8; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = zero16; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { @@ -1349,8 +2039,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , NULL @@ -1378,6 +2068,32 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = zero8; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = zero16; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { @@ -1407,8 +2123,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , NULL @@ -1436,6 +2152,32 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = zero8; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = zero16; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { @@ -1478,8 +2220,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , &int_int @@ -1491,15 +2233,365 @@ int main(int /*argc*/, char* /*argv*/[]) { rows.emit("count_bits()/0"); } + + } + +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt_SUPPORT) + // count_bits_by_intrinsic (/0) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = zero8; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = zero16; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = zero32; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = zero64; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); +#endif + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) + , &int_uint64 +#else + , NULL +#endif + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits_by_intrinsic()/0"); + } + } +#endif + + } + + + // ------------------------------------------------------------------ + // Single rotating bit. The 64-bit shift reaches bits 32..63. + // ------------------------------------------------------------------ + + // count_bits_by_Kernighan_method (bit) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ss_uint8_t(1) << (i % 8u); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ss_uint16_t(1) << (i % 16u); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = ss_uint32_t(1) << (i % 32u); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = ss_uint64_t(1) << (i % 64u); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits_by_Kernighan_method()/bit"); + } + } + + + // count_bits_by_8bit_table (bit) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ss_uint8_t(1) << (i % 8u); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ss_uint16_t(1) << (i % 16u); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = ss_uint32_t(1) << (i % 32u); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = ss_uint64_t(1) << (i % 64u); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 + , &int_uint64 + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("count_bits_by_8bit_table()/bit"); + } + } + + + // count_bits (bit) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ss_uint8_t(1) << (i % 8u); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ss_uint16_t(1) << (i % 16u); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = ss_uint32_t(1) << (i % 32u); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = ss_uint64_t(1) << (i % 64u); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + int const v = static_cast(ss_uint32_t(1) << (i % 32u)); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_int = sw.get_nanoseconds(); + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 + , &int_uint64 + , &int_int + , anchor_value + ); } - } + if (1 == W) + { + rows.emit("count_bits()/bit"); + } - // ------------------------------------------------------------------ - // Single rotating bit. The 64-bit shift reaches bits 32..63. - // ------------------------------------------------------------------ + } - // count_bits_by_Kernighan_method (bit) +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt_SUPPORT) + // count_bits_by_intrinsic (/bit) for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { if (int(NUM_SAMPLES) + 1 == W) @@ -1512,37 +2604,69 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = ss_uint8_t(1) << (i % 8u); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = ss_uint16_t(1) << (i % 16u); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { ss_uint32_t const v = ss_uint32_t(1) << (i % 32u); - anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + anchor_value += stlsoft::count_bits_by_intrinsic(v); } sw.stop(); interval_t const int_uint32 = sw.get_nanoseconds(); +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { ss_uint64_t const v = ss_uint64_t(1) << (i % 64u); - anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + anchor_value += stlsoft::count_bits_by_intrinsic(v); } sw.stop(); interval_t const int_uint64 = sw.get_nanoseconds(); +#endif if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) , &int_uint64 +#else + , NULL +#endif , NULL , anchor_value ); @@ -1550,12 +2674,17 @@ int main(int /*argc*/, char* /*argv*/[]) if (1 == W) { - rows.emit("count_bits_by_Kernighan_method()/bit"); + rows.emit("count_bits_by_intrinsic()/bit"); } } +#endif - // count_bits_by_8bit_table (bit) + // ------------------------------------------------------------------ + // Both halves live. uint32 high 16 bits vary; uint64 high half is i. + // ------------------------------------------------------------------ + + // count_bits_by_Kernighan_method (wide) for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { if (int(NUM_SAMPLES) + 1 == W) @@ -1571,9 +2700,33 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint32_t const v = ss_uint32_t(1) << (i % 32u); + ss_uint8_t const v = (ss_uint8_t(i) << 4) | ss_uint8_t(i & 0x0fu); - anchor_value += stlsoft::count_bits_by_8bit_table(v); + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = (ss_uint16_t(i) << 8) | ss_uint16_t(i & 0xffu); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = (ss_uint32_t(i) << 16) | ss_uint32_t(i & 0xffffu); + + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); } sw.stop(); @@ -1583,9 +2736,9 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint64_t const v = ss_uint64_t(1) << (i % 64u); + ss_uint64_t const v = (ss_uint64_t(i) << 32) | ss_uint64_t(i); - anchor_value += stlsoft::count_bits_by_8bit_table(v); + anchor_value += stlsoft::count_bits_by_Kernighan_method(v); } sw.stop(); @@ -1595,8 +2748,8 @@ int main(int /*argc*/, char* /*argv*/[]) if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 , NULL @@ -1606,12 +2759,12 @@ int main(int /*argc*/, char* /*argv*/[]) if (1 == W) { - rows.emit("count_bits_by_8bit_table()/bit"); + rows.emit("count_bits_by_Kernighan_method()/wide"); } } - // count_bits (bit) + // count_bits_by_8bit_table (wide) for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { if (int(NUM_SAMPLES) + 1 == W) @@ -1627,63 +2780,71 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint32_t const v = ss_uint32_t(1) << (i % 32u); + ss_uint8_t const v = (ss_uint8_t(i) << 4) | ss_uint8_t(i & 0x0fu); - anchor_value += stlsoft::count_bits(v); + anchor_value += stlsoft::count_bits_by_8bit_table(v); } sw.stop(); - interval_t const int_uint32 = sw.get_nanoseconds(); + interval_t const int_uint8 = sw.get_nanoseconds(); sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint64_t const v = ss_uint64_t(1) << (i % 64u); + ss_uint16_t const v = (ss_uint16_t(i) << 8) | ss_uint16_t(i & 0xffu); - anchor_value += stlsoft::count_bits(v); + anchor_value += stlsoft::count_bits_by_8bit_table(v); } sw.stop(); - interval_t const int_uint64 = sw.get_nanoseconds(); + interval_t const int_uint16 = sw.get_nanoseconds(); sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - int const v = static_cast(ss_uint32_t(1) << (i % 32u)); + ss_uint32_t const v = (ss_uint32_t(i) << 16) | ss_uint32_t(i & 0xffffu); - anchor_value += stlsoft::count_bits(v); + anchor_value += stlsoft::count_bits_by_8bit_table(v); } sw.stop(); - interval_t const int_int = sw.get_nanoseconds(); + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = (ss_uint64_t(i) << 32) | ss_uint64_t(i); + + anchor_value += stlsoft::count_bits_by_8bit_table(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 - , &int_int + , NULL , anchor_value ); } if (1 == W) { - rows.emit("count_bits()/bit"); + rows.emit("count_bits_by_8bit_table()/wide"); } } - // ------------------------------------------------------------------ - // Both halves live. uint32 high 16 bits vary; uint64 high half is i. - // ------------------------------------------------------------------ - - // count_bits_by_Kernighan_method (wide) + // count_bits (wide) for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { if (int(NUM_SAMPLES) + 1 == W) @@ -1696,12 +2857,36 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = (ss_uint8_t(i) << 4) | ss_uint8_t(i & 0x0fu); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = (ss_uint16_t(i) << 8) | ss_uint16_t(i & 0xffu); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { ss_uint32_t const v = (ss_uint32_t(i) << 16) | ss_uint32_t(i & 0xffffu); - anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + anchor_value += stlsoft::count_bits(v); } sw.stop(); @@ -1713,33 +2898,47 @@ int main(int /*argc*/, char* /*argv*/[]) { ss_uint64_t const v = (ss_uint64_t(i) << 32) | ss_uint64_t(i); - anchor_value += stlsoft::count_bits_by_Kernighan_method(v); + anchor_value += stlsoft::count_bits(v); } sw.stop(); interval_t const int_uint64 = sw.get_nanoseconds(); + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + int const v = static_cast((ss_uint32_t(i) << 16) | ss_uint32_t(i & 0xffffu)); + + anchor_value += stlsoft::count_bits(v); + } + sw.stop(); + + interval_t const int_int = sw.get_nanoseconds(); + + if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 - , NULL + , &int_int , anchor_value ); } if (1 == W) { - rows.emit("count_bits_by_Kernighan_method()/wide"); + rows.emit("count_bits()/wide"); } - } + } - // count_bits_by_8bit_table (wide) +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt_SUPPORT) + // count_bits_by_intrinsic (/wide) for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { if (int(NUM_SAMPLES) + 1 == W) @@ -1752,37 +2951,69 @@ int main(int /*argc*/, char* /*argv*/[]) ss_uint64_t anchor_value = 0; + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = (ss_uint8_t(i) << 4) | ss_uint8_t(i & 0x0fu); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = (ss_uint16_t(i) << 8) | ss_uint16_t(i & 0xffu); + + anchor_value += stlsoft::count_bits_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { ss_uint32_t const v = (ss_uint32_t(i) << 16) | ss_uint32_t(i & 0xffffu); - anchor_value += stlsoft::count_bits_by_8bit_table(v); + anchor_value += stlsoft::count_bits_by_intrinsic(v); } sw.stop(); interval_t const int_uint32 = sw.get_nanoseconds(); +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { ss_uint64_t const v = (ss_uint64_t(i) << 32) | ss_uint64_t(i); - anchor_value += stlsoft::count_bits_by_8bit_table(v); + anchor_value += stlsoft::count_bits_by_intrinsic(v); } sw.stop(); interval_t const int_uint64 = sw.get_nanoseconds(); +#endif if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) , &int_uint64 +#else + , NULL +#endif , NULL , anchor_value ); @@ -1790,12 +3021,13 @@ int main(int /*argc*/, char* /*argv*/[]) if (1 == W) { - rows.emit("count_bits_by_8bit_table()/wide"); + rows.emit("count_bits_by_intrinsic()/wide"); } } +#endif - // count_bits (wide) + // find_highest_bit for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { if (int(NUM_SAMPLES) + 1 == W) @@ -1811,59 +3043,73 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint32_t const v = (ss_uint32_t(i) << 16) | ss_uint32_t(i & 0xffffu); + ss_uint8_t const v = static_cast(i & 0xff); - anchor_value += stlsoft::count_bits(v); + anchor_value += stlsoft::find_highest_bit(v); } sw.stop(); - interval_t const int_uint32 = sw.get_nanoseconds(); + interval_t const int_uint8 = sw.get_nanoseconds(); sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint64_t const v = (ss_uint64_t(i) << 32) | ss_uint64_t(i); + ss_uint16_t const v = static_cast(i & 0xffff); - anchor_value += stlsoft::count_bits(v); + anchor_value += stlsoft::find_highest_bit(v); } sw.stop(); - interval_t const int_uint64 = sw.get_nanoseconds(); + interval_t const int_uint16 = sw.get_nanoseconds(); sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - int const v = static_cast((ss_uint32_t(i) << 16) | ss_uint32_t(i & 0xffffu)); + ss_uint32_t const v = i; - anchor_value += stlsoft::count_bits(v); + anchor_value += stlsoft::find_highest_bit(v); } sw.stop(); - interval_t const int_int = sw.get_nanoseconds(); + interval_t const int_uint32 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = i; + + anchor_value += stlsoft::find_highest_bit(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); if (W <= int(NUM_SAMPLES)) { rows.push( - NULL - , NULL + &int_uint8 + , &int_uint16 , &int_uint32 , &int_uint64 - , &int_int + , NULL , anchor_value ); } if (1 == W) { - rows.emit("count_bits()/wide"); + rows.emit("find_highest_bit()"); } - } + } - // find_highest_bit +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse_SUPPORT) + // find_highest_bit_by_intrinsic (sequential) for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) { if (int(NUM_SAMPLES) + 1 == W) @@ -1879,9 +3125,9 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint8_t const v = static_cast(i & 0xff); + ss_uint8_t const v = ss_uint8_t(i); - anchor_value += stlsoft::find_highest_bit(v); + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); } sw.stop(); @@ -1891,9 +3137,9 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint16_t const v = static_cast(i & 0xffff); + ss_uint16_t const v = ss_uint16_t(i); - anchor_value += stlsoft::find_highest_bit(v); + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); } sw.stop(); @@ -1905,23 +3151,26 @@ int main(int /*argc*/, char* /*argv*/[]) { ss_uint32_t const v = i; - anchor_value += stlsoft::find_highest_bit(v); + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); } sw.stop(); interval_t const int_uint32 = sw.get_nanoseconds(); +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { ss_uint64_t const v = i; - anchor_value += stlsoft::find_highest_bit(v); + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); } sw.stop(); interval_t const int_uint64 = sw.get_nanoseconds(); +#endif if (W <= int(NUM_SAMPLES)) @@ -1930,7 +3179,12 @@ int main(int /*argc*/, char* /*argv*/[]) &int_uint8 , &int_uint16 , &int_uint32 +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) , &int_uint64 +#else + , NULL +#endif , NULL , anchor_value ); @@ -1938,9 +3192,10 @@ int main(int /*argc*/, char* /*argv*/[]) if (1 == W) { - rows.emit("find_highest_bit()"); + rows.emit("find_highest_bit_by_intrinsic()"); } } +#endif // ------------------------------------------------------------------ @@ -2035,7 +3290,103 @@ int main(int /*argc*/, char* /*argv*/[]) { rows.emit("find_highest_bit()/0"); } + + } + +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse_SUPPORT) + // find_highest_bit_by_intrinsic (/0) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = zero8; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = zero16; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = zero32; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = zero64; + + STLSOFT_SUPPRESS_UNUSED(i); + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); +#endif + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) + , &int_uint64 +#else + , NULL +#endif + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("find_highest_bit_by_intrinsic()/0"); + } } +#endif + } @@ -2116,7 +3467,98 @@ int main(int /*argc*/, char* /*argv*/[]) { rows.emit("find_highest_bit()/bit"); } + + } + +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse_SUPPORT) + // find_highest_bit_by_intrinsic (/bit) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = static_cast(1u << (i % 8u)); + + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = static_cast(1u << (i % 16u)); + + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = ss_uint32_t(1) << (i % 32u); + + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = ss_uint64_t(1) << (i % 64u); + + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); +#endif + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) + , &int_uint64 +#else + , NULL +#endif + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("find_highest_bit_by_intrinsic()/bit"); + } } +#endif // find_highest_bit (wide) @@ -2196,7 +3638,98 @@ int main(int /*argc*/, char* /*argv*/[]) { rows.emit("find_highest_bit()/wide"); } + + } + +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse_SUPPORT) + // find_highest_bit_by_intrinsic (/wide) + for (int W = int(NUM_SAMPLES) + 1; 0 != W; --W) + { + if (int(NUM_SAMPLES) + 1 == W) + { + rows.clear(); + } + + stopwatch_t sw; + + ss_uint64_t anchor_value = 0; + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint8_t const v = static_cast(i & 0xff); + + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint8 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint16_t const v = static_cast(i & 0xffff); + + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint16 = sw.get_nanoseconds(); + + + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint32_t const v = (ss_uint32_t(i) << 16) | ss_uint32_t(i & 0xffffu); + + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint32 = sw.get_nanoseconds(); + + +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) + sw.start(); + for (unsigned i = 0; NUM_ITERATIONS != i; ++i) + { + ss_uint64_t const v = (ss_uint64_t(i) << 32) | ss_uint64_t(i); + + anchor_value += stlsoft::find_highest_bit_by_intrinsic(v); + } + sw.stop(); + + interval_t const int_uint64 = sw.get_nanoseconds(); +#endif + + + if (W <= int(NUM_SAMPLES)) + { + rows.push( + &int_uint8 + , &int_uint16 + , &int_uint32 +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) + , &int_uint64 +#else + , NULL +#endif + , NULL + , anchor_value + ); + } + + if (1 == W) + { + rows.emit("find_highest_bit_by_intrinsic()/wide"); + } } +#endif // calculate_xor_over_range diff --git a/test/unit/stlsoft/util/test.unit.stlsoft.util.bit_functions/entry.cpp b/test/unit/stlsoft/util/test.unit.stlsoft.util.bit_functions/entry.cpp index 16166f3b..610ba160 100644 --- a/test/unit/stlsoft/util/test.unit.stlsoft.util.bit_functions/entry.cpp +++ b/test/unit/stlsoft/util/test.unit.stlsoft.util.bit_functions/entry.cpp @@ -4,7 +4,7 @@ * Purpose: Unit-tests for `stlsoft::bit_functions`. * * Created: 19th March 2010 - * Updated: 24th September 2026 + * Updated: 25th September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -42,17 +42,45 @@ namespace { - static void TEST_count_bits_Kernighan_method_FOR_32(); - static void TEST_count_bits_Kernighan_method_FOR_64(); - static void TEST_count_bits_8bit_table_FOR_32(); - static void TEST_count_bits_8bit_table_FOR_64(); - static void TEST_count_bits_FOR_32(); - static void TEST_count_bits_FOR_64(); + static void TEST_count_bits_Kernighan_method_FOR_8_BIT(); + static void TEST_count_bits_Kernighan_method_FOR_16_BIT(); + static void TEST_count_bits_Kernighan_method_FOR_32_BIT(); + static void TEST_count_bits_Kernighan_method_FOR_64_BIT(); + static void TEST_count_bits_8bit_table_FOR_8_BIT(); + static void TEST_count_bits_8bit_table_FOR_16_BIT(); + static void TEST_count_bits_8bit_table_FOR_32_BIT(); + static void TEST_count_bits_8bit_table_FOR_64_BIT(); + static void TEST_count_bits_FOR_8_BIT(); + static void TEST_count_bits_FOR_16_BIT(); + static void TEST_count_bits_FOR_32_BIT(); + static void TEST_count_bits_FOR_64_BIT(); static void TEST_count_bits_FOR_int(); - static void TEST_find_high_bit_FOR_8(); - static void TEST_find_high_bit_FOR_16(); - static void TEST_find_high_bit_FOR_32(); - static void TEST_find_high_bit_FOR_64(); +#if defined(STLSOFT_API_INTERNAL_bitfns_popcount_uint16) + static void TEST_count_bits_by_intrinsic_FOR_8_BIT(); + static void TEST_count_bits_by_intrinsic_FOR_16_BIT(); +#endif +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt_SUPPORT) + static void TEST_count_bits_by_intrinsic_FOR_32_BIT(); +#endif +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) + static void TEST_count_bits_by_intrinsic_FOR_64_BIT(); +#endif +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse_SUPPORT) + static void TEST_find_highest_bit_by_intrinsic_FOR_8_BIT(); + static void TEST_find_highest_bit_by_intrinsic_FOR_16_BIT(); + static void TEST_find_highest_bit_by_intrinsic_FOR_32_BIT(); +#endif +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) + static void TEST_find_highest_bit_by_intrinsic_FOR_64_BIT(); +#endif + static void TEST_find_high_bit_FOR_8_BIT(); + static void TEST_find_high_bit_FOR_16_BIT(); + static void TEST_find_high_bit_FOR_32_BIT(); + static void TEST_find_high_bit_FOR_64_BIT(); static void TEST_calculate_xor_over_range_FOR_8_BIT(); static void TEST_calculate_xor_over_range_FOR_16_BIT(); static void TEST_calculate_xor_over_range_FOR_32_BIT(); @@ -73,17 +101,45 @@ int main(int argc, char* argv[]) if (XTESTS_START_RUNNER("test.unit.stlsoft.util.bit_functions", verbosity)) { - XTESTS_RUN_CASE(TEST_count_bits_Kernighan_method_FOR_32); - XTESTS_RUN_CASE(TEST_count_bits_Kernighan_method_FOR_64); - XTESTS_RUN_CASE(TEST_count_bits_8bit_table_FOR_32); - XTESTS_RUN_CASE(TEST_count_bits_8bit_table_FOR_64); - XTESTS_RUN_CASE(TEST_count_bits_FOR_32); - XTESTS_RUN_CASE(TEST_count_bits_FOR_64); + XTESTS_RUN_CASE(TEST_count_bits_Kernighan_method_FOR_8_BIT); + XTESTS_RUN_CASE(TEST_count_bits_Kernighan_method_FOR_16_BIT); + XTESTS_RUN_CASE(TEST_count_bits_Kernighan_method_FOR_32_BIT); + XTESTS_RUN_CASE(TEST_count_bits_Kernighan_method_FOR_64_BIT); + XTESTS_RUN_CASE(TEST_count_bits_8bit_table_FOR_8_BIT); + XTESTS_RUN_CASE(TEST_count_bits_8bit_table_FOR_16_BIT); + XTESTS_RUN_CASE(TEST_count_bits_8bit_table_FOR_32_BIT); + XTESTS_RUN_CASE(TEST_count_bits_8bit_table_FOR_64_BIT); + XTESTS_RUN_CASE(TEST_count_bits_FOR_8_BIT); + XTESTS_RUN_CASE(TEST_count_bits_FOR_16_BIT); + XTESTS_RUN_CASE(TEST_count_bits_FOR_32_BIT); + XTESTS_RUN_CASE(TEST_count_bits_FOR_64_BIT); XTESTS_RUN_CASE(TEST_count_bits_FOR_int); - XTESTS_RUN_CASE(TEST_find_high_bit_FOR_8); - XTESTS_RUN_CASE(TEST_find_high_bit_FOR_16); - XTESTS_RUN_CASE(TEST_find_high_bit_FOR_32); - XTESTS_RUN_CASE(TEST_find_high_bit_FOR_64); +#if defined(STLSOFT_API_INTERNAL_bitfns_popcount_uint16) + XTESTS_RUN_CASE(TEST_count_bits_by_intrinsic_FOR_8_BIT); + XTESTS_RUN_CASE(TEST_count_bits_by_intrinsic_FOR_16_BIT); +#endif +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt_SUPPORT) + XTESTS_RUN_CASE(TEST_count_bits_by_intrinsic_FOR_32_BIT); +#endif +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) + XTESTS_RUN_CASE(TEST_count_bits_by_intrinsic_FOR_64_BIT); +#endif +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse_SUPPORT) + XTESTS_RUN_CASE(TEST_find_highest_bit_by_intrinsic_FOR_8_BIT); + XTESTS_RUN_CASE(TEST_find_highest_bit_by_intrinsic_FOR_16_BIT); + XTESTS_RUN_CASE(TEST_find_highest_bit_by_intrinsic_FOR_32_BIT); +#endif +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) + XTESTS_RUN_CASE(TEST_find_highest_bit_by_intrinsic_FOR_64_BIT); +#endif + XTESTS_RUN_CASE(TEST_find_high_bit_FOR_8_BIT); + XTESTS_RUN_CASE(TEST_find_high_bit_FOR_16_BIT); + XTESTS_RUN_CASE(TEST_find_high_bit_FOR_32_BIT); + XTESTS_RUN_CASE(TEST_find_high_bit_FOR_64_BIT); XTESTS_RUN_CASE(TEST_calculate_xor_over_range_FOR_8_BIT); XTESTS_RUN_CASE(TEST_calculate_xor_over_range_FOR_16_BIT); XTESTS_RUN_CASE(TEST_calculate_xor_over_range_FOR_32_BIT); @@ -110,7 +166,27 @@ namespace { using stlsoft::uint64_t; -static void TEST_count_bits_Kernighan_method_FOR_32() +static void TEST_count_bits_Kernighan_method_FOR_8_BIT() +{ + TEST_INT_EQ(0u, stlsoft::count_bits_by_Kernighan_method(uint8_t(0x00u))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_Kernighan_method(uint8_t(0xffu))); + TEST_INT_EQ(4u, stlsoft::count_bits_by_Kernighan_method(uint8_t(0x55u))); + TEST_INT_EQ(4u, stlsoft::count_bits_by_Kernighan_method(uint8_t(0xaau))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint8_t(0x80u))); + TEST_INT_EQ(2u, stlsoft::count_bits_by_Kernighan_method(uint8_t(0x81u))); +} + +static void TEST_count_bits_Kernighan_method_FOR_16_BIT() +{ + TEST_INT_EQ(0u, stlsoft::count_bits_by_Kernighan_method(uint16_t(0x0000u))); + TEST_INT_EQ(16u, stlsoft::count_bits_by_Kernighan_method(uint16_t(0xffffu))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_Kernighan_method(uint16_t(0x5555u))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_Kernighan_method(uint16_t(0xaaaau))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_Kernighan_method(uint16_t(0x8000u))); + TEST_INT_EQ(2u, stlsoft::count_bits_by_Kernighan_method(uint16_t(0x8001u))); +} + +static void TEST_count_bits_Kernighan_method_FOR_32_BIT() { TEST_INT_EQ(0u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0))); TEST_INT_EQ(0u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x00000000))); @@ -155,7 +231,7 @@ static void TEST_count_bits_Kernighan_method_FOR_32() TEST_INT_EQ(2u, stlsoft::count_bits_by_Kernighan_method(uint32_t(0x10001000))); } -static void TEST_count_bits_Kernighan_method_FOR_64() +static void TEST_count_bits_Kernighan_method_FOR_64_BIT() { TEST_INT_EQ(0u, stlsoft::count_bits_by_Kernighan_method(uint64_t(0))); TEST_INT_EQ(0u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000000)))); @@ -179,7 +255,27 @@ static void TEST_count_bits_Kernighan_method_FOR_64() TEST_INT_EQ(2u, stlsoft::count_bits_by_Kernighan_method(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x8000000000000001)))); } -static void TEST_count_bits_8bit_table_FOR_32() +static void TEST_count_bits_8bit_table_FOR_8_BIT() +{ + TEST_INT_EQ(0u, stlsoft::count_bits_by_8bit_table(uint8_t(0x00u))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_8bit_table(uint8_t(0xffu))); + TEST_INT_EQ(4u, stlsoft::count_bits_by_8bit_table(uint8_t(0x55u))); + TEST_INT_EQ(4u, stlsoft::count_bits_by_8bit_table(uint8_t(0xaau))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint8_t(0x80u))); + TEST_INT_EQ(2u, stlsoft::count_bits_by_8bit_table(uint8_t(0x81u))); +} + +static void TEST_count_bits_8bit_table_FOR_16_BIT() +{ + TEST_INT_EQ(0u, stlsoft::count_bits_by_8bit_table(uint16_t(0x0000u))); + TEST_INT_EQ(16u, stlsoft::count_bits_by_8bit_table(uint16_t(0xffffu))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_8bit_table(uint16_t(0x5555u))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_8bit_table(uint16_t(0xaaaau))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_8bit_table(uint16_t(0x8000u))); + TEST_INT_EQ(2u, stlsoft::count_bits_by_8bit_table(uint16_t(0x8001u))); +} + +static void TEST_count_bits_8bit_table_FOR_32_BIT() { TEST_INT_EQ(0u, stlsoft::count_bits_by_8bit_table(uint32_t(0))); TEST_INT_EQ(0u, stlsoft::count_bits_by_8bit_table(uint32_t(0x00000000))); @@ -224,7 +320,7 @@ static void TEST_count_bits_8bit_table_FOR_32() TEST_INT_EQ(2u, stlsoft::count_bits_by_8bit_table(uint32_t(0x10001000))); } -static void TEST_count_bits_8bit_table_FOR_64() +static void TEST_count_bits_8bit_table_FOR_64_BIT() { TEST_INT_EQ(0u, stlsoft::count_bits_by_8bit_table(uint64_t(0))); TEST_INT_EQ(0u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x0000000000000000)))); @@ -248,7 +344,25 @@ static void TEST_count_bits_8bit_table_FOR_64() TEST_INT_EQ(2u, stlsoft::count_bits_by_8bit_table(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0x8000000000000001)))); } -static void TEST_count_bits_FOR_32() +static void TEST_count_bits_FOR_8_BIT() +{ + TEST_INT_EQ(0u, stlsoft::count_bits(uint8_t(0))); + TEST_INT_EQ(8u, stlsoft::count_bits(uint8_t(0xff))); + TEST_INT_EQ(4u, stlsoft::count_bits(uint8_t(0x55))); + TEST_INT_EQ(1u, stlsoft::count_bits(uint8_t(0x80))); + TEST_INT_EQ(2u, stlsoft::count_bits(uint8_t(0x81))); +} + +static void TEST_count_bits_FOR_16_BIT() +{ + TEST_INT_EQ(0u, stlsoft::count_bits(uint16_t(0))); + TEST_INT_EQ(16u, stlsoft::count_bits(uint16_t(0xffff))); + TEST_INT_EQ(8u, stlsoft::count_bits(uint16_t(0x5555))); + TEST_INT_EQ(1u, stlsoft::count_bits(uint16_t(0x8000))); + TEST_INT_EQ(2u, stlsoft::count_bits(uint16_t(0x8001))); +} + +static void TEST_count_bits_FOR_32_BIT() { TEST_INT_EQ(0u, stlsoft::count_bits(uint32_t(0))); TEST_INT_EQ(32u, stlsoft::count_bits(uint32_t(0xffffffff))); @@ -257,7 +371,7 @@ static void TEST_count_bits_FOR_32() TEST_INT_EQ(2u, stlsoft::count_bits(uint32_t(0x80000001))); } -static void TEST_count_bits_FOR_64() +static void TEST_count_bits_FOR_64_BIT() { TEST_INT_EQ(0u, stlsoft::count_bits(uint64_t(0))); TEST_INT_EQ(64u, stlsoft::count_bits(uint64_t(STLSOFT_GEN_UINT64_SUFFIX(0xffffffffffffffff)))); @@ -278,7 +392,7 @@ static void TEST_count_bits_FOR_int() TEST_INT_EQ(1u, stlsoft::count_bits(static_cast(1u << (sizeof(int) * 8u - 1u)))); } -static void TEST_find_high_bit_FOR_8() +static void TEST_find_high_bit_FOR_8_BIT() { TEST_INT_EQ(0u, stlsoft::find_highest_bit(uint8_t(0))); @@ -299,7 +413,7 @@ static void TEST_find_high_bit_FOR_8() TEST_INT_EQ(8u, stlsoft::find_highest_bit(uint8_t(0xff))); } -static void TEST_find_high_bit_FOR_16() +static void TEST_find_high_bit_FOR_16_BIT() { TEST_INT_EQ(0u, stlsoft::find_highest_bit(uint16_t(0))); @@ -320,7 +434,7 @@ static void TEST_find_high_bit_FOR_16() TEST_INT_EQ(16u, stlsoft::find_highest_bit(uint16_t(0xffff))); } -static void TEST_find_high_bit_FOR_32() +static void TEST_find_high_bit_FOR_32_BIT() { TEST_INT_EQ(0u, stlsoft::find_highest_bit(uint32_t(0))); @@ -342,7 +456,7 @@ static void TEST_find_high_bit_FOR_32() TEST_INT_EQ(32u, stlsoft::find_highest_bit(uint32_t(0x80000000))); } -static void TEST_find_high_bit_FOR_64() +static void TEST_find_high_bit_FOR_64_BIT() { TEST_INT_EQ(0u, stlsoft::find_highest_bit(uint64_t(0))); @@ -543,6 +657,94 @@ static void TEST_calculate_xor_over_range_FOR_64_BIT() TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x00000000000001f0), stlsoft::calculate_xor_over_range(elements, 14u)); TEST_INT_EQ(STLSOFT_GEN_UINT64_SUFFIX(0x80000000000001f0), stlsoft::calculate_xor_over_range(elements, 15u)); } +#if defined(STLSOFT_API_INTERNAL_bitfns_popcount_uint16) + +static void TEST_count_bits_by_intrinsic_FOR_8_BIT() +{ + TEST_INT_EQ(0u, stlsoft::count_bits_by_intrinsic(uint8_t(0u))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_intrinsic(uint8_t(1u))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_intrinsic(uint8_t(0x80u))); + TEST_INT_EQ(8u, stlsoft::count_bits_by_intrinsic(uint8_t(0xffu))); + TEST_INT_EQ(stlsoft::count_bits_by_8bit_table(uint8_t(0x3cu)), stlsoft::count_bits_by_intrinsic(uint8_t(0x3cu))); +} + +static void TEST_count_bits_by_intrinsic_FOR_16_BIT() +{ + TEST_INT_EQ(0u, stlsoft::count_bits_by_intrinsic(uint16_t(0u))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_intrinsic(uint16_t(1u))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_intrinsic(uint16_t(0x8000u))); + TEST_INT_EQ(16u, stlsoft::count_bits_by_intrinsic(uint16_t(0xffffu))); + TEST_INT_EQ(stlsoft::count_bits_by_8bit_table(uint16_t(0x1234u)), stlsoft::count_bits_by_intrinsic(uint16_t(0x1234u))); +} +#endif +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt_SUPPORT) + +static void TEST_count_bits_by_intrinsic_FOR_32_BIT() +{ + TEST_INT_EQ(0u, stlsoft::count_bits_by_intrinsic(uint32_t(0u))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_intrinsic(uint32_t(1u))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_intrinsic(uint32_t(0x80000000u))); + TEST_INT_EQ(32u, stlsoft::count_bits_by_intrinsic(uint32_t(0xffffffffu))); + TEST_INT_EQ(stlsoft::count_bits_by_8bit_table(uint32_t(0x12345678u)), stlsoft::count_bits_by_intrinsic(uint32_t(0x12345678u))); +} +#endif +#if defined(STLSOFT_CF__builtin_popcount_SUPPORT) || \ + defined(STLSOFT_CF__popcnt64_SUPPORT) + +static void TEST_count_bits_by_intrinsic_FOR_64_BIT() +{ + TEST_INT_EQ(0u, stlsoft::count_bits_by_intrinsic(uint64_t(0u))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_intrinsic(uint64_t(1u))); + TEST_INT_EQ(1u, stlsoft::count_bits_by_intrinsic(STLSOFT_GEN_UINT64_SUFFIX(0x8000000000000000))); + TEST_INT_EQ(64u, stlsoft::count_bits_by_intrinsic(STLSOFT_GEN_UINT64_SUFFIX(0xffffffffffffffff))); + TEST_INT_EQ( + stlsoft::count_bits_by_8bit_table(STLSOFT_GEN_UINT64_SUFFIX(0x0123456789abcdef)) + , stlsoft::count_bits_by_intrinsic(STLSOFT_GEN_UINT64_SUFFIX(0x0123456789abcdef)) + ); +} +#endif +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse_SUPPORT) + +static void TEST_find_highest_bit_by_intrinsic_FOR_8_BIT() +{ + TEST_INT_EQ(0u, stlsoft::find_highest_bit_by_intrinsic(uint8_t(0u))); + TEST_INT_EQ(1u, stlsoft::find_highest_bit_by_intrinsic(uint8_t(1u))); + TEST_INT_EQ(8u, stlsoft::find_highest_bit_by_intrinsic(uint8_t(0x80u))); + TEST_INT_EQ(stlsoft::find_highest_bit(uint8_t(0x3cu)), stlsoft::find_highest_bit_by_intrinsic(uint8_t(0x3cu))); +} + +static void TEST_find_highest_bit_by_intrinsic_FOR_16_BIT() +{ + TEST_INT_EQ(0u, stlsoft::find_highest_bit_by_intrinsic(uint16_t(0u))); + TEST_INT_EQ(1u, stlsoft::find_highest_bit_by_intrinsic(uint16_t(1u))); + TEST_INT_EQ(16u, stlsoft::find_highest_bit_by_intrinsic(uint16_t(0x8000u))); + TEST_INT_EQ(stlsoft::find_highest_bit(uint16_t(0x03c0u)), stlsoft::find_highest_bit_by_intrinsic(uint16_t(0x03c0u))); +} + +static void TEST_find_highest_bit_by_intrinsic_FOR_32_BIT() +{ + TEST_INT_EQ(0u, stlsoft::find_highest_bit_by_intrinsic(uint32_t(0u))); + TEST_INT_EQ(1u, stlsoft::find_highest_bit_by_intrinsic(uint32_t(1u))); + TEST_INT_EQ(32u, stlsoft::find_highest_bit_by_intrinsic(uint32_t(0x80000000u))); + TEST_INT_EQ(stlsoft::find_highest_bit(uint32_t(0x0003c000u)), stlsoft::find_highest_bit_by_intrinsic(uint32_t(0x0003c000u))); +} +#endif +#if defined(STLSOFT_CF__builtin_clz_SUPPORT) || \ + defined(STLSOFT_CF__BitScanReverse64_SUPPORT) + +static void TEST_find_highest_bit_by_intrinsic_FOR_64_BIT() +{ + TEST_INT_EQ(0u, stlsoft::find_highest_bit_by_intrinsic(uint64_t(0u))); + TEST_INT_EQ(1u, stlsoft::find_highest_bit_by_intrinsic(uint64_t(1u))); + TEST_INT_EQ(64u, stlsoft::find_highest_bit_by_intrinsic(STLSOFT_GEN_UINT64_SUFFIX(0x8000000000000000))); + TEST_INT_EQ( + stlsoft::find_highest_bit(STLSOFT_GEN_UINT64_SUFFIX(0x000000030000c000)) + , stlsoft::find_highest_bit_by_intrinsic(STLSOFT_GEN_UINT64_SUFFIX(0x000000030000c000)) + ); +} +#endif } // anonymous namespace diff --git a/test/unit/test.unit.versions/entry.cpp b/test/unit/test.unit.versions/entry.cpp index 83be0623..7a5883d7 100644 --- a/test/unit/test.unit.versions/entry.cpp +++ b/test/unit/test.unit.versions/entry.cpp @@ -4,7 +4,7 @@ * Purpose: Unit-tests for versions * * Created: 23rd August 2025 - * Updated: 21st September 2026 + * Updated: 25th September 2026 * * ////////////////////////////////////////////////////////////////////// */ @@ -133,7 +133,7 @@ namespace { static void TEST__STLSOFT_VER() { - TEST_INT_EQ(_STLSOFT_VER_1_11_1_RC6, _STLSOFT_VER); + TEST_INT_EQ(_STLSOFT_VER_1_11_1_RC7, _STLSOFT_VER); } #ifdef STLSOFT_HAS_ACE From 38b7ce5a8fdea68e5c067ca4b3bcbc9bad3b1f19 Mon Sep 17 00:00:00 2001 From: Matt Wilson Date: Fri, 25 Sep 2026 12:08:38 +1000 Subject: [PATCH 13/13] fix(stlsoft): cast popcnt64; enable perf group gaps in CI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cast MSVC `__popcnt64` through `STLSOFT_C_CAST(int, …)` so `/WX` builds stop on C4244. Export `SIS_PERFTESTS_GROUPGAPS=1` for the performance job and only print the harness banner when that env is truey. Quiet getenv C4996 and narrow `~` promotions on uint8/16 ones patterns. Record Linux GCC 13.3 (~3.1 ns soft popcount vs table) and Apple Clang 21 CI findings in `strategy/BIT_FUNCTIONS.md`. --- .github/workflows/ci-cell.yml | 2 + include/stlsoft/api/internal/bitfns.h | 2 +- strategy/BIT_FUNCTIONS.md | 78 +++++++++---------- .../stlsoft/bit_functions/main.cpp | 36 +++++---- 4 files changed, 63 insertions(+), 55 deletions(-) diff --git a/.github/workflows/ci-cell.yml b/.github/workflows/ci-cell.yml index b14fae42..2214d999 100644 --- a/.github/workflows/ci-cell.yml +++ b/.github/workflows/ci-cell.yml @@ -606,4 +606,6 @@ jobs: if [ "${{ inputs.c-compiler }}" = "mingw" ]; then export PATH="/mingw64/bin:$PATH" fi + # Gaps between pattern groups in the bit-functions harness. + export SIS_PERFTESTS_GROUPGAPS=1 SIS_CMAKE_BUILD_DIR="${{ github.workspace }}/build" ./execute_performance_tests.sh -M diff --git a/include/stlsoft/api/internal/bitfns.h b/include/stlsoft/api/internal/bitfns.h index bb90c9f4..206bc323 100644 --- a/include/stlsoft/api/internal/bitfns.h +++ b/include/stlsoft/api/internal/bitfns.h @@ -145,7 +145,7 @@ STLSOFT_API_INTERNAL_bitfns_popcount_uint64( return STLSOFT_API_EXTERNAL_bitfns_popcount_ullong(STLSOFT_C_CAST(unsigned long long, v)); # elif defined(STLSOFT_API_EXTERNAL_bitfns_popcnt_uint64) - return STLSOFT_API_EXTERNAL_bitfns_popcnt_uint64(STLSOFT_C_CAST(unsigned __int64, v)); + return STLSOFT_C_CAST(int, STLSOFT_API_EXTERNAL_bitfns_popcnt_uint64(STLSOFT_C_CAST(unsigned __int64, v))); # else # error Unexpected diff --git a/strategy/BIT_FUNCTIONS.md b/strategy/BIT_FUNCTIONS.md index 4931c442..3d6065d9 100644 --- a/strategy/BIT_FUNCTIONS.md +++ b/strategy/BIT_FUNCTIONS.md @@ -14,15 +14,13 @@ The cost of leaving the table in place on Apple Clang is a fraction of a nanosec ## What was measured -Two bodies of results. Both are optimised release builds (`optimisation: speed`, `NDEBUG`), five-sample medians, 20,000,000 iterations. Anchors match across machines, so the loops did the same work. +Optimised release builds (`optimisation: speed`, `NDEBUG`), five-sample medians, 20,000,000 iterations. Anchors match across machines for the same input pattern, so the loops did the same work. -* **Cross-platform, commit `74b74407875100e0f1b6f5f7658a714c9a8d57b3`.** Sequential `i`, dense `~i`, volatile all-ones, and `ones^1`, plus `find_highest_bit` and `calculate_xor_over_range`. Linux Clang 18.1.3, Linux GCC 13.3, two macOS runs, Windows MSVC 18, Windows MinGW GCC 15.2. -* **Later local Apple Clang 21 only.** Adds volatile zero, a rotating one-hot bit, a both-halves-live pattern, and the unselected intrinsic rows, and prints ns/call on the same row. Use it for the one-hot, zero, and intrinsic cases. Do not treat it as a second cross-platform matrix. +* **Cross-platform, commit `74b74407875100e0f1b6f5f7658a714c9a8d57b3`.** Sequential `i`, dense `~i`, volatile all-ones, and `ones^1`, plus `find_highest_bit` and `calculate_xor_over_range`. Linux Clang 18.1.3, Linux GCC 13.3, two macOS runs, Windows MSVC 18, Windows MinGW GCC 15.2. No intrinsic rows. +* **Later local Apple Clang 21**, then **CI** (GitHub Actions): full matrix of widths including `uint8_t` / `uint16_t`, plus unselected intrinsic rows, zero, one-hot, and both-halves-live patterns. Use CI for cross-compiler intrinsic evidence; use the early matrix for pre-intrinsic baselines. -The run labeled macOS GCC printed `compiler: Clang 21.0.0`. Both Mac columns are Apple Clang. There is no GCC result from macOS in this set. - -### Popcount, cross-platform +### Popcount, cross-platform (pre-intrinsic) Nanoseconds per call, `uint32_t` / `uint64_t`. Divide the printed loop time by 20,000,000. @@ -33,64 +31,62 @@ Nanoseconds per call, `uint32_t` / `uint64_t`. Divide the printed loop time by 2 | 8-bit table, sequential `i` | 0.98 / 0.98 | 0.94 / 0.94 | 1.7 / 1.2 | 1.3 / 0.95 | 1.4 / 1.3 | 0.96 / 0.95 | | 8-bit table, all-ones | 0.94 / 1.9 | 0.93 / 1.9 | 1.1 / 1.3 | 1.0 / 1.3 | 1.3 / 2.1 | 0.94 / 1.8 | -`count_bits()` tracks the table on every toolchain, including Apple Clang. One MSVC cell is slower than the named table entry (sequential `uint64_t`, about 3.1 ns against 1.3 ns). The others match. The dispatcher is not the Kernighan path unless `STLSOFT_BIT_COUNT_BY_Kernighan` is defined. +`count_bits()` tracks the table on every toolchain, including Apple Clang. -### Apple Clang 21, intrinsics included +### CI: Linux GCC 13.3 (ubuntu, intrinsic rows) -Nanoseconds per call. Popcount cells are `uint32_t` / `uint64_t`. `count_bits()` matches the table on every row below, so it is omitted. Anchors match the earlier Apple run, including `/bit` at `40,000,000` and `/0` at `0`. +Nanoseconds per call. Popcount cells shown as `uint8_t` / `uint16_t` / `uint32_t` / `uint64_t` where useful; otherwise `uint32_t` / `uint64_t`. | Row | Kernighan | 8-bit table | intrinsic | | --- | --- | --- | --- | -| sequential `i` | 0.114 / 0.119 | 0.913 / 0.689 | 0.116 / 0.116 | -| `~i` | 0.127 / 0.141 | 0.900 / 0.901 | 0.128 / 0.130 | -| all-ones | 0.248 / 0.347 | 0.681 / 0.735 | 0.246 / 0.226 | -| `ones^1` | 0.200 / 0.359 | 0.929 / 0.788 | 0.199 / 0.236 | -| volatile zero | 0.227 / 0.346 | 0.681 / 0.708 | 0.232 / 0.247 | -| rotating bit | 0 / 0 | 0.915 / 0.723 | 0 / 0 | -| both halves live | 0.141 / 0.142 | 0.326 / 0.421 | 0.145 / 0.176 | +| sequential `i` | 1.83 / 3.22 / 4.77 / 4.78 | 0.63 / 0.64 / 0.94 / 0.95 | 3.11 / 3.11 / 3.12 / 3.12 | +| `~i` | 1.62 / 3.60 / 8.84 / 14.09 | 0.63 / 0.83 / 0.94 / 0.94 | 3.11 / 3.11 / 3.11 / 3.11 | +| all-ones | 2.80 / 9.98 / 11.26 / 31.48 | 0.62 / 0.64 / 0.97 / 1.84 | 3.11 / 3.12 / 3.11 / 3.12 | +| rotating bit | 0.62 / 0.62 / 0.62 / 0.94 | 1.18 / 1.19 / 1.06 / 1.87 | 3.43 / 3.11 / 3.11 / 3.11 | + +`count_bits()` tracks the table. The intrinsic is flat at about **3.11 ns** on every density and width — a libgcc-style helper, not hardware `POPCNT`. The table wins by about 3× on sequential and dense inputs. Dense Kernighan still scales with set-bit count (all-ones `uint64_t` ~31 ns). + -The rotating-bit times of 0 are deleted loops. The anchor is still `40,000,000`. The table on that row stayed near 0.9 ns. +### CI: Apple Clang 21 (macos) -`find_highest_bit` cells are `uint8_t` / `uint16_t` / `uint32_t` / `uint64_t`. +| Row | Kernighan | 8-bit table | intrinsic | +| --- | --- | --- | --- | +| sequential `i` (32/64) | 0.21 / 0.25 | 1.69 / 1.20 | 0.36 / 0.40 | +| all-ones (32/64) | 0.44 / 0.81 | 1.47 / 2.03 | 0.49 / 0.44 | +| rotating bit (32/64) | 0 / 0.47 | 1.77 / 1.98 | 0 / 0 | -| Row | scan | intrinsic | -| --- | --- | --- | -| sequential `i` | 0.294 / 0.468 / 0.434 / 0.435 | 0.197 / 0.198 / 0.113 / 0.113 | -| volatile zero | 0.236 / 0.423 / 0.504 / 0.827 | 0.224 / 0.227 / 0.234 / 0.223 | -| rotating bit | 0.111 / 0.225 / 0.340 / 0.473 | 0.129 / 0.148 / 0.112 / 0.111 | -| both halves live | 0.297 / 0.473 / 0.378 / 0.465 | 0.197 / 0.197 / 0.199 / 0.232 | +Kernighan and the intrinsic stay in the same hardware-popcount band; `/bit` again deletes several loops (0 ns, anchor still `80,000,000` with 8/16 columns). The table remains ~1–2 ns. `find_highest_bit_by_intrinsic` is clearly faster than the scan from 16 bits up (sequential 0.32 / 0.19 / 0.22 vs 1.53 / 1.25 / 1.23 for 16/32/64). ## Learnings -* **Kernighan scales with the number of set bits when the compiler leaves the loop alone.** All-ones is about 32 iterations at ~0.4 ns each for `uint32_t`, and about twice that for `uint64_t`, because the 64-bit function is two 32-bit calls. That is Linux Clang 18, Linux GCC 13.3, MSVC, and MinGW GCC 15.2. -* **Apple Clang rewrites the Kernighan source into the same instruction as the intrinsic.** On 32-bit rows the two times match (all-ones 0.248 ns and 0.246 ns). On 64-bit rows with a live high half the intrinsic is one `__builtin_popcountll` and Kernighan is still two 32-bit popcounts: all-ones 0.226 ns against 0.347 ns, `ones^1` 0.236 ns against 0.359 ns, volatile zero 0.247 ns against 0.346 ns. Sequential `uint64_t` does not show that gap, because its high half is zero. The both-halves-live row goes the other way by a small amount (Kernighan 0.142 ns, intrinsic 0.176 ns): the high half is a copy of a sparse `i`, not a dense word. -* **Generic x86-64 does not emit `POPCNT` for this loop.** The instruction is outside the baseline `-march=x86-64` target. Clang 18, GCC 13, GCC 15, and MSVC all kept the source loop in these release builds. Recognition of the idiom is not enough if the target forbids the instruction. -* **A one-hot input can delete both the Kernighan loop and the popcount intrinsic.** On Apple Clang 21, `count_bits_by_Kernighan_method()/bit` and `count_bits_by_intrinsic()/bit` reported 0 ns while the anchor stayed `40,000,000`. The table on that input stayed near 0.9 ns, and `count_bits()` followed the table. Do not read a zero time as "sparse popcount is free". -* **Sequential `uint64_t` is not a 64-bit input.** `uint64_t v = i` with `i` below 2^25 has a zero high half. Equal `uint32_t` and `uint64_t` times on that row do not mean the wide operation is free. All-ones and `~i` are the rows that exercise the high half. -* **The table is density-insensitive, with two shortcuts.** Four lookups are about 1 ns; eight are about 2 ns when the high half is real. All-zero and all-ones are cheaper when every byte hits the same slot. The both-halves-live row on Apple Clang was cheaper still, because the two halves are copies and the compiler reused lookups. That row is not the cost of arbitrary 64-bit data. -* **`find_highest_bit` is a software scan on every toolchain in the cross-platform matrix.** `uint32_t` and `uint64_t` stay close on the sequential input because the high half is zero. MSVC is the slowest and the one case where that zero high half still costs extra. On Apple Clang 21 the scan still grows with width: zero is 0.236 / 0.423 / 0.504 / 0.827 ns, and a rotating bit is 0.111 / 0.225 / 0.340 / 0.473 ns. -* **The `clz` intrinsic is flat on that same Apple Clang run.** Zero is about 0.22–0.23 ns at every width, which is the explicit zero test (`clz` of zero is undefined), not a descent. A rotating bit is 0.129 / 0.148 / 0.112 / 0.111 ns. The 8-bit scan is still slightly faster on that one-hot row (0.111 ns), because the intrinsic is a 32-bit `clz` of a zero-extended value. From 16 bits upward the intrinsic wins, and the both-halves-live `uint64_t` row is 0.232 ns against 0.465 ns for the scan. -* **XOR is a range scan.** At an average length of 32.5 elements it is several nanoseconds per call. `uint64_t` was the expensive width on Linux and MinGW, and the cheap width on Apple Clang. It is not comparable to the scalar rows, and it does not justify SIMD on this harness (`n` at most 64, data in L1). +* **Kernighan scales with the number of set bits when the compiler leaves the loop alone.** All-ones is about 32 iterations for `uint32_t`, and about twice that for `uint64_t` (two 32-bit calls). That is Linux Clang 18, Linux GCC 13.3, MSVC, and MinGW GCC 15.2. +* **Apple Clang rewrites the Kernighan source into the same class of instruction as the intrinsic.** CI macOS Clang 21 reproduces that. Local runs with a live high half showed the intrinsic beating two-call Kernighan on dense 64-bit words. +* **Generic x86-64 does not emit `POPCNT` for this loop or for `__builtin_popcount`.** CI Linux GCC 13.3 is the decisive measurement: the explicit intrinsic is a flat ~3.11 ns call (library helper), while the table is ~0.6–1.0 ns. Recognition of the idiom is not enough if the target forbids the instruction. +* **A one-hot input can delete Kernighan and the popcount intrinsic on Apple Clang.** `/bit` reports 0 ns while the anchor stays non-zero. The table stays near 1–2 ns. Do not read a zero time as "sparse popcount is free". Linux GCC does not delete those loops (~0.6 ns Kernighan, ~3.1 ns intrinsic). +* **Sequential `uint64_t` is not a 64-bit input.** `uint64_t v = i` with `i` below 2^25 has a zero high half. All-ones and `~i` are the rows that exercise the high half. +* **The table is density-insensitive relative to Kernighan**, with byte-wise shortcuts on all-zero / all-ones and on copied halves. +* **`find_highest_bit` is a software scan by default.** On Apple Clang the `clz` / `BitScanReverse` intrinsic is faster from 16 bits up. On Linux GCC 13.3 the intrinsic is only sometimes better (sequential 32-bit 1.24 vs 1.67; elsewhere similar or worse), so it does not justify a default switch on that toolchain alone. +* **XOR is a range scan** and does not justify SIMD on this harness (`n` at most 64, data in L1). ## Strategy * **Keep the 8-bit table as the default** for `count_bits()`, on every architecture. -* **Do not select Kernighan by Apple, by ARM, or by Clang.** The measured switch is "this compiler, on this target, rewrote this loop". Apple Clang 21 did. Linux Clang 18 did not. GCC on Apple Silicon has not been measured. -* **Leave `STLSOFT_BIT_COUNT_BY_Kernighan` as an opt-in** for a build that has been measured. The macro calls `count_bits_by_Kernighan_method`. -* **Keep `count_bits_by_intrinsic()` unselected until it is measured off Apple Clang.** The call goes through `STLSOFT_API_INTERNAL_bitfns_popcount_uint32` / `uint64`, which prefer `__builtin_popcount` / `__builtin_popcountll` and otherwise use `__popcnt` / `__popcnt64`. On Apple Clang 21 that matches rewritten Kernighan for 32-bit values and beats it for a dense 64-bit value, and it beats the table on every row whose loop was not deleted. `__builtin_popcount` without a `POPCNT` target can be a library call. That case is the one that could lose to the table, and it has not been timed. `count_bits()` still uses the table. -* **Keep `find_highest_bit_by_intrinsic()` unselected for the same reason.** It is 1-based and returns 0 for a zero input. GCC and Clang use `__builtin_clz` / `__builtin_clzll`; MSVC uses `_BitScanReverse` / `_BitScanReverse64`, with the index converted to the same 1-based result. The 8-bit and 16-bit forms are a 32-bit operation on a zero-extended value. On Apple Clang 21 it is the faster form from 16 bits up, including zero and a live high half. One Linux or Windows run of the same four rows is enough to decide whether the scan stays the default. `find_highest_bit()` still uses the shift-and-split scan. -* **MSVC popcount is gated by architecture and toolset, in `external/bitfns.h`.** `__popcnt16` and `__popcnt` are x86 and x64 from MSVC 15.00, and ARM64 from Visual Studio 2022 17.11 (`_MSC_VER` 1941). They are not promised for 32-bit ARM or for ARM64EC. `__popcnt64` is x64 from MSVC 15.00, and ARM64 and ARM64EC from 17.11. `_BitScanReverse64` is ARM64, ARM64EC, and x64 from MSVC 14.00. When a GCC/Clang builtin and an MSVC intrinsic are both visible, the builtin is the one the internal popcount function calls. +* **Do not select Kernighan by Apple, by ARM, or by Clang.** The measured switch is "this compiler, on this target, rewrote this loop". Apple Clang 21 did. Linux GCC 13.3 did not. +* **Leave `STLSOFT_BIT_COUNT_BY_Kernighan` as an opt-in** for a build that has been measured. +* **Keep `count_bits_by_intrinsic()` unselected.** On Apple Clang it matches rewritten Kernighan and beats the table when the loop is not deleted. On Linux GCC 13.3 without a `POPCNT`-legal arch it is ~3× slower than the table. `count_bits()` still uses the table. +* **Keep `find_highest_bit_by_intrinsic()` unselected.** Favourable on Apple Clang from 16 bits up; mixed on Linux GCC 13.3. `find_highest_bit()` still uses the shift-and-split scan. +* **MSVC popcount is gated by architecture and toolset, in `external/bitfns.h`.** `__popcnt16` / `__popcnt`: x86 and x64 from MSVC 15.00, ARM64 from VS 2022 17.11 (`_MSC_VER` 1941); not 32-bit ARM or ARM64EC. `__popcnt64`: x64 from 15.00, ARM64 and ARM64EC from 17.11. `_BitScanReverse64`: ARM64, ARM64EC, and x64 from 14.00. When a GCC/Clang builtin and an MSVC intrinsic are both visible, the builtin wins. * **Do not change `calculate_xor_over_range` for performance** on the strength of this harness. ## Not yet done -* Selecting `count_bits_by_intrinsic()` or `find_highest_bit_by_intrinsic()` into the dispatchers. Apple Clang 21 favours both. The missing evidence is the same rows on Linux Clang, Linux GCC, MSVC, and MinGW, including a target where `POPCNT` is not a baseline instruction. -* GCC, or Clang, on Apple Silicon as a compiler that is not Apple Clang. -* Linux or Windows built with `-march=native` or `/arch` that allows `POPCNT`, for the Kernighan idiom and for the explicit intrinsic. +* Selecting either intrinsic into the dispatchers. Apple Clang favours both; Linux GCC 13.3 favours the table for popcount and is mixed for highest-bit. Still wanted: MSVC and MinGW intrinsic rows, and a build with `-march=native` / `-mpopcnt` / `/arch` that allows `POPCNT`. +* GCC, or non-Apple Clang, on Apple Silicon. +* Correct INTERNAL `clz(0)` to full width (`32` / `64`), then optionally drive `find_highest_bit_by_intrinsic` through those adapters. diff --git a/test/performance/stlsoft/bit_functions/main.cpp b/test/performance/stlsoft/bit_functions/main.cpp index 10aed588..b19331e8 100644 --- a/test/performance/stlsoft/bit_functions/main.cpp +++ b/test/performance/stlsoft/bit_functions/main.cpp @@ -302,7 +302,14 @@ env_is_truey( char const* name ) { +#if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4996) +#endif char const* const env = ::getenv(name); +#if defined(_MSC_VER) +# pragma warning(pop) +#endif if (NULL == env || '\0' == *env) { @@ -518,9 +525,12 @@ emit_build_banner() std::cout << "width columns are loop ns; ns columns are ns/call; el columns are ns/element" << std::endl; - std::cout - << "Env: SIS_PERFTESTS_GROUPGAPS." - << std::endl; + if (env_is_truey("SIS_PERFTESTS_GROUPGAPS")) + { + std::cout + << "Env: SIS_PERFTESTS_GROUPGAPS=1" + << std::endl; + } } @@ -1245,8 +1255,8 @@ int main(int /*argc*/, char* /*argv*/[]) // ------------------------------------------------------------------ { - ss_uint8_t volatile ones8 = ~ss_uint8_t(0); - ss_uint16_t volatile ones16 = ~ss_uint16_t(0); + ss_uint8_t volatile ones8 = ss_uint8_t(~ss_uint8_t(0)); + ss_uint16_t volatile ones16 = ss_uint16_t(~ss_uint16_t(0)); ss_uint32_t volatile ones32 = ~ss_uint32_t(0); ss_uint64_t volatile ones64 = ~ss_uint64_t(0); @@ -1633,7 +1643,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint8_t const v = ~ss_uint8_t(0) ^ (ss_uint8_t(1) << (i % 8u)); + ss_uint8_t const v = ss_uint8_t(~ss_uint8_t(0)) ^ (ss_uint8_t(1) << (i % 8u)); anchor_value += stlsoft::count_bits_by_Kernighan_method(v); } @@ -1645,7 +1655,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint16_t const v = ~ss_uint16_t(0) ^ (ss_uint16_t(1) << (i % 16u)); + ss_uint16_t const v = ss_uint16_t(~ss_uint16_t(0)) ^ (ss_uint16_t(1) << (i % 16u)); anchor_value += stlsoft::count_bits_by_Kernighan_method(v); } @@ -1713,7 +1723,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint8_t const v = ~ss_uint8_t(0) ^ (ss_uint8_t(1) << (i % 8u)); + ss_uint8_t const v = ss_uint8_t(~ss_uint8_t(0)) ^ (ss_uint8_t(1) << (i % 8u)); anchor_value += stlsoft::count_bits_by_8bit_table(v); } @@ -1725,7 +1735,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint16_t const v = ~ss_uint16_t(0) ^ (ss_uint16_t(1) << (i % 16u)); + ss_uint16_t const v = ss_uint16_t(~ss_uint16_t(0)) ^ (ss_uint16_t(1) << (i % 16u)); anchor_value += stlsoft::count_bits_by_8bit_table(v); } @@ -1793,7 +1803,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint8_t const v = ~ss_uint8_t(0) ^ (ss_uint8_t(1) << (i % 8u)); + ss_uint8_t const v = ss_uint8_t(~ss_uint8_t(0)) ^ (ss_uint8_t(1) << (i % 8u)); anchor_value += stlsoft::count_bits(v); } @@ -1805,7 +1815,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint16_t const v = ~ss_uint16_t(0) ^ (ss_uint16_t(1) << (i % 16u)); + ss_uint16_t const v = ss_uint16_t(~ss_uint16_t(0)) ^ (ss_uint16_t(1) << (i % 16u)); anchor_value += stlsoft::count_bits(v); } @@ -1887,7 +1897,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint8_t const v = ~ss_uint8_t(0) ^ (ss_uint8_t(1) << (i % 8u)); + ss_uint8_t const v = ss_uint8_t(~ss_uint8_t(0)) ^ (ss_uint8_t(1) << (i % 8u)); anchor_value += stlsoft::count_bits_by_intrinsic(v); } @@ -1899,7 +1909,7 @@ int main(int /*argc*/, char* /*argv*/[]) sw.start(); for (unsigned i = 0; NUM_ITERATIONS != i; ++i) { - ss_uint16_t const v = ~ss_uint16_t(0) ^ (ss_uint16_t(1) << (i % 16u)); + ss_uint16_t const v = ss_uint16_t(~ss_uint16_t(0)) ^ (ss_uint16_t(1) << (i % 16u)); anchor_value += stlsoft::count_bits_by_intrinsic(v); }