From a5e4a650de3e3f00be3124b14936fc777e89bb4e Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Mon, 9 Nov 2026 06:45:55 +0000 Subject: [PATCH] Report the build's worker allocations when asked OCaml's allocation report at exit (OCAMLRUNPARAM=v=0x400) counts only the main domain, and the build compiles on worker domains: a clean build of tests/tests reported 3.2M words instead of about 252M. With REWATCH_REPORT_WORKER_ALLOCATIONS set, the build also prints `worker_allocated_words: `, for the compiler benchmarks. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Kbf3gGPsF3fHZn9rBxA7Hm Signed-off-by: Christoph Knittel --- rewatch-ocaml/README.md | 5 +++++ rewatch-ocaml/process.ml | 24 ++++++++++++++++++++---- rewatch-ocaml/process.mli | 4 ++++ rewatch-ocaml/rescript_ocaml.ml | 10 ++++++++++ rewatch-ocaml/tests/run.sh | 16 ++++++++++++++++ 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/rewatch-ocaml/README.md b/rewatch-ocaml/README.md index e3f25b27ea..48b47847bf 100644 --- a/rewatch-ocaml/README.md +++ b/rewatch-ocaml/README.md @@ -97,6 +97,11 @@ to choose another count: a positive integer, lowered to the machine's limit of `max(12, min(32, CPUs))`. The main domain schedules dependencies; workers run compiler requests and publish what they produce. +The runtime's allocation report at exit (`OCAMLRUNPARAM=v=0x400`) only counts +the main domain. With `REWATCH_REPORT_WORKER_ALLOCATIONS` set, the build also +prints `worker_allocated_words: `, the words its workers allocated, which +the compiler benchmarks (`scripts/compilerBenchmarks.js`) add to it. + The driver gives each request fresh compiler state (flags, warnings, caches, predefined types, identifier counters) and restores it on every exit, so requests on different domains don't interfere and repeated requests produce diff --git a/rewatch-ocaml/process.ml b/rewatch-ocaml/process.ml index 737071ccb6..4de93fb3e8 100644 --- a/rewatch-ocaml/process.ml +++ b/rewatch-ocaml/process.ml @@ -226,6 +226,20 @@ let rec worker_loop pool = memory. 8M added memory for little more speed. *) let worker_minor_heap_words = 4 * 1024 * 1024 +(* The words that finished workers allocated. The runtime's own report at exit + (OCAMLRUNPARAM=v=0x400) counts only the main domain, so the compiler + benchmarks add these to it. Allocated words are counted as the runtime + counts them: minor plus major, without the promoted ones. *) +let worker_allocated_words_total = Atomic.make 0 + +let record_worker_allocations () = + let minor, promoted, major = Gc.counters () in + ignore + (Atomic.fetch_and_add worker_allocated_words_total + (int_of_float (minor +. major -. promoted))) + +let worker_allocated_words () = Atomic.get worker_allocated_words_total + let create_worker_pool ~max_jobs notifier = let pool = { @@ -251,10 +265,12 @@ let create_worker_pool ~max_jobs notifier = before re-raising the first error. *) let run_worker () = Gc.set {(Gc.get ()) with minor_heap_size = worker_minor_heap_words}; - try worker_loop pool - with exn -> - Child.with_lock pool.mutex (fun () -> - if Option.is_none pool.worker_error then pool.worker_error <- Some exn) + (try worker_loop pool + with exn -> + Child.with_lock pool.mutex (fun () -> + if Option.is_none pool.worker_error then + pool.worker_error <- Some exn)); + record_worker_allocations () in let rec start_workers remaining = if remaining > 0 then ( diff --git a/rewatch-ocaml/process.mli b/rewatch-ocaml/process.mli index 4ba7c22b5c..e9e7a783aa 100644 --- a/rewatch-ocaml/process.mli +++ b/rewatch-ocaml/process.mli @@ -14,6 +14,10 @@ val succeeded : result -> bool val status_string : Unix.process_status -> string val default_max_jobs : int +val worker_allocated_words : unit -> int +(** The words that the worker domains of finished worker pools allocated, + which the runtime's report at exit leaves out. *) + val task : ?env:Spawn.Env.t -> ?on_result:(result -> result) -> job -> task val concurrent_task : cancel:(unit -> unit) -> diff --git a/rewatch-ocaml/rescript_ocaml.ml b/rewatch-ocaml/rescript_ocaml.ml index bf934a6cea..4687735458 100644 --- a/rewatch-ocaml/rescript_ocaml.ml +++ b/rewatch-ocaml/rescript_ocaml.ml @@ -64,8 +64,18 @@ let run = function | command -> with_termination_handlers (fun ~poll -> run_command ~poll command) +(* For the compiler benchmarks (scripts/compilerBenchmarks.js): the runtime's + report at exit only counts the main domain's allocations, and the compiler + runs on worker domains. *) +let report_worker_allocations () = + if Option.is_some (Sys.getenv_opt "REWATCH_REPORT_WORKER_ALLOCATIONS") then + at_exit (fun () -> + Printf.eprintf "worker_allocated_words: %d\n%!" + (Process.worker_allocated_words ())) + let main () = Platform.configure_standard_streams (); + report_worker_allocations (); try match Cli.eval Sys.argv with | Cli.Run command -> run command diff --git a/rewatch-ocaml/tests/run.sh b/rewatch-ocaml/tests/run.sh index 44b53c7744..f454ee6cba 100644 --- a/rewatch-ocaml/tests/run.sh +++ b/rewatch-ocaml/tests/run.sh @@ -2398,3 +2398,19 @@ for attempt in first second; do exit 1 fi done + +# The compiler benchmarks add the words the worker domains allocated, which +# the runtime's report at exit leaves out, to that report. +worker_allocations="$work/worker-allocations" +mkdir -p "$worker_allocations/src" +printf '%s\n' \ + '{"name":"worker-allocations","sources":"src","package-specs":{"module":"esmodule","in-source":true,"suffix":".mjs"}}' \ + >"$worker_allocations/rescript.json" +printf 'let x = 1\n' >"$worker_allocations/src/A.res" +env REWATCH_REPORT_WORKER_ALLOCATIONS=1 REWATCH_COMPILER_DOMAINS=1 \ + "$port" build "$worker_allocations" >"$worker_allocations/build.log" 2>&1 +worker_words=$(sed -n 's/^worker_allocated_words: \([0-9]*\)$/\1/p' "$worker_allocations/build.log") +if ! test "${worker_words:-0}" -gt 0; then + cat "$worker_allocations/build.log" >&2 + exit 1 +fi