From 164eedd5fb96ef707a54595b306792547771f400 Mon Sep 17 00:00:00 2001 From: "James E. King III" Date: Mon, 23 Jun 2025 21:18:14 -0400 Subject: [PATCH] Improve is_palindrome performance significantly (C++03 compat) Refactored is_palindrome to use std::equal yielding a 57% performance gain in limited testing (x86_64, gcc 12.2). --- build.jam | 1 + include/boost/algorithm/is_palindrome.hpp | 24 ++----- performance/Jamfile.v2 | 30 +++++++++ performance/perf_is_palindrome.cpp | 82 +++++++++++++++++++++++ 4 files changed, 117 insertions(+), 20 deletions(-) create mode 100644 performance/Jamfile.v2 create mode 100644 performance/perf_is_palindrome.cpp diff --git a/build.jam b/build.jam index 49099290d..0fd92cb34 100644 --- a/build.jam +++ b/build.jam @@ -34,6 +34,7 @@ explicit [ alias all : boost_algorithm test example minmax/example minmax/test + performance string/example string/test ] ; diff --git a/include/boost/algorithm/is_palindrome.hpp b/include/boost/algorithm/is_palindrome.hpp index 531f82ecf..e90966539 100644 --- a/include/boost/algorithm/is_palindrome.hpp +++ b/include/boost/algorithm/is_palindrome.hpp @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -38,26 +39,9 @@ namespace boost { namespace algorithm { template bool is_palindrome(BidirectionalIterator begin, BidirectionalIterator end, Predicate p) { - if(begin == end) - { - return true; - } - - --end; - while(begin != end) - { - if(!p(*begin, *end)) - { - return false; - } - ++begin; - if(begin == end) - { - break; - } - --end; - } - return true; + BidirectionalIterator midpoint = begin; + std::advance(midpoint, std::distance(begin, end) / 2); + return std::equal(begin, midpoint, boost::make_reverse_iterator(end), p); } /// \fn is_palindrome ( BidirectionalIterator begin, BidirectionalIterator end ) diff --git a/performance/Jamfile.v2 b/performance/Jamfile.v2 new file mode 100644 index 000000000..1ed90b422 --- /dev/null +++ b/performance/Jamfile.v2 @@ -0,0 +1,30 @@ +#============================================================================== +# Copyright (c) Antony Polukhin, 2012-2024. +# Copyright (c) James E. King III, 2025. +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#============================================================================== + +# performance tests +import testing ; +import path ; + +path-constant TEST_DIR : . ; + +project performance + : source-location ./ + : requirements + /boost/chrono//boost_chrono + /boost/program_options//boost_program_options + /boost/system//boost_system + static + freebsd:"-lrt" + linux:"-lrt" + gcc:-fvisibility=hidden + intel-linux:-fvisibility=hidden + sun:-xldscope=hidden + : default-build release + ; + +run perf_is_palindrome.cpp : $(TEST_DIR) ; diff --git a/performance/perf_is_palindrome.cpp b/performance/perf_is_palindrome.cpp new file mode 100644 index 000000000..4459983de --- /dev/null +++ b/performance/perf_is_palindrome.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include +#include +#include + +using boost::algorithm::is_palindrome; +namespace po = boost::program_options; + +void run_is_palindrome_test(size_t iterations) { + // Prepare test cases: small, medium, large, palindromes and non-palindromes + std::vector test_cases = { + "madam", "racecar", "level", "hello", "world", // small + std::string(100, 'a'), // medium palindrome + std::string(42, 'a') + "b" + std::string(57, 'a'), // medium non-palindrome + std::string(10000, 'x'), // large palindrome + std::string(3248, 'y') + "z" + std::string(6751, 'y'), // large non-palindrome + }; + + // Make sure some are not palindromes + test_cases.push_back("abcdefg"); + test_cases.push_back("abccba"); + test_cases.push_back("abccbx"); + + // Warm up + volatile bool dummy = false; + for (const auto& s : test_cases) { + dummy ^= is_palindrome(s); + } + + // Start timing + boost::chrono::high_resolution_clock::time_point start = boost::chrono::high_resolution_clock::now(); + + size_t palindrome_count = 0; + for (size_t i = 0; i < iterations; ++i) { + for (const auto& s : test_cases) { + if (is_palindrome(s)) { + ++palindrome_count; + } + } + } + + boost::chrono::high_resolution_clock::time_point end = boost::chrono::high_resolution_clock::now(); + boost::chrono::duration elapsed = end - start; + + size_t total_runs = iterations * test_cases.size(); + double avg_time_per_run = elapsed.count() / total_runs * 1e6; // microseconds + + std::cout << "Total runs: " << total_runs << std::endl; + std::cout << "Total palindromes found: " << palindrome_count << std::endl; + std::cout << "Total elapsed time: " << elapsed.count() << " seconds" << std::endl; + std::cout << "Average time per is_palindrome: " << avg_time_per_run << " microseconds" << std::endl; +} + +int main(int argc, char* argv[]) { + size_t iterations = 1000000; + + po::options_description desc("Allowed options"); + desc.add_options() + ("help,h", "produce help message") + ("iterations,n", po::value(&iterations)->default_value(1000000), "number of iterations") + ; + + po::variables_map vm; + try { + po::store(po::parse_command_line(argc, argv, desc), vm); + po::notify(vm); + } catch (const std::exception& ex) { + std::cerr << "Error parsing options: " << ex.what() << std::endl; + return 1; + } + + if (vm.count("help")) { + std::cout << desc << std::endl; + return 0; + } + + run_is_palindrome_test(iterations); + return 0; // this is not a correctness test; there are others for that +}