Skip to content

Commit

Permalink
Apply a short-vector optimization to EvalString.
Browse files Browse the repository at this point in the history
This very often holds only a single RAW token, so we do not
need to allocate elements on an std::vector for it in the
common case.

For a no-op build of Chromium (Linux, Zen 2),
this reduces time spent from 5.48 to 5.14 seconds.

Note that this opens up for a potential optimization where
EvalString::Evaluate() could just return a StringPiece, without
making a std::string out of it (which requires allocation; this is
about 5% of remaining runtime). However, this would also require
that CanonicalizePath() somehow learned to work with StringPiece
(presumably allocating a new StringPiece if and only if changes
were needed).
  • Loading branch information
Steinar H. Gunderson committed Nov 4, 2024
1 parent 5d24d12 commit 8c986bd
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 15 deletions.
60 changes: 47 additions & 13 deletions src/eval_env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ string BindingEnv::LookupWithFallback(const string& var,
}

string EvalString::Evaluate(Env* env) const {
if (parsed_.empty()) {
return single_token_.AsString();
}

string result;
for (TokenList::const_iterator i = parsed_.begin(); i != parsed_.end(); ++i) {
if (i->second == RAW)
Expand All @@ -112,34 +116,64 @@ string EvalString::Evaluate(Env* env) const {
}

void EvalString::AddText(StringPiece text) {
parsed_.push_back(std::make_pair(text, RAW));
if (parsed_.empty()) {
if (!single_token_.empty()) {
// Going from one to two tokens, so we can no longer apply
// our single_token_ optimization and need to push everything
// onto the vector.
parsed_.push_back(std::make_pair(single_token_, RAW));
} else {
// This is the first (nonempty) token, so we don't need to
// allocate anything on the vector (yet).
single_token_ = text;
return;
}
}
parsed_.push_back(make_pair(text, RAW));
}

void EvalString::AddSpecial(StringPiece text) {
if (parsed_.empty() && !single_token_.empty()) {
// Going from one to two tokens, so we can no longer apply
// our single_token_ optimization and need to push everything
// onto the vector.
parsed_.push_back(std::make_pair(single_token_, RAW));
}
parsed_.push_back(std::make_pair(text, SPECIAL));
}

string EvalString::Serialize() const {
string result;
for (const auto& pair : parsed_) {
if (parsed_.empty() && !single_token_.empty()) {
result.append("[");
if (pair.second == SPECIAL)
result.append("$");
result.append(pair.first.begin(), pair.first.end());
result.append(single_token_.begin(), single_token_.end());
result.append("]");
} else {
for (const auto& pair : parsed_) {
result.append("[");
if (pair.second == SPECIAL)
result.append("$");
result.append(pair.first.begin(), pair.first.end());
result.append("]");
}
}
return result;
}

string EvalString::Unparse() const {
string result;
for (TokenList::const_iterator i = parsed_.begin();
i != parsed_.end(); ++i) {
bool special = (i->second == SPECIAL);
if (special)
result.append("${");
result.append(i->first.begin(), i->first.end());
if (special)
result.append("}");
if (parsed_.empty() && !single_token_.empty()) {
result.append(single_token_.begin(), single_token_.end());
} else {
for (TokenList::const_iterator i = parsed_.begin();
i != parsed_.end(); ++i) {
bool special = (i->second == SPECIAL);
if (special)
result.append("${");
result.append(i->first.begin(), i->first.end());
if (special)
result.append("}");
}
}
return result;
}
10 changes: 8 additions & 2 deletions src/eval_env.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ struct EvalString {
/// @return The string with variables not expanded.
std::string Unparse() const;

void Clear() { parsed_.clear(); }
bool empty() const { return parsed_.empty(); }
void Clear() { parsed_.clear(); single_token_ = StringPiece(); }
bool empty() const { return parsed_.empty() && single_token_.empty(); }

void AddText(StringPiece text);
void AddSpecial(StringPiece text);
Expand All @@ -53,6 +53,12 @@ struct EvalString {
enum TokenType { RAW, SPECIAL };
typedef std::vector<std::pair<StringPiece, TokenType> > TokenList;
TokenList parsed_;

// If we hold only a single RAW token, then we keep it here instead of
// pushing it on TokenList. This saves a bunch of allocations for
// what is a common case. If parsed_ is nonempty, then this value
// must be ignored.
StringPiece single_token_;
};

/// An invocable build command and associated metadata (description, etc.).
Expand Down
4 changes: 4 additions & 0 deletions src/string_piece.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ struct StringPiece {
return len_;
}

size_t empty() const {
return len_ == 0;
}

const char* str_;
size_t len_;
};
Expand Down

0 comments on commit 8c986bd

Please sign in to comment.