Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMartin Hořeňovský2025-09-22 22:41:14 +0200
committerMartin Hořeňovský2025-09-27 22:43:14 +0200
commita00d6544375e92ccedec786aaee47ac392ea4900 (patch)
treeb989e018ee884edcc12b604b6e9477b36cc6f38c
parent756ae05d307168eaa27bd904dbd8e591a6406889 (diff)
downloadcatch3-a00d6544375e92ccedec786aaee47ac392ea4900.tar.gz
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.
-rw-r--r--src/catch2/catch_tostring.cpp38
-rw-r--r--tests/SelfTest/IntrospectiveTests/ToString.tests.cpp29
2 files changed, 46 insertions, 21 deletions
diff --git a/src/catch2/catch_tostring.cpp b/src/catch2/catch_tostring.cpp
index 4d80b173..cc686567 100644
--- a/src/catch2/catch_tostring.cpp
+++ b/src/catch2/catch_tostring.cpp
@@ -61,35 +61,35 @@ namespace Detail {
std::string ret;
// This is enough for the "don't escape invisibles" case, and a good
// lower bound on the "escape invisibles" case.
- ret.reserve(string.size() + 2);
+ ret.reserve( string.size() + 2 );
- if (!escapeInvisibles) {
+ if ( !escapeInvisibles ) {
ret += '"';
ret += string;
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 += '"';
- for (char c : string) {
- switch (c) {
- case '\r':
- ret.append("\\r");
- break;
- case '\n':
- ret.append("\\n");
- break;
- case '\t':
- ret.append("\\t");
- break;
- case '\f':
- ret.append("\\f");
- break;
- default:
- ret.push_back(c);
- break;
+ for ( size_t i = 0; i < string.size(); ++i ) {
+ const char c = string[i];
+ if ( c == '\r' || c == '\n' || c == '\t' || c == '\f' ) {
+ write_to( i );
+ if ( c == '\r' ) { ret.append( "\\r" ); }
+ if ( c == '\n' ) { ret.append( "\\n" ); }
+ if ( c == '\t' ) { ret.append( "\\t" ); }
+ if ( c == '\f' ) { ret.append( "\\f" ); }
}
}
+ write_to( string.size() );
ret += '"';
return ret;
diff --git a/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp b/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp
index be639ce3..01d989a7 100644
--- a/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp
+++ b/tests/SelfTest/IntrospectiveTests/ToString.tests.cpp
@@ -6,11 +6,13 @@
// 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/matchers/catch_matchers_string.hpp>
#include <catch2/matchers/catch_matchers_vector.hpp>
-#include <catch2/catch_test_macros.hpp>
-#include <catch2/catch_template_test_macros.hpp>
#include <chrono>
@@ -139,3 +141,26 @@ TEST_CASE( "Exception thrown inside stringify does not fail the test", "[toStrin
ThrowsOnStringification 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 );
+ };
+}