Speed up processing mostly visible strings in convertIntoString

This commit causes small (~5%) slowdown when processing strings
that consist mostly of escaped characters, but improves throughput
by about 100% for strings that consist mostly of characters that
do not need escaping.
This commit is contained in:
Martin Hořeňovský
2025-09-22 22:41:14 +02:00
parent 756ae05d30
commit a00d654437
2 changed files with 46 additions and 21 deletions

View File

@@ -61,35 +61,35 @@ namespace Detail {
std::string ret; std::string ret;
// This is enough for the "don't escape invisibles" case, and a good // This is enough for the "don't escape invisibles" case, and a good
// lower bound on the "escape invisibles" case. // lower bound on the "escape invisibles" case.
ret.reserve(string.size() + 2); ret.reserve( string.size() + 2 );
if (!escapeInvisibles) { if ( !escapeInvisibles ) {
ret += '"'; ret += '"';
ret += string; ret += string;
ret += '"'; ret += '"';
return ret; return ret;
} }
size_t last_start = 0;
auto write_to = [&]( size_t idx ) {
if ( last_start < idx ) {
ret += string.substr( last_start, idx - last_start );
}
last_start = idx + 1;
};
ret += '"'; ret += '"';
for (char c : string) { for ( size_t i = 0; i < string.size(); ++i ) {
switch (c) { const char c = string[i];
case '\r': if ( c == '\r' || c == '\n' || c == '\t' || c == '\f' ) {
ret.append("\\r"); write_to( i );
break; if ( c == '\r' ) { ret.append( "\\r" ); }
case '\n': if ( c == '\n' ) { ret.append( "\\n" ); }
ret.append("\\n"); if ( c == '\t' ) { ret.append( "\\t" ); }
break; if ( c == '\f' ) { ret.append( "\\f" ); }
case '\t':
ret.append("\\t");
break;
case '\f':
ret.append("\\f");
break;
default:
ret.push_back(c);
break;
} }
} }
write_to( string.size() );
ret += '"'; ret += '"';
return ret; return ret;

View File

@@ -6,11 +6,13 @@
// SPDX-License-Identifier: BSL-1.0 // SPDX-License-Identifier: BSL-1.0
#include <catch2/benchmark/catch_benchmark.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/internal/catch_enum_values_registry.hpp> #include <catch2/internal/catch_enum_values_registry.hpp>
#include <catch2/matchers/catch_matchers_string.hpp> #include <catch2/matchers/catch_matchers_string.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp> #include <catch2/matchers/catch_matchers_vector.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/catch_template_test_macros.hpp>
#include <chrono> #include <chrono>
@@ -139,3 +141,26 @@ TEST_CASE( "Exception thrown inside stringify does not fail the test", "[toStrin
ThrowsOnStringification tos; ThrowsOnStringification tos;
CHECK( tos == tos ); CHECK( tos == tos );
} }
TEST_CASE( "string escaping benchmark", "[toString][!benchmark]" ) {
const auto input_length = GENERATE( as<size_t>{}, 10, 100, 10'000, 100'000 );
std::string test_input( input_length, 'a' );
BENCHMARK( "no-escape string, no-escaping, len=" +
std::to_string( input_length ) ) {
return Catch::Detail::convertIntoString( test_input, false );
};
BENCHMARK( "no-escape string, escaping, len=" +
std::to_string( input_length ) ) {
return Catch::Detail::convertIntoString( test_input, true );
};
std::string escape_input( input_length, '\r' );
BENCHMARK( "full escape string, no-escaping, len=" +
std::to_string( input_length ) ) {
return Catch::Detail::convertIntoString( escape_input, false );
};
BENCHMARK( "full escape string, escaping, len=" +
std::to_string( input_length ) ) {
return Catch::Detail::convertIntoString( escape_input, true );
};
}