Skip to content

Commit 39ce344

Browse files
Avoid string copies on lookup methods
Create a transparent comparator `StringPieceLess` that can allow us to lookup up values in a `std::map<std::string, V>` using a `StringPiece` without needing to create a temporary `std::string`. Similarly, there are other methods on classes in `graph.h` that take a `std::string` that can work just fine with a `StringPIece` and avoid more temporaries.
1 parent 931d990 commit 39ce344

File tree

8 files changed

+80
-40
lines changed

8 files changed

+80
-40
lines changed

src/eval_env.cc

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,32 @@
1818

1919
using namespace std;
2020

21-
string BindingEnv::LookupVariable(const string& var) {
22-
map<string, string>::iterator i = bindings_.find(var);
21+
std::string BindingEnv::LookupVariable(StringPiece var) {
22+
const auto i = bindings_.find(var);
2323
if (i != bindings_.end())
2424
return i->second;
2525
if (parent_)
2626
return parent_->LookupVariable(var);
2727
return "";
2828
}
2929

30-
void BindingEnv::AddBinding(const string& key, const string& val) {
31-
bindings_[key] = val;
30+
void BindingEnv::AddBinding(const std::string& key, StringPiece val) {
31+
bindings_[key].assign(val.begin(), val.end());
3232
}
3333

3434
void BindingEnv::AddRule(std::unique_ptr<const Rule> rule) {
3535
assert(LookupRuleCurrentScope(rule->name()) == NULL);
3636
rules_[rule->name()] = std::move(rule);
3737
}
3838

39-
const Rule* BindingEnv::LookupRuleCurrentScope(const string& rule_name) {
39+
const Rule* BindingEnv::LookupRuleCurrentScope(StringPiece rule_name) {
4040
auto i = rules_.find(rule_name);
4141
if (i == rules_.end())
4242
return NULL;
4343
return i->second.get();
4444
}
4545

46-
const Rule* BindingEnv::LookupRule(const string& rule_name) {
46+
const Rule* BindingEnv::LookupRule(StringPiece rule_name) {
4747
auto i = rules_.find(rule_name);
4848
if (i != rules_.end())
4949
return i->second.get();
@@ -56,7 +56,7 @@ void Rule::AddBinding(const string& key, const EvalString& val) {
5656
bindings_[key] = val;
5757
}
5858

59-
const EvalString* Rule::GetBinding(const string& key) const {
59+
const EvalString* Rule::GetBinding(StringPiece key) const {
6060
Bindings::const_iterator i = bindings_.find(key);
6161
if (i == bindings_.end())
6262
return NULL;
@@ -74,7 +74,7 @@ bool Rule::IsPhony() const {
7474
}
7575

7676
// static
77-
bool Rule::IsReservedBinding(const string& var) {
77+
bool Rule::IsReservedBinding(StringPiece var) {
7878
return var == "command" ||
7979
var == "depfile" ||
8080
var == "dyndep" ||
@@ -88,14 +88,14 @@ bool Rule::IsReservedBinding(const string& var) {
8888
var == "msvc_deps_prefix";
8989
}
9090

91-
const map<string, std::unique_ptr<const Rule>>& BindingEnv::GetRules() const {
91+
const map<string, std::unique_ptr<const Rule>, StringPieceLess>& BindingEnv::GetRules() const {
9292
return rules_;
9393
}
9494

95-
string BindingEnv::LookupWithFallback(const string& var,
96-
const EvalString* eval,
97-
Env* env) {
98-
map<string, string>::iterator i = bindings_.find(var);
95+
std::string BindingEnv::LookupWithFallback(StringPiece var,
96+
const EvalString* eval,
97+
Env* env) {
98+
const auto i = bindings_.find(var);
9999
if (i != bindings_.end())
100100
return i->second;
101101

src/eval_env.h

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@
2121
#include <vector>
2222

2323
#include "string_piece.h"
24+
#include "string_piece_util.h"
2425

2526
struct Rule;
2627

2728
/// An interface for a scope for variable (e.g. "$foo") lookups.
2829
struct Env {
2930
virtual ~Env() {}
30-
virtual std::string LookupVariable(const std::string& var) = 0;
31+
virtual std::string LookupVariable(StringPiece var) = 0;
3132
};
3233

3334
/// A tokenized string that contains variable references.
@@ -74,16 +75,16 @@ struct Rule {
7475

7576
void AddBinding(const std::string& key, const EvalString& val);
7677

77-
static bool IsReservedBinding(const std::string& var);
78+
static bool IsReservedBinding(StringPiece var);
7879

79-
const EvalString* GetBinding(const std::string& key) const;
80+
const EvalString* GetBinding(StringPiece key) const;
8081

8182
private:
8283
// Allow the parsers to reach into this object and fill out its fields.
8384
friend struct ManifestParser;
8485

8586
std::string name_;
86-
typedef std::map<std::string, EvalString> Bindings;
87+
typedef std::map<std::string, EvalString, StringPieceLess> Bindings;
8788
Bindings bindings_;
8889
bool phony_ = false;
8990
};
@@ -95,26 +96,27 @@ struct BindingEnv : public Env {
9596
explicit BindingEnv(BindingEnv* parent) : parent_(parent) {}
9697

9798
virtual ~BindingEnv() {}
98-
virtual std::string LookupVariable(const std::string& var);
99+
virtual std::string LookupVariable(StringPiece var);
99100

100101
void AddRule(std::unique_ptr<const Rule> rule);
101-
const Rule* LookupRule(const std::string& rule_name);
102-
const Rule* LookupRuleCurrentScope(const std::string& rule_name);
103-
const std::map<std::string, std::unique_ptr<const Rule>>& GetRules() const;
102+
const Rule* LookupRule(StringPiece rule_name);
103+
const Rule* LookupRuleCurrentScope(StringPiece rule_name);
104+
const std::map<std::string, std::unique_ptr<const Rule>, StringPieceLess>&
105+
GetRules() const;
104106

105-
void AddBinding(const std::string& key, const std::string& val);
107+
void AddBinding(const std::string& key, StringPiece val);
106108

107109
/// This is tricky. Edges want lookup scope to go in this order:
108110
/// 1) value set on edge itself (edge_->env_)
109111
/// 2) value set on rule, with expansion in the edge's scope
110112
/// 3) value set on enclosing scope of edge (edge_->env_->parent_)
111113
/// This function takes as parameters the necessary info to do (2).
112-
std::string LookupWithFallback(const std::string& var, const EvalString* eval,
114+
std::string LookupWithFallback(StringPiece var, const EvalString* eval,
113115
Env* env);
114116

115117
private:
116-
std::map<std::string, std::string> bindings_;
117-
std::map<std::string, std::unique_ptr<const Rule>> rules_;
118+
std::map<std::string, std::string, StringPieceLess> bindings_;
119+
std::map<std::string, std::unique_ptr<const Rule>, StringPieceLess> rules_;
118120
BindingEnv* parent_;
119121
};
120122

src/graph.cc

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ struct EdgeEnv : public Env {
393393

394394
EdgeEnv(const Edge* const edge, const EscapeKind escape)
395395
: edge_(edge), escape_in_out_(escape), recursive_(false) {}
396-
virtual string LookupVariable(const string& var);
396+
virtual std::string LookupVariable(StringPiece var);
397397

398398
/// Given a span of Nodes, construct a list of paths suitable for a command
399399
/// line.
@@ -406,7 +406,7 @@ struct EdgeEnv : public Env {
406406
bool recursive_;
407407
};
408408

409-
string EdgeEnv::LookupVariable(const string& var) {
409+
std::string EdgeEnv::LookupVariable(StringPiece var) {
410410
if (var == "in" || var == "in_newline") {
411411
int explicit_deps_count =
412412
static_cast<int>(edge_->inputs_.size() - edge_->implicit_deps_ -
@@ -453,12 +453,16 @@ string EdgeEnv::LookupVariable(const string& var) {
453453
// at all.
454454
//
455455
if (recursive_) {
456-
auto it = std::find(lookups_.begin(), lookups_.end(), var);
456+
auto it = std::find_if(lookups_.begin(),
457+
lookups_.end(),
458+
[var](const std::string& lookup){
459+
return var == lookup;
460+
});
457461
if (it != lookups_.end()) {
458462
std::string cycle;
459463
for (; it != lookups_.end(); ++it)
460464
cycle.append(*it + " -> ");
461-
cycle.append(var);
465+
cycle.append(var.str_, var.len_);
462466
Fatal(("cycle in rule variables: " + cycle).c_str());
463467
}
464468
}
@@ -467,7 +471,7 @@ string EdgeEnv::LookupVariable(const string& var) {
467471
const EvalString* eval = edge_->rule_->GetBinding(var);
468472
bool record_varname = recursive_ && eval;
469473
if (record_varname)
470-
lookups_.push_back(var);
474+
lookups_.push_back(var.AsString());
471475

472476
// In practice, variables defined on rules never use another rule variable.
473477
// For performance, only start checking for cycles after the first lookup.
@@ -508,12 +512,12 @@ std::string Edge::EvaluateCommand(const bool incl_rsp_file) const {
508512
return command;
509513
}
510514

511-
std::string Edge::GetBinding(const std::string& key) const {
515+
std::string Edge::GetBinding(StringPiece key) const {
512516
EdgeEnv env(this, EdgeEnv::kShellEscape);
513517
return env.LookupVariable(key);
514518
}
515519

516-
bool Edge::GetBindingBool(const string& key) const {
520+
bool Edge::GetBindingBool(StringPiece key) const {
517521
return !GetBinding(key).empty();
518522
}
519523

src/graph.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,8 @@ struct Edge {
190190
std::string EvaluateCommand(bool incl_rsp_file = false) const;
191191

192192
/// Returns the shell-escaped value of |key|.
193-
std::string GetBinding(const std::string& key) const;
194-
bool GetBindingBool(const std::string& key) const;
193+
std::string GetBinding(StringPiece key) const;
194+
bool GetBindingBool(StringPiece key) const;
195195

196196
/// Like GetBinding("depfile"), but without shell escaping.
197197
std::string GetUnescapedDepfile() const;

src/ninja.cc

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -685,12 +685,10 @@ int NinjaMain::ToolRules(const Options* options, int argc, char* argv[]) {
685685

686686
// Print rules
687687

688-
typedef map<string, std::unique_ptr<const Rule>> Rules;
689-
const Rules& rules = state_.bindings_.GetRules();
690-
for (Rules::const_iterator i = rules.begin(); i != rules.end(); ++i) {
691-
printf("%s", i->first.c_str());
688+
for (const auto& r : state_.bindings_.GetRules()) {
689+
printf("%s", r.first.c_str());
692690
if (print_description) {
693-
const Rule* rule = i->second.get();
691+
const Rule* rule = r.second.get();
694692
const EvalString* description = rule->GetBinding("description");
695693
if (description != NULL) {
696694
printf(": %s", description->Unparse().c_str());

src/string_piece_util.cc

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414

1515
#include "string_piece_util.h"
1616

17-
#include <algorithm>
17+
#include <cstring>
1818
#include <string>
1919
#include <vector>
20+
2021
using namespace std;
2122

2223
vector<StringPiece> SplitStringPiece(StringPiece input, char sep) {
@@ -76,3 +77,15 @@ bool EqualsCaseInsensitiveASCII(StringPiece a, StringPiece b) {
7677

7778
return true;
7879
}
80+
81+
bool StringPieceLess::operator()(StringPiece lhs,
82+
StringPiece rhs) const {
83+
84+
const std::size_t min_length = std::min(lhs.size(), rhs.size());
85+
const int cmp = std::memcmp(lhs.str_, rhs.str_, min_length);
86+
if (cmp == 0) {
87+
return lhs.size() < rhs.size();
88+
} else {
89+
return cmp < 0;
90+
}
91+
}

src/string_piece_util.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifndef NINJA_STRINGPIECE_UTIL_H_
1616
#define NINJA_STRINGPIECE_UTIL_H_
1717

18+
#include <algorithm>
1819
#include <string>
1920
#include <vector>
2021

@@ -30,4 +31,13 @@ inline char ToLowerASCII(char c) {
3031

3132
bool EqualsCaseInsensitiveASCII(StringPiece a, StringPiece b);
3233

34+
/// A comparator for StringPiece that can be used in
35+
/// `std::map<std::string, V, StringPieceLess>` that will allow using a
36+
/// StringPiece as a key.
37+
struct StringPieceLess {
38+
using is_transparent = void;
39+
40+
bool operator()(StringPiece lhs, StringPiece rhs) const;
41+
};
42+
3343
#endif // NINJA_STRINGPIECE_UTIL_H_

src/string_piece_util_test.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,16 @@ TEST(StringPieceUtilTest, EqualsCaseInsensitiveASCII) {
129129
EXPECT_FALSE(EqualsCaseInsensitiveASCII("/", "\\"));
130130
EXPECT_FALSE(EqualsCaseInsensitiveASCII("1", "10"));
131131
}
132+
133+
134+
TEST(StringPieceUtilTest, StringPieceLess) {
135+
const StringPiece strings[] = { "", "A", "AbC", "a", "abc", "c", "ca" };
136+
const StringPieceLess less;
137+
138+
for (int l = 0; l < std::size(strings); ++l) {
139+
for (int r = 0; r < std::size(strings); ++r) {
140+
EXPECT_EQ(less(strings[l], strings[r]), l < r)
141+
<< strings[l].AsString() << " < " << strings[r].AsString();
142+
}
143+
}
144+
}

0 commit comments

Comments
 (0)