Share ResolvedTargetData across targets in --ide=json GenerateJSON() builds a description for every resolved target, and TargetDescBuilder::BuildDescription() constructed a fresh ResolvedTargetData for each one to compute inherited libs/frameworks. ResolvedTargetData is a memoizing cache whose lib/framework computation recurses over the transitive linked-deps subgraph; recreating it per target threw away all memoization and made the whole pass quadratic. Add an optional ResolvedTargetData* parameter to DescBuilder::DescriptionForTarget() and have json_project_writer pass a single shared instance for all targets. Other callers (command_desc) keep the previous per-call behavior via the nullptr default. For a Chromium out/debug tree (31145 targets), "gn gen --ide=json" drops from ~62.5s to ~2.1s for the JSON phase (total gen 64s -> 3.8s). The generated 507 MB project.json is byte-identical before and after. Change-Id: I5480c5145b292221032b5a447f3eeaede6f1c54e Reviewed-on: https://gn-review.googlesource.com/c/gn/+/23460 Reviewed-by: Takuto Ikuta <tikuta@google.com> Commit-Queue: Philipp Wollermann <philwo@google.com>
diff --git a/src/gn/desc_builder.cc b/src/gn/desc_builder.cc index 143726d..8dce294 100644 --- a/src/gn/desc_builder.cc +++ b/src/gn/desc_builder.cc
@@ -319,8 +319,11 @@ const std::set<std::string>& what, bool all, bool tree, - bool blame) - : BaseDescBuilder(what, all, tree, blame), target_(target) {} + bool blame, + ResolvedTargetData* resolved) + : BaseDescBuilder(what, all, tree, blame), + target_(target), + resolved_(resolved) {} std::unique_ptr<base::DictionaryValue> BuildDescription() { auto res = std::make_unique<base::DictionaryValue>(); @@ -592,7 +595,11 @@ // currently implement a blame feature for this since the bottom-up // inheritance makes this difficult. - ResolvedTargetData resolved; + // Use the caller-provided cache if any, otherwise a local one. Sharing a + // single instance across many targets avoids recomputing the transitive + // dependency walk for every target. + ResolvedTargetData local_resolved; + ResolvedTargetData& resolved = resolved_ ? *resolved_ : local_resolved; // Libs can be part of any target and get recursively pushed up the chain, // so display them regardless of target type. @@ -946,6 +953,8 @@ } const Target* target_; + // Optional shared cache for inherited lib/framework info; may be null. + ResolvedTargetData* resolved_ = nullptr; }; } // namespace @@ -955,11 +964,12 @@ const std::string& what, bool all, bool tree, - bool blame) { + bool blame, + ResolvedTargetData* resolved) { std::set<std::string> w; if (!what.empty()) w.insert(what); - TargetDescBuilder b(target, w, all, tree, blame); + TargetDescBuilder b(target, w, all, tree, blame, resolved); return b.BuildDescription(); }
diff --git a/src/gn/desc_builder.h b/src/gn/desc_builder.h index e0e95a3..57c7f88 100644 --- a/src/gn/desc_builder.h +++ b/src/gn/desc_builder.h
@@ -8,15 +8,23 @@ #include "base/values.h" #include "gn/target.h" +class ResolvedTargetData; + class DescBuilder { public: - // Creates Dictionary representation for given target + // Creates Dictionary representation for given target. + // + // If |resolved| is non-null it is used to compute (and memoize) inherited + // lib/framework information. Callers that describe many targets in a row + // should pass a single shared instance to avoid recomputing the transitive + // dependency walk for every target (which is quadratic otherwise). static std::unique_ptr<base::DictionaryValue> DescriptionForTarget( const Target* target, const std::string& what, bool all, bool tree, - bool blame); + bool blame, + ResolvedTargetData* resolved = nullptr); // Creates Dictionary representation for given config static std::unique_ptr<base::DictionaryValue> DescriptionForConfig(
diff --git a/src/gn/json_project_writer.cc b/src/gn/json_project_writer.cc index 9eb6e77..498c0b2 100644 --- a/src/gn/json_project_writer.cc +++ b/src/gn/json_project_writer.cc
@@ -19,6 +19,7 @@ #include "gn/desc_builder.h" #include "gn/filesystem_utils.h" #include "gn/invoke_python.h" +#include "gn/resolved_target_data.h" #include "gn/scheduler.h" #include "gn/settings.h" #include "gn/string_output_buffer.h" @@ -385,14 +386,17 @@ json_writer.EndDict(); // build_settings std::map<Label, const Toolchain*> toolchains; + // Shared across all targets so that inherited lib/framework information is + // memoized once rather than recomputed per target (avoids quadratic blowup). + ResolvedTargetData resolved; json_writer.BeginDict("targets"); { for (const auto* target : sorted_targets) { - auto description = - DescBuilder::DescriptionForTarget(target, "", false, false, false); + auto description = DescBuilder::DescriptionForTarget( + target, "", false, false, false, &resolved); // Outputs need to be asked for separately. - auto outputs = DescBuilder::DescriptionForTarget(target, "source_outputs", - false, false, false); + auto outputs = DescBuilder::DescriptionForTarget( + target, "source_outputs", false, false, false, &resolved); base::DictionaryValue* outputs_value = nullptr; if (outputs->GetDictionary("source_outputs", &outputs_value) && !outputs_value->empty()) {