Read each build file once per OCaml rewatch build
Why
Every in-process compile read the interfaces it uses from disk again, above all the runtime's, and unmarshalled them again, and finding one took a failed open in each load-path directory before the one that has it. On macOS an open costs about 15 µs and gets slower when several threads call it at once, so a clean build of a 2000-module project spent a third of its CPU time in open.
The compiler now reads and writes build files through Build_file_store. Rewatch enables it for the duration of each build: a file is read from disk at most once, a file written through the store is kept for later reads, and each domain keeps the interfaces it has unmarshalled until the file is written again. bsc leaves the store disabled and reads the disk as before. Writers render their output in memory first, so the bytes written are unchanged: every artifact of the fixtures below, .cmt files included, is byte-identical to master's.
modifiedIO file system calls of a clean build, 2000 modules, 8 domains
text
modifiedBuild wall time, median of 5, Apple M4 Pro
text
Review
- Can a request read a stale file from the store? Within a build a file is read only after the request that writes it has finished, because rewatch orders requests by their dependencies, and writes go through the store. Requests spell paths relative to their working directory, such as
lib/bs/../ocaml/A.cmi, so keys remove.and..lexically. The store starts every build empty, so it sees changes made between builds. - Is it safe to reuse an unmarshalled interface in later requests? Only on the domain that read it: copying a signature marks its types in place until the copy ends, which is why domains don't share them. A request that fails drops the domain's interfaces, in case it stopped with a type still marked, and every request resets the abbreviations it memoized, including those in cached interfaces.