Skip to content

Commit 0028139

Browse files
Add a small speed up to PathDecanonicalized
Avoid a mandatory allocation in `PathDecanonicalized` by taking an output `std::string*` to append to. Improve its performance by swapping `strchr` with `std::string::find`, which can take advantage of knowing the length of the string. We can also return as soon as we run out of forwardslashes that need to be turned into backslashes, instead of iterating through all forwardslashes. If backslashes are the common path separator in ninja build files on Windows, it may be worthwhile canonicalizing paths into backslashes-only, which would now allow us to skip the loop entirely in `PathDecanonicalized` in the case of paths with no forwardslashes. Rename this function to `AppendPathDecanonicalized` to reflect its new functionality. Running `manifest_parser_perftest.exe` on Windows gives initial timings of: min 549ms max 613ms avg 573.1ms and timings after this change of: min 534ms max 597ms avg 559.9ms which is a 2-3% improvement.
1 parent cc60300 commit 0028139

File tree

4 files changed

+71
-31
lines changed

4 files changed

+71
-31
lines changed

src/graph.cc

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -484,18 +484,20 @@ string EdgeEnv::LookupVariable(const string& var) {
484484
std::string EdgeEnv::MakePathList(const Node* const* const span,
485485
const size_t size, const char sep) const {
486486
string result;
487+
std::string temp_path;
487488
for (const Node* const* i = span; i != span + size; ++i) {
488489
if (!result.empty())
489490
result.push_back(sep);
490-
const string& path = (*i)->PathDecanonicalized();
491491
if (escape_in_out_ == kShellEscape) {
492+
temp_path.clear();
493+
(*i)->AppendPathDecanonicalized(&temp_path);
492494
#ifdef _WIN32
493-
GetWin32EscapedString(path, &result);
495+
GetWin32EscapedString(temp_path, &result);
494496
#else
495-
GetShellEscapedString(path, &result);
497+
GetShellEscapedString(temp_path, &result);
496498
#endif
497499
} else {
498-
result.append(path);
500+
(*i)->AppendPathDecanonicalized(&result);
499501
}
500502
}
501503
return result;
@@ -580,18 +582,29 @@ bool Edge::maybe_phonycycle_diagnostic() const {
580582
}
581583

582584
// static
583-
string Node::PathDecanonicalized(const string& path, uint64_t slash_bits) {
584-
string result = path;
585+
void Node::AppendPathDecanonicalized(std::string* output, StringPiece path,
586+
uint64_t slash_bits) {
587+
if (path.empty()) {
588+
return;
589+
}
590+
output->append(path.begin(), path.end());
585591
#ifdef _WIN32
586-
uint64_t mask = 1;
587-
for (char* c = &result[0]; (c = strchr(c, '/')) != NULL;) {
588-
if (slash_bits & mask)
592+
char* data = &(*output)[0];
593+
char* end = data + output->size();
594+
char* c = end - path.size();
595+
while (slash_bits != 0) {
596+
c = static_cast<char*>(memchr(c, '/', end - c));
597+
598+
// If `memchr` returns null then there are too many bits in `slash_bits`,
599+
// which happens when we pass `~0` in `build.cc` instead of calculating it.
600+
if (!c)
601+
return;
602+
if (slash_bits & 1)
589603
*c = '\\';
590-
c++;
591-
mask <<= 1;
604+
++c;
605+
slash_bits >>= 1;
592606
}
593607
#endif
594-
return result;
595608
}
596609

597610
void Node::Dump(const char* prefix) const {
@@ -788,22 +801,23 @@ void InputsCollector::VisitNode(const Node* node) {
788801

789802
std::vector<std::string> InputsCollector::GetInputsAsStrings(
790803
bool shell_escape) const {
791-
std::vector<std::string> result;
792-
result.reserve(inputs_.size());
804+
std::vector<std::string> result(inputs_.size());
805+
auto out = result.begin();
793806

807+
std::string temp_path;
794808
for (const Node* input : inputs_) {
795-
std::string unescaped = input->PathDecanonicalized();
796809
if (shell_escape) {
797-
std::string path;
810+
temp_path.clear();
811+
input->AppendPathDecanonicalized(&temp_path);
798812
#ifdef _WIN32
799-
GetWin32EscapedString(unescaped, &path);
813+
GetWin32EscapedString(temp_path, &*out);
800814
#else
801-
GetShellEscapedString(unescaped, &path);
815+
GetShellEscapedString(temp_path, &*out);
802816
#endif
803-
result.push_back(std::move(path));
804817
} else {
805-
result.push_back(std::move(unescaped));
818+
input->AppendPathDecanonicalized(&*out);
806819
}
820+
++out;
807821
}
808822
return result;
809823
}

src/graph.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ struct Node {
8181

8282
const std::string& path() const { return path_; }
8383
/// Get |path()| but use slash_bits to convert back to original slash styles.
84-
std::string PathDecanonicalized() const {
85-
return PathDecanonicalized(path_, slash_bits_);
84+
void AppendPathDecanonicalized(std::string* output) const {
85+
return AppendPathDecanonicalized(output, path_, slash_bits_);
8686
}
87-
static std::string PathDecanonicalized(const std::string& path,
88-
uint64_t slash_bits);
87+
static void AppendPathDecanonicalized(std::string* output, StringPiece path,
88+
uint64_t slash_bits);
8989
uint64_t slash_bits() const { return slash_bits_; }
9090

9191
TimeStamp mtime() const { return mtime_; }
@@ -122,7 +122,7 @@ struct Node {
122122
std::string path_;
123123

124124
/// Set bits starting from lowest for backslashes that were normalized to
125-
/// forward slashes by CanonicalizePath. See |PathDecanonicalized|.
125+
/// forward slashes by CanonicalizePath. See |AppendPathDecanonicalized|.
126126
uint64_t slash_bits_ = 0;
127127

128128
/// Possible values of mtime_:

src/graph_test.cc

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -619,10 +619,35 @@ TEST_F(GraphTest, Decanonicalize) {
619619
EXPECT_EQ(root_nodes[1]->path(), "out/out2/out3/out4");
620620
EXPECT_EQ(root_nodes[2]->path(), "out3");
621621
EXPECT_EQ(root_nodes[3]->path(), "out4/foo");
622-
EXPECT_EQ(root_nodes[0]->PathDecanonicalized(), "out\\out1");
623-
EXPECT_EQ(root_nodes[1]->PathDecanonicalized(), "out\\out2/out3\\out4");
624-
EXPECT_EQ(root_nodes[2]->PathDecanonicalized(), "out3");
625-
EXPECT_EQ(root_nodes[3]->PathDecanonicalized(), "out4\\foo");
622+
623+
std::string decanon;
624+
root_nodes[0]->AppendPathDecanonicalized(&decanon);
625+
EXPECT_EQ(decanon, "out\\out1");
626+
627+
// Check we are indeed appending to the end
628+
decanon = "text at the start:";
629+
root_nodes[0]->AppendPathDecanonicalized(&decanon);
630+
EXPECT_EQ(decanon, "text at the start:out\\out1");
631+
632+
decanon.clear();
633+
root_nodes[1]->AppendPathDecanonicalized(&decanon);
634+
EXPECT_EQ(decanon, "out\\out2/out3\\out4");
635+
636+
decanon.clear();
637+
root_nodes[2]->AppendPathDecanonicalized(&decanon);
638+
EXPECT_EQ(decanon, "out3");
639+
640+
decanon.clear();
641+
root_nodes[3]->AppendPathDecanonicalized(&decanon);
642+
EXPECT_EQ(decanon, "out4\\foo");
643+
644+
// In build.cc there is one place we pass `~0` as the slash_bits
645+
// because we assume the output is only backslashes and the existing
646+
// code doesn't bother to calculate it. Ensure this doesn't cause
647+
// a problem by indicating there are more backslashes then there are.
648+
decanon = "/path/=";
649+
Node::AppendPathDecanonicalized(&decanon, "a/b/c", ~0);
650+
EXPECT_EQ(decanon, "/path/=a\\b\\c");
626651
}
627652
#endif
628653

src/ninja.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -351,8 +351,9 @@ Node* NinjaMain::CollectTarget(const char* cpath, string* err) {
351351
}
352352
return node;
353353
} else {
354-
*err =
355-
"unknown target '" + Node::PathDecanonicalized(path, slash_bits) + "'";
354+
*err = "unknown target '";
355+
Node::AppendPathDecanonicalized(err, path, slash_bits);
356+
*err += "'";
356357
if (path == "clean") {
357358
*err += ", did you mean 'ninja -t clean'?";
358359
} else if (path == "help") {

0 commit comments

Comments
 (0)