Skip to content

Improve is_palindrome performance significantly (C++03 compat) #126

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.jam
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ explicit
[ alias all : boost_algorithm test
example
minmax/example minmax/test
performance
string/example string/test
]
;
Expand Down
24 changes: 4 additions & 20 deletions include/boost/algorithm/is_palindrome.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <cstring>

#include <boost/config.hpp>
#include <boost/iterator/reverse_iterator.hpp>
#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>

Expand All @@ -38,26 +39,9 @@ namespace boost { namespace algorithm {
template <typename BidirectionalIterator, typename Predicate>
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 )
Expand Down
30 changes: 30 additions & 0 deletions performance/Jamfile.v2
Original file line number Diff line number Diff line change
@@ -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
<library>/boost/chrono//boost_chrono
<library>/boost/program_options//boost_program_options
<library>/boost/system//boost_system
<link>static
<target-os>freebsd:<linkflags>"-lrt"
<target-os>linux:<linkflags>"-lrt"
<toolset>gcc:<cxxflags>-fvisibility=hidden
<toolset>intel-linux:<cxxflags>-fvisibility=hidden
<toolset>sun:<cxxflags>-xldscope=hidden
: default-build release
;

run perf_is_palindrome.cpp : $(TEST_DIR) ;
82 changes: 82 additions & 0 deletions performance/perf_is_palindrome.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#include <boost/algorithm/is_palindrome.hpp>
#include <boost/chrono.hpp>
#include <boost/program_options.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

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<std::string> 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<double> 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<size_t>(&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
}