Do not copy parent build_dependency_files_ in Scope constructors.

SourceFileSet insert operations appear high in performance profiles,
and can be reduced by doing less of them. This is achieved by
ensuring that Scope::build_dependency_files_ only contains the
source files added by a given scope, instead of the union of the
scope itself and of its parents.

A new method Scope::CollectBuildDependencyFiles() is introduced to
compute the final set of source files for users that need it.

Measurements with hyperfine shows that this speeds up `gn gen`
x1.05 with a small Fuchsia build plan (7.2s -> 6,8s). Also
peak RSS goes from 3.78 GiB to 3.58 GiB (200 MiB saved!).

Change-Id: I684b305421ec684e45b02785e70580efa8f1c2ff
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/17880
Reviewed-by: Dirk Pranke <dpranke@google.com>
Commit-Queue: David Turner <digit@google.com>
diff --git a/src/gn/function_toolchain.cc b/src/gn/function_toolchain.cc
index d86e4cc..1945a6a 100644
--- a/src/gn/function_toolchain.cc
+++ b/src/gn/function_toolchain.cc
@@ -210,7 +210,7 @@
   // This object will actually be copied into the one owned by the toolchain
   // manager, but that has to be done in the lock.
   std::unique_ptr<Toolchain> toolchain = std::make_unique<Toolchain>(
-      scope->settings(), label, scope->build_dependency_files());
+      scope->settings(), label, scope->CollectBuildDependencyFiles());
   toolchain->set_defined_from(function);
   toolchain->visibility().SetPublic();
 
diff --git a/src/gn/functions.cc b/src/gn/functions.cc
index adc1ce3..5d958a5 100644
--- a/src/gn/functions.cc
+++ b/src/gn/functions.cc
@@ -369,7 +369,7 @@
 
   // Create the new config.
   std::unique_ptr<Config> config = std::make_unique<Config>(
-      scope->settings(), label, scope->build_dependency_files());
+      scope->settings(), label, scope->CollectBuildDependencyFiles());
   config->set_defined_from(function);
   if (!Visibility::FillItemVisibility(config.get(), scope, err))
     return Value();
@@ -909,7 +909,7 @@
 
   // Create the new pool.
   std::unique_ptr<Pool> pool = std::make_unique<Pool>(
-      scope->settings(), label, scope->build_dependency_files());
+      scope->settings(), label, scope->CollectBuildDependencyFiles());
 
   if (label.name() == "console") {
     const Settings* settings = scope->settings();
diff --git a/src/gn/scope.cc b/src/gn/scope.cc
index 0efe1f9..0e8ec91 100644
--- a/src/gn/scope.cc
+++ b/src/gn/scope.cc
@@ -58,22 +58,21 @@
       mutable_containing_(parent),
       settings_(parent->settings()),
       mode_flags_(0),
-      item_collector_(nullptr),
-      build_dependency_files_(parent->build_dependency_files_) {}
+      item_collector_(nullptr) {}
 
 Scope::Scope(const Scope* parent)
     : const_containing_(parent),
       mutable_containing_(nullptr),
       settings_(parent->settings()),
       mode_flags_(0),
-      item_collector_(nullptr),
-      build_dependency_files_(parent->build_dependency_files_) {}
+      item_collector_(nullptr) {}
 
 Scope::~Scope() = default;
 
 void Scope::DetachFromContaining() {
   const_containing_ = nullptr;
   mutable_containing_ = nullptr;
+  build_dependency_files_ = CollectBuildDependencyFiles();
 }
 
 bool Scope::HasValues(SearchNested search_nested) const {
@@ -209,6 +208,29 @@
   return nullptr;
 }
 
+SourceFileSet Scope::CollectBuildDependencyFiles() const {
+  SourceFileSet result;
+
+  // Compute capacity first.
+  size_t capacity = 0;
+  const Scope* scope = this;
+  do {
+    capacity += scope->build_dependency_files_.size();
+    scope = scope->containing();
+  } while (scope);
+
+  result.reserve(capacity);
+
+  scope = this;
+  do {
+    result.insert(scope->build_dependency_files_.begin(),
+                  scope->build_dependency_files_.end());
+    scope = scope->containing();
+  } while (scope);
+
+  return result;
+}
+
 void Scope::MarkUsed(std::string_view ident) {
   RecordMap::iterator found = values_.find(ident);
   if (found == values_.end()) {
diff --git a/src/gn/scope.h b/src/gn/scope.h
index b852941..c9db294 100644
--- a/src/gn/scope.h
+++ b/src/gn/scope.h
@@ -297,12 +297,12 @@
   // set is constructed conservatively, meaning that every file that can
   // potentially affect this scope is included, but not necessarily every change
   // to these files will affect this scope.
-  const SourceFileSet& build_dependency_files() const {
-    return build_dependency_files_;
-  }
   void AddBuildDependencyFile(const SourceFile& build_dependency_file);
   void AddBuildDependencyFiles(const SourceFileSet& build_dependency_files);
 
+  // Collect all dependency files from this scope (and parent ones).
+  SourceFileSet CollectBuildDependencyFiles() const;
+
   // The item collector is where Items (Targets, Configs, etc.) go that have
   // been defined. If a scope can generate items, this non-owning pointer will
   // point to the storage for such items. The creator of this scope will be
@@ -413,6 +413,8 @@
 
   SourceDir source_dir_;
 
+  // MOTE: build dependency files defined in this scope, not including
+  // parent ones.
   SourceFileSet build_dependency_files_;
 
   Scope(const Scope&) = delete;
diff --git a/src/gn/scope_unittest.cc b/src/gn/scope_unittest.cc
index 9f82685..c16cb3a 100644
--- a/src/gn/scope_unittest.cc
+++ b/src/gn/scope_unittest.cc
@@ -26,9 +26,8 @@
 
 bool ContainsBuildDependencyFile(const Scope* scope,
                                  const SourceFile& source_file) {
-  const auto& build_dependency_files = scope->build_dependency_files();
-  return build_dependency_files.end() !=
-         build_dependency_files.find(source_file);
+  const auto& build_dependency_files = scope->CollectBuildDependencyFiles();
+  return build_dependency_files.find(source_file) != build_dependency_files.end();
 }
 
 }  // namespace
@@ -39,7 +38,7 @@
   setup.scope()->AddBuildDependencyFile(source_file);
 
   Scope new_scope(setup.scope());
-  EXPECT_EQ(1U, new_scope.build_dependency_files().size());
+  EXPECT_EQ(1U, new_scope.CollectBuildDependencyFiles().size());
   EXPECT_TRUE(ContainsBuildDependencyFile(&new_scope, source_file));
 }
 
@@ -225,7 +224,7 @@
     EXPECT_TRUE(from_scope.NonRecursiveMergeTo(&to_scope, options, &assignment,
                                                "error", &err));
     EXPECT_FALSE(err.has_error());
-    EXPECT_EQ(1U, to_scope.build_dependency_files().size());
+    EXPECT_EQ(1U, to_scope.CollectBuildDependencyFiles().size());
     EXPECT_TRUE(ContainsBuildDependencyFile(&to_scope, source_file));
   }
 }
diff --git a/src/gn/setup.cc b/src/gn/setup.cc
index c9e6b8b..c2f0aed 100644
--- a/src/gn/setup.cc
+++ b/src/gn/setup.cc
@@ -626,7 +626,7 @@
   arg_scope.GetCurrentScopeValues(&overrides);
   build_settings_.build_args().AddArgOverrides(overrides);
   build_settings_.build_args().set_build_args_dependency_files(
-      arg_scope.build_dependency_files());
+      arg_scope.CollectBuildDependencyFiles());
   return true;
 }
 
diff --git a/src/gn/target_generator.cc b/src/gn/target_generator.cc
index 4138889..59bcf1a 100644
--- a/src/gn/target_generator.cc
+++ b/src/gn/target_generator.cc
@@ -94,7 +94,7 @@
     g_scheduler->Log("Defining target", label.GetUserVisibleName(true));
 
   std::unique_ptr<Target> target = std::make_unique<Target>(
-      scope->settings(), label, scope->build_dependency_files());
+      scope->settings(), label, scope->CollectBuildDependencyFiles());
   target->set_defined_from(function_call);
 
   // Create and call out to the proper generator.
diff --git a/src/gn/template.cc b/src/gn/template.cc
index 029393c..a01cd8c 100644
--- a/src/gn/template.cc
+++ b/src/gn/template.cc
@@ -76,7 +76,7 @@
   // Propagate build dependency files from invoker scope (template scope already
   // propagated via parent scope).
   template_scope.AddBuildDependencyFiles(
-      invocation_scope->build_dependency_files());
+      invocation_scope->CollectBuildDependencyFiles());
 
   ScopePerFileProvider per_file_provider(&template_scope, true);