@@ -533,6 +533,154 @@ static void bench_incremental(const std::vector<std::string>& headers,
533533 fs::remove (f);
534534}
535535
536+ // ---------------------------------------------------------------------------
537+ // AST load latency: compile a source file using monolithic vs chained PCH
538+ // ---------------------------------------------------------------------------
539+
540+ // / Compile a source file with a given PCH and measure the time.
541+ static double compile_with_pch (const std::string& source_text,
542+ const std::string& source_file,
543+ const std::string& pch_path,
544+ std::uint32_t preamble_bound,
545+ const std::string& resource_dir) {
546+ CompilationParams cp;
547+ cp.kind = CompilationKind::Content;
548+ cp.directory = " /tmp" ;
549+
550+ std::vector<std::string> arg_storage =
551+ {" clang++" , " -std=c++20" , " -resource-dir" , resource_dir, " -fsyntax-only" , source_file};
552+ std::vector<const char *> args;
553+ for (auto & s: arg_storage)
554+ args.push_back (s.c_str ());
555+ cp.arguments = args;
556+
557+ cp.add_remapped_file (source_file, source_text);
558+ cp.pch = {pch_path, preamble_bound};
559+
560+ auto start = Clock::now ();
561+ auto unit = compile (cp);
562+ bool ok = unit.completed ();
563+ unit = CompilationUnit (nullptr );
564+ auto end = Clock::now ();
565+
566+ if (!ok)
567+ return -1.0 ;
568+ return std::chrono::duration<double , std::milli>(end - start).count ();
569+ }
570+
571+ static void bench_ast_load (const std::vector<std::string>& headers,
572+ std::size_t count,
573+ int runs,
574+ const std::string& resource_dir) {
575+ std::println (" \n === AST LOAD LATENCY ({} headers, {} runs) ===" , count, runs);
576+
577+ // Source file that uses symbols from multiple headers.
578+ std::string source_text = make_preamble (headers, count) + R"cpp(
579+ int main() {
580+ std::vector<std::string> v = {"hello", "world"};
581+ std::map<int, std::string> m;
582+ auto opt = std::make_optional(42);
583+ return 0;
584+ }
585+ )cpp" ;
586+ std::string source_file = " /tmp/ast-load-test.cpp" ;
587+ auto preamble_bound = static_cast <std::uint32_t >(make_preamble (headers, count).size ());
588+
589+ // --- Build monolithic PCH ---
590+ std::string mono_preamble = make_preamble (headers, count);
591+ std::string mono_hdr = temp_path (" ast-mono" , " h" );
592+ std::string mono_pch = temp_path (" ast-mono" , " pch" );
593+ auto mono_r = build_one_pch (mono_preamble, mono_hdr, mono_pch, resource_dir);
594+ if (!mono_r.success ) {
595+ std::println (" Monolithic PCH build failed" );
596+ fs::remove (mono_hdr);
597+ return ;
598+ }
599+
600+ // --- Build chained PCH ---
601+ std::string prev_pch;
602+ std::vector<std::string> chain_temps;
603+ bool chain_ok = true ;
604+ for (std::size_t i = 0 ; i < count && i < headers.size (); ++i) {
605+ std::string text = " #include <" + headers[i] + " >\n " ;
606+ std::string fp = temp_path (" ast-chain" , " h" );
607+ std::string pp = temp_path (" ast-chain" , " pch" );
608+ chain_temps.push_back (fp);
609+ chain_temps.push_back (pp);
610+
611+ auto r = build_one_pch (text, fp, pp, resource_dir, prev_pch, 0 );
612+ if (!r.success ) {
613+ std::println (" Chain build failed at link {} (<{}>): {}" , i + 1 , headers[i], r.error );
614+ chain_ok = false ;
615+ break ;
616+ }
617+ prev_pch = pp;
618+ }
619+
620+ if (!chain_ok) {
621+ fs::remove (mono_hdr);
622+ fs::remove (mono_pch);
623+ for (auto & f: chain_temps)
624+ fs::remove (f);
625+ return ;
626+ }
627+
628+ std::string chain_pch = prev_pch;
629+
630+ // --- Benchmark: compile source with monolithic PCH ---
631+ std::vector<double > mono_times, chain_times;
632+
633+ for (int r = 0 ; r < runs; ++r) {
634+ double t =
635+ compile_with_pch (source_text, source_file, mono_pch, preamble_bound, resource_dir);
636+ if (t >= 0 )
637+ mono_times.push_back (t);
638+ }
639+
640+ // --- Benchmark: compile source with chained PCH ---
641+ for (int r = 0 ; r < runs; ++r) {
642+ // For chained PCH, bound=0 since the PCH doesn't cover the source file's bytes.
643+ // But we need the source to NOT re-parse the preamble includes.
644+ // With chained PCH loaded via ImplicitPCHInclude and bound=0,
645+ // clang will parse the source from the beginning but the includes
646+ // will hit the PCH's already-parsed headers.
647+ double t =
648+ compile_with_pch (source_text, source_file, chain_pch, preamble_bound, resource_dir);
649+ if (t >= 0 )
650+ chain_times.push_back (t);
651+ }
652+
653+ // --- Report ---
654+ if (!mono_times.empty ()) {
655+ std::sort (mono_times.begin (), mono_times.end ());
656+ double med = mono_times[mono_times.size () / 2 ];
657+ std::println (" Monolithic PCH → compile: median {:.1f}ms (min {:.1f}, max {:.1f})" ,
658+ med,
659+ mono_times.front (),
660+ mono_times.back ());
661+ }
662+ if (!chain_times.empty ()) {
663+ std::sort (chain_times.begin (), chain_times.end ());
664+ double med = chain_times[chain_times.size () / 2 ];
665+ std::println (" Chained PCH → compile: median {:.1f}ms (min {:.1f}, max {:.1f})" ,
666+ med,
667+ chain_times.front (),
668+ chain_times.back ());
669+ }
670+ if (!mono_times.empty () && !chain_times.empty ()) {
671+ double mono_med = mono_times[mono_times.size () / 2 ];
672+ double chain_med = chain_times[chain_times.size () / 2 ];
673+ double ratio = chain_med / mono_med;
674+ std::println (" Ratio (chained/mono): {:.2f}x" , ratio);
675+ }
676+
677+ // Cleanup.
678+ fs::remove (mono_hdr);
679+ fs::remove (mono_pch);
680+ for (auto & f: chain_temps)
681+ fs::remove (f);
682+ }
683+
536684// ---------------------------------------------------------------------------
537685// Main
538686// ---------------------------------------------------------------------------
@@ -599,6 +747,7 @@ int main(int argc, char* argv[]) {
599747 bench_monolithic (ALL_HEADERS, chain_length, runs, resource_dir);
600748 bench_chained (ALL_HEADERS, chain_length, runs, resource_dir);
601749 bench_incremental (ALL_HEADERS, chain_length - 1 , runs, resource_dir);
750+ bench_ast_load (ALL_HEADERS, chain_length, runs, resource_dir);
602751
603752 return 0 ;
604753}
0 commit comments