# Is the CompiledFunctionCall WVM opcode efficient?

There is example in Mathematica documentation about Compile

cf = Compile[{{x}}, Sin[x]];
cf5 = Compile[ {{x}}, cf[x^2],
CompilationOptions -> {"InlineExternalDefinitions" -> True,
"InlineCompiledFunctions" -> False}];


The above code makes "InlineCompiledFunctions" False, thus prevents inlining. If we looks at the CompilePrint output,

<<CompiledFunctionTools
CompilePrint[cf5]


will be

        1 argument
3 Real registers
Underflow checking off
Overflow checking off
Integer overflow checking on
RuntimeAttributes -> {}

R0 = A1
Result = R2

1   R1 = Square[ R0]
2   R2 = CompiledFunctionCall[ Hold[CompiledFunction[{x}, Sin[x], -CompiledCode-]][ R1]]
3   Return


There is a CompiledFunctionCall opcode. I don't know whether this call is as efficient as the "inline" version or not. The documentation didn't say.

I quote a paragraph here which I don't quite understand.

Here the external definition is used, but the compiled function is not inlined. Instead it uses an efficient instruction to allow one compiled function to call another. This type of call is important since it could allow a compiled function to call itself, and when parallel execution is carried out in the compiler the call can be done without any synchronization locking.

What does it mean? It says "it could allow a compiled function to call itself". But there is a example in the same documentation where the compiled function is a recursive call. For example,

cFact = Compile[{{x, _Integer}}, If[x == 1, 1, x*cFact[x - 1]],
{{_cFact, _Integer}},
CompilationOptions -> {"InlineExternalDefinitions" -> True}];
CompilePrint[cFact]


And what is "synchronization locking"?

-

The Wolfram Virtual Machine's CompiledFunctionCall opcode is a fast way to let one compiled function call another. The speed advantage is largely because the call can be made without leaving the virtual machine.

Sometimes inlining can be fast, especially for functions with very small function bodies, but you would simply need to test both ways to know which is better.

That's why the documentation doesn't say which one is faster -- it depends on the code you have running.

As to synchronization locking:

When the virtual machine is running in a parallel thread and needs to call the Mathematica evaluator, it first has to perform "synchronization locking". Imagine that parallel threads are rooms where work can be done, they open out to a hallway, and at the end of the hall is the evaluator room. The rule is that only one person can be in the evaluator room at a time, and to enforce this, there is a lock on the door to the evaluator room. If someone is in a parallel thread room and needs to use the evaluator room, they leave their room, and if the evaluator room is empty, they enter, lock the door (this act is called "synchronization locking"), perform their work, unlock the door and leave. You can see that if someone else needs to use the evaluator room when it is already in use, they cannot enter and they need to wait. This waiting slows things down. The paragraph is explaining that if the only reason you need the evaluator is to call another compiled function, this new feature of CompiledFunctionCall opcode means you can do that without leaving the parallel thread, and avoid the possibility of waiting due to synchronization locking.

-
This answer definitely clarifies what is written in the documentation--thanks and +1. But, ideally I'd like to see the documentation itself clarified in the future, as absent your answer, it isn't at all obvious what the threads are supposed to be synchronizing on here. Actually, I'm not sure if that mutex was even implemented [yet], at least for calls using opcode 46 or 47, as these produce CompiledFunction::pext with Parallelization -> True`. Only opcode 43 seems to be allowable, which seems to make this discussion about synchronization irrelevant, as none is apparently happening. –  Oleksandr R. Jan 5 at 6:05
I wasn't able to get a recursive compiled function to work either (that is, to create one, much less parallelize it--it generates a big mess of nested opcode 46 calls, and doesn't seem to use 43 at all). So, unless I'm missing something important, as far as the documentation is concerned, both the comment about the synchronization and the recursion point seem to be non sequiturs as these features are actually not implemented in the current release? –  Oleksandr R. Jan 5 at 7:07