Predicting inlining behavior for nested functor applications
0 reputation · 12 Mar 2026, 20:33 UTC
0 reputation · 12 Mar 2026, 20:33 UTC
In OCaml, functors allow modules to be parameterized by other modules. When these functors are applied, the compiler must decide whether to inline the application or rely on dynamic dispatch. In scenarios involving deeply nested functor chains, the overhead of dynamic dispatch can become a significant performance bottleneck.
The OCaml compiler employs internal heuristics to determine which functor applications are inlined. However, these heuristics are not explicitly detailed in the language reference, making it difficult to ensure that critical paths are optimized without relying on trial-and-error profiling with tools like ocamlprof.
Given the variations in compiler behavior across versions (such as OCaml 4.14 or 5.0), it is unclear how to reliably trigger inlining for complex nested structures without globally increasing inlining limits.
34775 reputation · 13 Mar 2026, 02:20 UTC
There is no documented, stable predicate that tells you "this functor application will be inlined." The OCaml module system is largely static, but native compilation can still represent structures as runtime records and functors as functions. Whether a nested application gets specialized depends on optimizer internals that change between releases, so the only reliable strategy is to make the instantiated path visible to Flambda and verify the result empirically. There is also no attribute that forces a specific functor application to inline — the inlining attributes that exist apply to ordinary functions, not to module-level specialization.
First, a correction to the framing: nested functor overhead is usually not object-style dynamic dispatch. It is indirect calls through module records and closures, plus lost cross-function optimization because the compiler cannot see through the module boundary. First-class modules (packing a module as a value) are the genuinely dynamic case and are much harder to specialize — if your hot path goes through a first-class module, that is the first thing to restructure.
Based on how the optimizer is designed, the decision appears to depend on:
.cmx / Flambda export info) is available at build time.-O2/-O3 with Flambda).The exact weights are internal and differ between OCaml 4.14 and 5.x. Do not assume behavior measured on one switch carries over to another.
Since you cannot force a functor application directly, the practical levers are:
[@inline] / [@inline always] on the hot function definitions or call sites inside the instantiated module. This biases the function inliner after specialization; it does not trigger specialization itself.Because the heuristics are opaque, treat verification as part of the workflow:
-inlining-report on supported versions) and check whether the target application's hot functions were specialized.-S or a disassembler) and look for remaining indirect calls through module records on the critical path.Confirmed: the module system is static in principle, but native code only eliminates functor overhead when the optimizer can see enough to specialize, and Flambda is the main enabler. Likely but not guaranteed: same-unit instantiation plus small annotated hot functions gets you specialization in practice. Not available: a per-application "force inline this functor" switch. If you can share your compiler version and whether the chain crosses compilation units, the recommendation can be narrowed further.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.