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;
}
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 Catch {
@ -52,23 +65,23 @@ namespace Catch {
}
if ( it != itEnd ) {
auto const& next = *it;
StringRef next = *it;
if ( isOptPrefix( next[0] ) ) {
auto delimiterPos = next.find_first_of( " :=" );
if ( delimiterPos != std::string::npos ) {
auto delimiterPos = find_first_separator(next);
if ( delimiterPos != StringRef::npos ) {
m_tokenBuffer.push_back(
{ TokenType::Option,
next.substr( 0, delimiterPos ) } );
m_tokenBuffer.push_back(
{ TokenType::Argument,
next.substr( delimiterPos + 1 ) } );
next.substr( delimiterPos + 1, next.size() ) } );
} else {
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 ) {
opt[1] = next[i];
m_tokenBuffer.push_back(
{ TokenType::Option, opt } );
{ TokenType::Option,
next.substr( i, 1 ) } );
}
} else {
m_tokenBuffer.push_back(
@ -128,7 +141,7 @@ namespace Catch {
size_t ParserBase::cardinality() const { return 1; }
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,
@ -166,7 +179,7 @@ namespace Catch {
auto valueRef =
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)
return Detail::InternalParseResult(result);
else
@ -192,7 +205,7 @@ namespace Catch {
return { oss.str(), m_description };
}
bool Opt::isMatch(std::string const& optToken) const {
bool Opt::isMatch(StringRef optToken) const {
auto normalisedToken = normaliseOpt(optToken);
for (auto const& name : m_optNames) {
if (normaliseOpt(name) == normalisedToken)
@ -237,7 +250,7 @@ namespace Catch {
return Detail::InternalParseResult::runtimeError(
"Expected argument following " +
token.token);
const auto result = valueRef->setValue(argToken.token);
const auto result = valueRef->setValue(static_cast<std::string>(argToken.token));
if (!result)
return Detail::InternalParseResult(result);
if (result.value() ==
@ -433,7 +446,7 @@ namespace Catch {
Args::Args(int argc, char const* const* argv) :
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_args(args.begin() + 1, args.end()) {}

View File

@ -102,17 +102,16 @@ namespace Catch {
enum class TokenType { Option, Argument };
struct Token {
TokenType type;
std::string token;
StringRef token;
};
// Abstracts iterators into args as a stream of tokens, with option
// arguments uniformly handled
class TokenStream {
using Iterator = std::vector<std::string>::const_iterator;
using Iterator = std::vector<StringRef>::const_iterator;
Iterator it;
Iterator itEnd;
std::vector<Token> m_tokenBuffer;
void loadBuffer();
public:
@ -582,7 +581,7 @@ namespace Catch {
Detail::HelpColumns getHelpColumns() const;
bool isMatch(std::string const& optToken) const;
bool isMatch(StringRef optToken) const;
using ParserBase::parse;
@ -676,18 +675,20 @@ namespace Catch {
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 {
friend Detail::TokenStream;
std::string m_exeName;
std::vector<std::string> m_args;
StringRef m_exeName;
std::vector<StringRef> m_args;
public:
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 const_iterator = const char*;
static constexpr size_type npos{ static_cast<size_type>( -1 ) };
private:
static constexpr char const* const s_empty = "";