Args and TokenStream use StringRefs instead of std::strings

Because TokenStream is copied around a lot, moving it to use
`StringRef` removes _a lot_ of allocations per `Opt` in the parser.
Args are not copied around much, but changing them as well makes it
obvious that they do not participate in the ownership.

The changes add up to removing ~180 allocations for "empty"
invocation of the test binary.
(`./tests/ExtraTests/NoTests --allow-running-no-tests -o /dev/null`
is down to 317 allocs)
This commit is contained in:
Martin Hořeňovský 2023-12-27 16:19:49 +01:00
parent cd3c7ebe87
commit 5d637d4c6b
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
3 changed files with 38 additions and 22 deletions

View File

@ -37,6 +37,19 @@ namespace {
return optName; return optName;
} }
static size_t find_first_separator(Catch::StringRef sr) {
auto is_separator = []( char c ) {
return c == ' ' || c == ':' || c == '=';
};
size_t pos = 0;
while (pos < sr.size()) {
if (is_separator(sr[pos])) { return pos; }
++pos;
}
return Catch::StringRef::npos;
}
} // namespace } // namespace
namespace Catch { namespace Catch {
@ -52,23 +65,23 @@ namespace Catch {
} }
if ( it != itEnd ) { if ( it != itEnd ) {
auto const& next = *it; StringRef next = *it;
if ( isOptPrefix( next[0] ) ) { if ( isOptPrefix( next[0] ) ) {
auto delimiterPos = next.find_first_of( " :=" ); auto delimiterPos = find_first_separator(next);
if ( delimiterPos != std::string::npos ) { if ( delimiterPos != StringRef::npos ) {
m_tokenBuffer.push_back( m_tokenBuffer.push_back(
{ TokenType::Option, { TokenType::Option,
next.substr( 0, delimiterPos ) } ); next.substr( 0, delimiterPos ) } );
m_tokenBuffer.push_back( m_tokenBuffer.push_back(
{ TokenType::Argument, { TokenType::Argument,
next.substr( delimiterPos + 1 ) } ); next.substr( delimiterPos + 1, next.size() ) } );
} else { } else {
if ( next[1] != '-' && next.size() > 2 ) { if ( next[1] != '-' && next.size() > 2 ) {
std::string opt = "- "; // Combined short args, e.g. "-ab" for "-a -b"
for ( size_t i = 1; i < next.size(); ++i ) { for ( size_t i = 1; i < next.size(); ++i ) {
opt[1] = next[i];
m_tokenBuffer.push_back( m_tokenBuffer.push_back(
{ TokenType::Option, opt } ); { TokenType::Option,
next.substr( i, 1 ) } );
} }
} else { } else {
m_tokenBuffer.push_back( m_tokenBuffer.push_back(
@ -128,7 +141,7 @@ namespace Catch {
size_t ParserBase::cardinality() const { return 1; } size_t ParserBase::cardinality() const { return 1; }
InternalParseResult ParserBase::parse( Args const& args ) const { InternalParseResult ParserBase::parse( Args const& args ) const {
return parse( args.exeName(), TokenStream( args ) ); return parse( static_cast<std::string>(args.exeName()), TokenStream( args ) );
} }
ParseState::ParseState( ParseResultType type, ParseState::ParseState( ParseResultType type,
@ -166,7 +179,7 @@ namespace Catch {
auto valueRef = auto valueRef =
static_cast<Detail::BoundValueRefBase*>(m_ref.get()); static_cast<Detail::BoundValueRefBase*>(m_ref.get());
auto result = valueRef->setValue(remainingTokens->token); auto result = valueRef->setValue(static_cast<std::string>(token.token));
if (!result) if (!result)
return Detail::InternalParseResult(result); return Detail::InternalParseResult(result);
else else
@ -192,7 +205,7 @@ namespace Catch {
return { oss.str(), m_description }; return { oss.str(), m_description };
} }
bool Opt::isMatch(std::string const& optToken) const { bool Opt::isMatch(StringRef optToken) const {
auto normalisedToken = normaliseOpt(optToken); auto normalisedToken = normaliseOpt(optToken);
for (auto const& name : m_optNames) { for (auto const& name : m_optNames) {
if (normaliseOpt(name) == normalisedToken) if (normaliseOpt(name) == normalisedToken)
@ -237,7 +250,7 @@ namespace Catch {
return Detail::InternalParseResult::runtimeError( return Detail::InternalParseResult::runtimeError(
"Expected argument following " + "Expected argument following " +
token.token); token.token);
const auto result = valueRef->setValue(argToken.token); const auto result = valueRef->setValue(static_cast<std::string>(argToken.token));
if (!result) if (!result)
return Detail::InternalParseResult(result); return Detail::InternalParseResult(result);
if (result.value() == if (result.value() ==
@ -433,7 +446,7 @@ namespace Catch {
Args::Args(int argc, char const* const* argv) : Args::Args(int argc, char const* const* argv) :
m_exeName(argv[0]), m_args(argv + 1, argv + argc) {} m_exeName(argv[0]), m_args(argv + 1, argv + argc) {}
Args::Args(std::initializer_list<std::string> args) : Args::Args(std::initializer_list<StringRef> args) :
m_exeName(*args.begin()), m_exeName(*args.begin()),
m_args(args.begin() + 1, args.end()) {} m_args(args.begin() + 1, args.end()) {}

View File

@ -102,17 +102,16 @@ namespace Catch {
enum class TokenType { Option, Argument }; enum class TokenType { Option, Argument };
struct Token { struct Token {
TokenType type; TokenType type;
std::string token; StringRef token;
}; };
// Abstracts iterators into args as a stream of tokens, with option // Abstracts iterators into args as a stream of tokens, with option
// arguments uniformly handled // arguments uniformly handled
class TokenStream { class TokenStream {
using Iterator = std::vector<std::string>::const_iterator; using Iterator = std::vector<StringRef>::const_iterator;
Iterator it; Iterator it;
Iterator itEnd; Iterator itEnd;
std::vector<Token> m_tokenBuffer; std::vector<Token> m_tokenBuffer;
void loadBuffer(); void loadBuffer();
public: public:
@ -582,7 +581,7 @@ namespace Catch {
Detail::HelpColumns getHelpColumns() const; Detail::HelpColumns getHelpColumns() const;
bool isMatch(std::string const& optToken) const; bool isMatch(StringRef optToken) const;
using ParserBase::parse; using ParserBase::parse;
@ -676,18 +675,20 @@ namespace Catch {
Detail::TokenStream const& tokens) const override; Detail::TokenStream const& tokens) const override;
}; };
// Transport for raw args (copied from main args, or supplied via /**
// init list for testing) * Wrapper over argc + argv, assumes that the inputs outlive it
*/
class Args { class Args {
friend Detail::TokenStream; friend Detail::TokenStream;
std::string m_exeName; StringRef m_exeName;
std::vector<std::string> m_args; std::vector<StringRef> m_args;
public: public:
Args(int argc, char const* const* argv); Args(int argc, char const* const* argv);
Args(std::initializer_list<std::string> args); // Helper constructor for testing
Args(std::initializer_list<StringRef> args);
std::string const& exeName() const { return m_exeName; } StringRef exeName() const { return m_exeName; }
}; };

View File

@ -25,6 +25,8 @@ namespace Catch {
using size_type = std::size_t; using size_type = std::size_t;
using const_iterator = const char*; using const_iterator = const char*;
static constexpr size_type npos{ static_cast<size_type>( -1 ) };
private: private:
static constexpr char const* const s_empty = ""; static constexpr char const* const s_empty = "";