Parallelize per-target JSON rendering in --ide=json GenerateJSON() built each target's description and serialized it to JSON on the main thread, one target at a time. The desc building and JSON serialization are independent per target, so render them on a WorkerPool into a pre-sized, order-preserving vector; only appending to the shared output buffer and collecting toolchains stays serial. The shared ResolvedTargetData is safe for concurrent access -- the parallel Ninja target writers already share a single instance the same way (see TargetWriteInfo in command_gen.cc). Verified race-free with a ThreadSanitizer build over a full Chromium out/debug tree (0 reports). For that tree (31145 targets) the JSON phase drops from ~2.1s to ~0.4s (5.2x), bringing total "gn gen --ide=json" to ~2.1s vs ~1.7s for plain "gn gen". The generated project.json is byte-identical before and after. Change-Id: I04de9bec3965d5636a431e50d7ddeae2e9cea1c3 Reviewed-on: https://gn-review.googlesource.com/c/gn/+/23461 Commit-Queue: Philipp Wollermann <philwo@google.com> Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/json_project_writer.cc b/src/gn/json_project_writer.cc index 498c0b2..79df92e 100644 --- a/src/gn/json_project_writer.cc +++ b/src/gn/json_project_writer.cc
@@ -23,6 +23,7 @@ #include "gn/scheduler.h" #include "gn/settings.h" #include "gn/string_output_buffer.h" +#include "util/worker_pool.h" // Structure of JSON output file // { @@ -388,26 +389,44 @@ 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 is safe for concurrent access (the parallel Ninja + // writers share a single instance the same way), so the per-target + // descriptions below are rendered on a worker pool. ResolvedTargetData resolved; + + // Render each target's JSON dictionary in parallel into a pre-sized slot, + // preserving the sorted order. Only the rendering (desc building + JSON + // serialization) is parallelized; appending to the shared output buffer and + // collecting toolchains happens serially afterwards. + std::vector<std::string> rendered(sorted_targets.size()); + { + WorkerPool pool; + for (size_t i = 0; i < sorted_targets.size(); i++) { + pool.PostTask([&sorted_targets, &resolved, &rendered, i]() { + const Target* target = sorted_targets[i]; + 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, &resolved); + base::DictionaryValue* outputs_value = nullptr; + if (outputs->GetDictionary("source_outputs", &outputs_value) && + !outputs_value->empty()) { + description->MergeDictionary(outputs.get()); + } + + base::JSONWriter::WriteWithOptions( + *description.get(), base::JSONWriter::OPTIONS_PRETTY_PRINT, + &rendered[i]); + }); + } + } // pool destructor drains the queue and joins all workers. + json_writer.BeginDict("targets"); { - for (const auto* target : sorted_targets) { - 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, &resolved); - base::DictionaryValue* outputs_value = nullptr; - if (outputs->GetDictionary("source_outputs", &outputs_value) && - !outputs_value->empty()) { - description->MergeDictionary(outputs.get()); - } - - std::string json_dict; - base::JSONWriter::WriteWithOptions(*description.get(), - base::JSONWriter::OPTIONS_PRETTY_PRINT, - &json_dict); - json_writer.AddJSONDict(target_labels[target], json_dict); + for (size_t i = 0; i < sorted_targets.size(); i++) { + const Target* target = sorted_targets[i]; + json_writer.AddJSONDict(target_labels[target], rendered[i]); toolchains[target->toolchain()->label()] = target->toolchain(); } }