Welcome! Please see the About page for a little more info on how this works.

0 votes
in Compiler by

Currently, the approach to enabling direct linking (DL) is mostly all-of-nothing with pinpoint opt-out (^:redef). In my experience, it makes using DL somewhat awkward in production cases where you still want to retain the ability to jack into the application for debugging and investigation purposes. Knowing upfront which functions you would need to recompile is cumbersome and mentally taxing. In fact, I end up not enabling DL at all because of it.

I wonder if having a more flexible way to tune DL would make it more popular. Here are my thoughts pertaining to this.

Currently, the decision whether to DL is made at the callsite depending on the value of *compiler-options* at the moment of compiling that callsite. The alternative I envision is when the decision to DL is made at the function definition time, and all the future invocations of that function honor that decision.

Let me give you an example. Clojure itself compiles clojure.core namespace with DL enabled. This means that functions within clojure.core statically link to other clojure.core functions. However, if the developer has DL disabled, then the calls to clojure.core functions would go through Var resolution. I personally can't envision a scenario where this precise behavior is desired. Would someone want to redefine a clojure.core function for the code they run while being content with an old version still being used by some other code? Is this something that the language design wants to encourage?

My second point is that a blanket DL usually brings quite unsubstantial performance improvements except when used for specific functions called in tight loops that are amenable to JIT cascade optimizations due to inlining. Such performance-critical functions can be reliably identified by the developer with a help of a profiler, and then it makes sense to only enable DL for those functions. As of now, the only way to make a function that is guaranteed to be JIT-inlinable without going buying into DL wholesale is to use the semi-obscure :inline function which is just a macro, after all.

On the implementation side, this could be implemented by a combination of a special value of *compiler-options* :direct-linking (kinda similar to how *unchecked-math* can just be true or :warn-on-boxed) and a metadata on Vars (could reuse the currently noop ^:static).

TL;DR: I'd like a mode of DL when all callsites of a function compiled with DL to be directly linked automatically, and a way to explicitly mark a function to always use DL. Does anybody else find something like this useful? Would love to hear what you think.

1 Answer

+1 vote
by

On some particular points:

Would someone want to redefine a clojure.core function for the code they run while being content with an old version still being used by some other code?

People do do this, whether it's a good idea or not. https://github.com/generateme/fastmath is one example.

My second point is that a blanket DL usually brings quite unsubstantial performance improvements

There's more to direct linking than just the perf improvements (which, as you say, are usually not significant other than hot loop code). In particular, not needing to load a var for linking significantly reduces the static initializer and constant pool size, resulting in smaller class sizes and reduced load times. Doing DL on a broad basis makes that significant (clojure.core is particularly big so this is magnified there). If we added lazy var loading, that plus DL would additionally improve that.

All that said, I think there are probably ways that DL could be open to more targeted use, and signalling to consumers of a function might be helpful (kind of the opposite of ^:redef - "please direct link me"). But I think it would be best to work this from the angle of "I am trying to do X and can't", and I'm not hearing that kind of a request here. Or alternately - why is DL not used when it could be? Maybe it's just a lack of awareness or maybe it's because it does not work well with other features or workflows.

by
Thank you for the answer, Alex!

> In particular, not needing to load a var for linking significantly reduces the static initializer and constant pool size

Fair point, I haven't considered that.

> "I am trying to do X and can't"

I'll try to re-iterate the two usecases I have.

1. I want to ensure that certain functions of mine are always directly linked so that they stay inlinable (e.g., using something like `^:static` meta).

2. I want to ensure that all `clojure.core` functions are always DLed, in my code or otherwise.

Both #1 and #2 are currently achievable by blanketly enabling DL, but I'd rather not for the reasons I stated in the question. Now, of course, my particular needs may be overly specific, so I created this thread to possibly gather more DL usecases that people might have. If there are more, then it could help surface the most suitable way to extend the available controls over DL.
...