mirror of
https://github.com/catchorg/Catch2.git
synced 2025-08-25 07:55:40 +02:00
Improve performance of writing JSON values
The old code was exceedingly simple, as it went char-by-char and decided whether to write it to the output stream as-is, or escaped. This caused a _lot_ of stream writes of individual characters. The new code instead looks for characters that need escaping, and bulk-writes the non-escaped characters in between them. This leads to about the same performance for strings that comprise of only escaped characters, and 3-10x improvement for strings without any escaping needed. In practice, we should expect the former rather than the latter, but this is still nice improvement.
This commit is contained in:
@@ -7,8 +7,36 @@
|
|||||||
// SPDX-License-Identifier: BSL-1.0
|
// SPDX-License-Identifier: BSL-1.0
|
||||||
#include <catch2/internal/catch_enforce.hpp>
|
#include <catch2/internal/catch_enforce.hpp>
|
||||||
#include <catch2/internal/catch_jsonwriter.hpp>
|
#include <catch2/internal/catch_jsonwriter.hpp>
|
||||||
|
#include <catch2/internal/catch_unreachable.hpp>
|
||||||
|
|
||||||
namespace Catch {
|
namespace Catch {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
static bool needsEscape( char c ) {
|
||||||
|
return c == '"' || c == '\\' || c == '\b' || c == '\f' ||
|
||||||
|
c == '\n' || c == '\r' || c == '\t';
|
||||||
|
}
|
||||||
|
|
||||||
|
static Catch::StringRef makeEscapeStringRef( char c ) {
|
||||||
|
if ( c == '"' ) {
|
||||||
|
return "\\\""_sr;
|
||||||
|
} else if ( c == '\\' ) {
|
||||||
|
return "\\\\"_sr;
|
||||||
|
} else if ( c == '\b' ) {
|
||||||
|
return "\\b"_sr;
|
||||||
|
} else if ( c == '\f' ) {
|
||||||
|
return "\\f"_sr;
|
||||||
|
} else if ( c == '\n' ) {
|
||||||
|
return "\\n"_sr;
|
||||||
|
} else if ( c == '\r' ) {
|
||||||
|
return "\\r"_sr;
|
||||||
|
} else if ( c == '\t' ) {
|
||||||
|
return "\\t"_sr;
|
||||||
|
}
|
||||||
|
Catch::Detail::Unreachable();
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void JsonUtils::indent( std::ostream& os, std::uint64_t level ) {
|
void JsonUtils::indent( std::ostream& os, std::uint64_t level ) {
|
||||||
for ( std::uint64_t i = 0; i < level; ++i ) {
|
for ( std::uint64_t i = 0; i < level; ++i ) {
|
||||||
os << " ";
|
os << " ";
|
||||||
@@ -118,30 +146,19 @@ namespace Catch {
|
|||||||
|
|
||||||
void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) {
|
void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) {
|
||||||
if ( quote ) { m_os << '"'; }
|
if ( quote ) { m_os << '"'; }
|
||||||
for (char c : value) {
|
size_t current_start = 0;
|
||||||
// Escape list taken from https://www.json.org/json-en.html,
|
for ( size_t i = 0; i < value.size(); ++i ) {
|
||||||
// string definition.
|
if ( needsEscape( value[i] ) ) {
|
||||||
// Note that while forward slash _can_ be escaped, it does
|
if ( current_start < i ) {
|
||||||
// not have to be, if JSON is not further embedded somewhere
|
m_os << value.substr( current_start, i - current_start );
|
||||||
// where forward slash is meaningful.
|
}
|
||||||
if ( c == '"' ) {
|
m_os << makeEscapeStringRef( value[i] );
|
||||||
m_os << "\\\"";
|
current_start = i + 1;
|
||||||
} else if ( c == '\\' ) {
|
|
||||||
m_os << "\\\\";
|
|
||||||
} else if ( c == '\b' ) {
|
|
||||||
m_os << "\\b";
|
|
||||||
} else if ( c == '\f' ) {
|
|
||||||
m_os << "\\f";
|
|
||||||
} else if ( c == '\n' ) {
|
|
||||||
m_os << "\\n";
|
|
||||||
} else if ( c == '\r' ) {
|
|
||||||
m_os << "\\r";
|
|
||||||
} else if ( c == '\t' ) {
|
|
||||||
m_os << "\\t";
|
|
||||||
} else {
|
|
||||||
m_os << c;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ( current_start < value.size() ) {
|
||||||
|
m_os << value.substr( current_start, value.size() - current_start );
|
||||||
|
}
|
||||||
if ( quote ) { m_os << '"'; }
|
if ( quote ) { m_os << '"'; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -7,6 +7,8 @@
|
|||||||
// SPDX-License-Identifier: BSL-1.0
|
// SPDX-License-Identifier: BSL-1.0
|
||||||
|
|
||||||
#include <catch2/catch_test_macros.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <catch2/benchmark/catch_benchmark.hpp>
|
||||||
|
#include <catch2/generators/catch_generators.hpp>
|
||||||
#include <catch2/internal/catch_jsonwriter.hpp>
|
#include <catch2/internal/catch_jsonwriter.hpp>
|
||||||
#include <catch2/matchers/catch_matchers_string.hpp>
|
#include <catch2/matchers/catch_matchers_string.hpp>
|
||||||
|
|
||||||
@@ -17,7 +19,6 @@ namespace {
|
|||||||
static std::ostream& operator<<( std::ostream& os, Custom const& ) {
|
static std::ostream& operator<<( std::ostream& os, Custom const& ) {
|
||||||
return os << "custom";
|
return os << "custom";
|
||||||
}
|
}
|
||||||
} // namespace
|
|
||||||
|
|
||||||
TEST_CASE( "JsonWriter", "[JSON][JsonWriter]" ) {
|
TEST_CASE( "JsonWriter", "[JSON][JsonWriter]" ) {
|
||||||
|
|
||||||
@@ -150,3 +151,28 @@ TEST_CASE( "JsonWriter escapes characters in strings properly", "[JsonWriter]" )
|
|||||||
REQUIRE( sstream.str() == "\"\\\\/\\t\\r\\n\"" );
|
REQUIRE( sstream.str() == "\"\\\\/\\t\\r\\n\"" );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE( "JsonWriter benchmarks", "[JsonWriter][!benchmark]" ) {
|
||||||
|
const auto input_length = GENERATE( as<size_t>{}, 10, 100, 10'000 );
|
||||||
|
std::string test_input( input_length, 'a' );
|
||||||
|
BENCHMARK_ADVANCED( "write string, no-escaping, len=" +
|
||||||
|
std::to_string( input_length ) )(
|
||||||
|
Catch::Benchmark::Chronometer meter ) {
|
||||||
|
std::stringstream sstream;
|
||||||
|
meter.measure( [&]( int ) {
|
||||||
|
Catch::JsonValueWriter( sstream ).write( test_input );
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string escape_input( input_length, '\b' );
|
||||||
|
BENCHMARK_ADVANCED( "write string, all-escaped, len=" +
|
||||||
|
std::to_string( input_length ) )(
|
||||||
|
Catch::Benchmark::Chronometer meter ) {
|
||||||
|
std::stringstream sstream;
|
||||||
|
meter.measure( [&]( int ) {
|
||||||
|
Catch::JsonValueWriter( sstream ).write( escape_input );
|
||||||
|
} );
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
Reference in New Issue
Block a user