How to avoid “CopyTensor” in compile function with external call?

I am writing a complicated compile function. To make it clear, I want to separate it into two smaller compile functions with one calling another. As you can see the example below, the cf2 calling cf1

cf1 = Compile[{{h, _Complex, 1}}, Module[{fab},
fab = Table[0. I, {a, 1, 5, 1}, {b, 1, 5, 1}];
fab]];

cf2 = Compile[{{h, _Complex, 1}}, Module[{fab},
fab = cf1[h]
], CompilationOptions -> {"InlineExternalDefinitions" -> True,
"InlineCompiledFunctions" -> True}];

The problem is that if you use CompilePrint[cf2] to check compiled code, there is "CopyTensor".

T(C1)1 = CopyTensor[ T(C1)0]]

I understand that "CopyTensor" is nothing but passing argument to another function, but it is a well known inefficient command. So, my question is is it possible to avoid that? Thanks!

The CopyTensor is an artifact produced by inlining. Instead, you can try inlining with With as follows:

cf1 = Compile[{{h, _Complex, 1}},
Table[0. I, {a, 1, 5, 1}, {b, 1, 5, 1}],
CompilationTarget -> "C"
];
cf2 = With[{cf1 = cf1},
Compile[{{h, _Complex, 1}},
cf1[h],
CompilationTarget -> "C"
]
];

Now the pseudo code reads as

CompiledFunctionToolsCompilePrint[cf2]

[...]

1 T(C2)1 = LibraryFunction[<>, compiledFunction11, {{Complex, 1, Constant}}, {Complex, 2}][ T(C1)0]]

2 Return

So we replaced the CopyTensor by a function call. Since in C, arrays are passed by reference, this should be faster than a CopyTensor. But I am not sure. One should try both variants and choose the one that performs better.

PS.: One can also try to set CompilationTarget -> "WVM" in both Compiles. Then the critical lines of code looks like this:

[...]

1 T(C2)1 = CompiledFunctionCall[ Hold[CompiledFunction[{h}, Table[0. I, {a, 1, 5, 1}, {b, 1, 5, 1}], -CompiledCode-]][ T(C1)0]]

2 Return

Sometimes, it is better to not leave the Wolfram Virtual Machine when frequently calling compiled functions by each other.

• wow! It works exactly what I want! Thank you very much! – cwei Sep 10 '18 at 22:52
• You're welcome! – Henrik Schumacher Sep 10 '18 at 22:53

You could use simple quoting mechanism to inline code of one function into another at top level.

ClearAll[quote, inline, unquote, eval]
SetAttributes[{quote, unquote}, HoldAllComplete]
quote@expr : Except@_Symbol := Unevaluated@expr /. {x : _unquote | _quote | _inline :> x, s_Symbol :> quote@s}
unquote@args___ := args
eval = # /. HoldPattern@quote@s_ :> s &;

inline::unsupported = "1 is not one of function formats supported by inline.";
inline@HoldPattern@Function@body_ := Function@quote@body
inline@HoldPattern@Function[args_ , body_, attrs_ | PatternSequence[]] := Function[args, quote@body, attrs]
inline@HoldPattern@CompiledFunction[___, f_Function, ___] := inline@f
inline@f_ := (Message[inline::unsupported, HoldForm@f]; \$Failed)

Note that such inlining will not exactly mimic behavior of function call in top level Mathematica code. It'll fail if function result relies on argument evaluation, before they are passed to function body, or when single symbol used as function argument can represent Sequence of function arguments. In top level code, above inlining works, for arbitrary arguments, only when inlined function has HoldAllComplete attribute, but for compiled functions neither evaluation control nor Sequence splicing is relevant.

In case from OP:

cf1 = Compile[{{h, _Complex, 1}}, Module[{fab},
fab = Table[0. I,{a,1,5,1},{b,1,5,1}];
fab
]];

cf2 = quote@Compile[{{h, _Complex, 1}},
Module[{fab}, fab = inline[cf1]@h]
] // eval;

Needs@"CompiledFunctionTools"
CompilePrint@cf2
(* ...
1   I2 = I1
2   I3 = I1
3   I5 = I6
4   T(C2)1 = Table[ I2, I3]
5   I4 = I6
6   goto 11
7   I7 = I6
8   goto 10
9   Element[ T(C2)1, I5] = C0
10  if[ ++ I7 <= I3] goto 9
11  if[ ++ I4 <= I2] goto 7
12  Return *)
• That's a cute trick! Thanks! – cwei Sep 14 '18 at 1:39