-
Notifications
You must be signed in to change notification settings - Fork 13
Qi Compiler Sync Sept 16 2022
Qi Compiler Sync Sept 16 2022
Adjacent meetings: Previous | Up | Next
We scrutinized the dependencies in the bindingspec library to see if the require latency (that is, the time taken on (require bindingspec)
) could be reduced, and found some quick wins. We explored a number of tools to help with this along the way.
Previously, we discovered that introducing the bindingspec dependency inflated Qi's require latency by about 37%. While the resulting value of 250ms for (require qi)
was still in the normal range for Racket libraries, we were curious whether this latency could be minimized.
The initial value was obtained from the require-latency raco tool:
$ raco require-latency bindingspec
(after compiling the bindingspec package via raco setup bindingspec
)
We wanted a more transparent way to measure the time taken for the purposes of investigating it. Starting a REPL and then running (time ...)
proved not to be a reliable way to measure require latency since, it turned out, the REPL already loads racket/init
(and also xrepl
) which includes a lot of modules, thus reducing the time taken for subsequent imports (which would not load or instantiate any dependencies already loaded).
We used:
$ racket -l racket/base -e "(time (dynamic-require 'bindingspec 0))"
(Note that the opaque second argument to dynamic-require
controls whether the module is loaded only for runtime or also for other phases. It may be worth trying #f
and (void)
there to compare results).
This reported an initial value of about 276ms, which was consistent with the value reported by raco require-latency bindingspec
on this machine.
To understand which dependencies were being loaded here, we tried a few different tools.
We used the profile-imports raco command:
$ raco profile-imports -l bindingspec
to get a sense of modules contributing to the load time. This gave a breakdown of the percentage of time contributed by each module, resembling:
Profiling 25 discovered modules. This will take approx 50.00s...
Timings:
* ...a/work/racket/bindingspec/private/runtime/syntax-classes.rkt (154.49ms, weight 0.158483)
* .../bindingspec/private/syntax/compile/nonterminal-expander.rkt (146.46ms, weight 0.150243)
* ...rk/racket/bindingspec/private/syntax/compile/syntax-spec.rkt (129.43ms, weight 0.132779)
* ...tha/work/racket/bindingspec/private/runtime/binding-spec.rkt (94.29ms, weight 0.096728)
* /Users/siddhartha/work/racket/ee-lib/main.rkt (92.37ms, weight 0.094756)
* ...dhartha/work/racket/bindingspec/private/syntax/interface.rkt (72.07ms, weight 0.073934)
* /Applications/Racket-Latest/collects/syntax/id-set.rkt (62.99ms, weight 0.064622)
* /Users/siddhartha/work/racket/ee-lib/persistent-id-table.rkt (56.58ms, weight 0.058046)
* ...ddhartha/work/racket/bindingspec/private/runtime/compile.rkt (38.56ms, weight 0.039555)
* /Users/siddhartha/work/racket/ee-lib/define.rkt (37.30ms, weight 0.038269)
* ...ha/work/racket/bindingspec/private/syntax/syntax-classes.rkt (28.91ms, weight 0.029655)
* ...ddhartha/work/racket/bindingspec/private/syntax/env-reps.rkt (27.75ms, weight 0.028468)
* /Applications/Racket-Latest/collects/syntax/parse.rkt (25.53ms, weight 0.026192)
The output appeared to include both runtime as well as compile-time dependencies. We were interested in further distinguishing the contributions from each.
raco show-dependencies main.rkt
This listed dependencies but didn't give a lot more information than that.
DrRacket's Module Browser (with "follow lib links" checked) showed the full tree of dependencies for bindingspec's main.rkt, and also showed what phase those dependencies were required at. We discovered, for instance, that as expected, syntax/parse
was only required at phase 1 and above.
We tried the check-requires
tool to see if it would report any dependencies that could be removed, but we weren't able to use it for our purposes:
$ raco check-requires bindingspec
bindingspec:
DROP syntax/parse at 1
DROP ee-lib at 1
[...]
This reported that syntax/parse could be dropped at phase 1, but that wasn't right. We concluded that this tool doesn't recognize empty provides (i.e. require and provide without otherwise using it in the module).
$ raco check-requires qi
qi:
ERROR in qi
-: contract violation
expected: number?
given: '(0 . qi)
context...:
/Applications/Racket-Latest/share/pkgs/macro-debugger-text-lib/macro-debugger/analysis/private/nom-use-alg.rkt:143:2
/Applications/Racket-Latest/share/pkgs/macro-debugger-text-lib/macro-debugger/analysis/private/nom-use-alg.rkt:141:0: mod->bypass-table
[...]
produced an error. We concluded that the tool is not aware of binding spaces.
racket -l racket/base -e "(let ([old (current-load/use-compiled)]) (current-load/use-compiled (lambda (p n) (displayln p) (old p n))))" -e "(time (dynamic-require 'bindingspec 0))"
produced a full list of all modules loaded by bindingspec, resembling:
/Users/siddhartha/work/racket/bindingspec/main.rkt
/Users/siddhartha/work/racket/bindingspec/private/syntax/interface.rkt
/Users/siddhartha/work/racket/bindingspec/private/runtime/errors.rkt
/Users/siddhartha/work/racket/bindingspec/private/runtime/compile.rkt
/Applications/Racket-Latest/collects/racket/private/check.rkt
/Applications/Racket-Latest/collects/racket/match.rkt
/Applications/Racket-Latest/collects/racket/match/match.rkt
/Applications/Racket-Latest/collects/racket/match/runtime.rkt
/Applications/Racket-Latest/collects/racket/match/match-expander.rkt
[...]
There are several stages in the module lifecycle from when it is parsed to when it is required in another module:
- The module is expanded
- The module is compiled
- The compiled file is loaded (i.e. it is read from disk)
- The loaded file is evaluated or "instantiated"
Additionally, there is also the phase at which a module is required that matters for how much latency it contributes overall.
A module may require another module at runtime, or at another phase such as compile-time via (require (for-syntax ...))
. The chain of dependencies and phases within a package determines what phases a particular dependent module is employed at in this package.
When a module is required at runtime, all of its dependencies are loaded from disk, but only the dependencies at the present phase are instantiated, and, in fact, these are instantiated lazily [TODO: verify].
Cross-referencing the output from a combination of the tools above, we identified id-set.rkt
as a dependency that was responsible for pulling in a lot of modules from racket/contract
, and which could be avoided by using id-table
and racket/list
's remove-duplicates
together with the free-identifier=?
predicate, instead. This minimized the reliance on modules from racket/contract
. Measuring the time again with $ racket -l racket/base -e "(time (dynamic-require 'bindingspec 0))"
, the time was now down from 276ms to about 189ms.
- Continue exploring these various tools to try and reduce latency further, especially by instrumenting the instantiation part of the module lifecycle more.
- Validate whether
raco profile-imports
reports both compile-time and runtime dependencies or just the latter. [Update 09/19: it can now do either. It defaults to both, and can limit to a maximum phase (e.g.0
for runtime) via the-P
flag] - Continue on prototyping compiler optimizations and bindings for Qi.
Michael, Sid
Home | Developer's Guide | Calendar | Events | Projects | Meeting Notes