• No results found

Caching Model Evaluations and Model Values

In document SRC RR 177 pdf (Page 131-134)

We treat two closely related topics in this section: caching evaluations of models, and recording dependencies on model values in other cache entries.

7.5.1 Model Evaluations

The evaluator creates two cache entries for each model evaluation: a special entry that takes advantage of the special semantics of models, and a normal entry that caches the model evaluation in the same manner as the evaluation of any other

function call.3

The main difference between special and normal model entries is in the dis- tribution of dependencies between the primary and secondary cache keys. The primary key of a special model entry combines two items:

1. the fingerprint of the immutable directory containing the model, which (as explained in Section 4.2.3) captures the state of the entire immutable tree rooted at that directory, and

2. the model’s name relative to that directory.

Computing the primary key in this way implies that the evaluation depends on the model’s entire directory tree, including the complete text of the model itself. All lo- cally referenced files are thus captured in the primary key and need not be recorded in the secondary key. All non-local model imports are also captured, because the absolute pathname of each such import appears in the text of the model file and thus contributes to the fingerprint. (Unfortunately, any files in the model’s directory tree that the model does not reference are also captured, as well as irrelevant elements in the model text such as imports that are not used, comments, and whitespace.) Because so much is captured by the primary key, the secondary key of a special model entry includes only fine-grained dependencies on the model’s environment (“.”) parameter.

In contrast, the dependencies of a normal model entry are calculated in the standard way, by treating the model as a closure of one argument whose context is defined by itsfilesandimportclauses. Thus a normal model entry’s primary key captures only the parse tree of the model body, while its secondary key includes both references to the environment (“.”) parameter and references to files and models made through the body’s free variables.

Why two kinds of entries? Special model entries have fewer secondary depen- dencies than normal model entries, making them faster to look up in the cache. More importantly, as mentioned at the end of Section 7.1, special model entries serve as cut-off points to prevent too many dependencies from propagating up the function call graph: individual dependencies on files or imports of a model are not propagated beyond calls or references to that model. Without these cut-offs, the root node of the function call graph of a build would contain a dependency on every source file contributing to the build.

Moreover, cache hits on special model entries are common. For example, when an application is built against several frequently-used libraries, chances are that most of the libraries will be unchanged, so the evaluator will get fast cache hits on

3Recall from Section 5.3.4 that models are semantically equivalent to closures of one argument,

the libraries’ special model entries. In fact, the performance benefits of the special cache entries created for model evaluations are so appealing that we sometimes create a separate model for a piece of Vesta code, rather than simply wrapping that code in a function definition within an existing model. This technique leads to a slightly larger number of model files in exchange for better caching performance.

False cache misses on special model entries are possible because the entries are intentionally coarse-grained. When looking up a model evaluation in the cache, the evaluator always checks for a special model entry first; if that lookup misses, it checks for a normal model entry. A new special model entry is created whenever the first lookup misses, even in the event of a cache hit on the normal model entry. Although it is uncommon for a cache lookup to miss on a special model entry but hit on the corresponding normal entry, this does happen occasionally. Such hits save enough work to amply justify the cost of creating the normal entry.

7.5.2 Model Values

Recall from Section 5.2.4 that every imported model has a corresponding closure value that can be returned as (part of) a function result just like any other value. Whenever any function evaluation depends on an unevaluated model value, we record this dependency in a coarse-grained manner, using the same idea described above for constructing the primary key of a model evaluation.

For example, suppose function f1 calls model m1, while function f2 returns an unevaluated model m2 as part of its result. (Both these cases are common in our own usage of Vesta.) When f1 calls m1, the evaluator will of course either find or create a special model entry for m1 as just described, and f1 will inherit the limited secondary dependencies from this coarse-grained cache entry as part of its own dependencies. In the case of f2, although m2 is unevaluated and hence no cache entry is involved, the evaluator still constructs f2’s dependency on m2 using the same model fingerprinting technique. That is, the evaluator creates a “V” dependency whose fingerprint field combines (1) the fingerprint of the immutable directory from which m2was imported, and (2) m2’s name relative to that directory. As before, this technique is correct because m2’s value as an (unevaluated) closure is fully specified by the immutable text of the model file that defines it and the immutable contents of the directory tree where the files and import clauses in that model are resolved.

In document SRC RR 177 pdf (Page 131-134)