Actual Performance Costs of OOP Objects

@vlaviron The code is written with let module M = B in ... call (module M); if we wrote call (module B) ... instead, I would have naively expected the (module B) coercion to be recognized as a constant and be allocated statically. But this doesn’t work, and I think it is because the module field reads are treated as mutable loads, probably due to the compiler scheme for recursive modules.

Intuitively we should be able to do better here, because (1) B is not recursive, and (2) in the general case, all code that is not lexically inside the mutual-recursion nest of B will never be able to observe mutation, and should be able to treat those loads as immutable. (It’s not exactly an “initializing writes” scenario, but B could be considered redefined as an immutable structure after its recursive declaration.) What do you think?

(I haven’t tried to dig into the actual code, so this is mostly (informed) speculation)

I believe that recursive modules are unrelated; when you reach Lambda, all field reads are mutable (you might remember that I’ve tried to push for changes to that in the past). The only information that the middle-end can recover is which allocations are immutable or not, and indeed a module block allocation is always immutable. In particular, with Flambda I would expect the coercion itself to disappear.
Note that let module M = B in ... call (module M), without any coercion, should be equivalent to ... call (module B). There are some subtleties if the module definition is at toplevel, but that isn’t the case here.
So my guess for the reason why the module isn’t allocated statically is that Closure has limits on the shape of constants that it can statically allocate (it’s possible that it can’t statically allocate blocks containing closures except in fields of the global module, or something like that).

That is clearly an unfair comparison :slightly_smiling_face: akin to creating a new object in every iteration of the loop. Normally one would unpack the module outside the loop and call its functions in the loop, which amounts to a record access.

5 Likes

The internals of liquidsoap rely on objects for its sources. Historically, I believe it was in order to be able to implement inheritance patterns from a general source API to specialized operators.

For us, performance bottleneck is not in the OO side so we’re not worried about it too much. However, I do believe that, in general, the object paradigm is indeed phasing out in programming languages in general. It feels like an antiquated concept which is not particularly well suited for static analysis, manual debugging and code reasoning in general.

In OCaml, modules, especially now with first-class modules, provide much better tools for most of the task you could use objects for. In this context, I am not much worried about the lack of development of objects. I don’t see it as an important feature of the language.

2 Likes