22
$\begingroup$

I wanted to look at the pros & cons of Compile & FunctionCompile. This code below is from Neat Examples in FunctionCompile documentation.

restrictFlow[ x_, y_]:=Module[{z},
  z=1-1/(x+I y)^2;
  {{x,y},{Re[z],-Im[z]}}
];

decl=FunctionDeclaration[restrictFlow, Typed[{"Real64","Real64"}->"PackedArray"::["Real64",2]]@DownValuesFunction[restrictFlow]];

func=Function[{Typed[x0,"Real64"],Typed[x1,"Real64"],Typed[y0,"Real64"], Typed[y1,"Real64"]},
  Table[Flatten[ Table[{restrictFlow[x+d/4,y], restrictFlow[x+d/4,-y]},{x,x0,x1},{y,y0,y1}],2] ,{d, 0,3, 0.05}]
];

usingFunctionCompile=FunctionCompile[decl, func];

Next I try to do the same thing using Compile.

usingCompiled=Compile[{{x0,_Real},{x1,_Real},{y0,_Real},{y1,_Real}},
  Module[{zP,zM},
    Table[Flatten[ Table[
      zP=1-1/(x+d/4+I y)^2;
      zM=1-1/(x+d/4-I y)^2;
      {{{x,y},{Re[zP],-Im[zP]}}, {{x,-y},{Re[zM],-Im[zM]}}},
      {x,x0,x1},{y,y0,y1}],2] ,{d, 0,3, 0.05}
    ]
 ]];

I find that the above implementations return nearly the same results. Any ideas how we can make them return exactly the same results? Now look at the timing comparison.

RepeatedTiming[compileResult = usingCompiled[-7,5,0.5, 3];]
(*  {0.00125593, Null} *)

and

RepeatedTiming[functionCompileResult = usingFunctionCompile[-7,5,0.5, 3];]
(* {0.00273739, Null} *)

The CompiledCodeFunction that takes over twice as long to evaluate than the version written with Compile. Coding with Compile is much easier than coding with FunctionCompile. Compile[] evaluates almost instantly, but FunctionCompile[] takes a while to compile. Am I right that we should only use FunctionCompile when we can't use Compile?

$\endgroup$

1 Answer 1

21
$\begingroup$

On my M1 Max processor and with Mathematica 13.2.1.0, the speed differences are even more severe:

RepeatedTiming[
 functionCompileResult = usingFunctionCompile[-7, 5, 0.5, 3];
 ]

RepeatedTiming[
 compileResult = usingCompiled[-7, 5, 0.5, 3];
 ]

{0.00305243, Null}

{0.000576201, Null}

By using CompilationTarget -> "C" and by performing some simplifications on compile time, I can squeeze out even some more performance from Compile:

usingCompiled2 = Block[{zP, zM, x, y, d},
   
   zP = 1 - 1/(x + d + I y)^2;
   zM = 1 - 1/(x + d - I y)^2;
   
   With[{code = 
      N@ComplexExpand[{{{x, y}, {Re[zP], -Im[zP]}}, {{x, -y}, {Re[zM], -Im[zM]}}}]},
    
    Compile[{{x0, _Real}, {x1, _Real}, {y0, _Real}, {y1, _Real}},
     Table[code, {d, 0., 0.25 3., 0.25 0.05}, {x, x0, x1}, {y, y0, y1}]
     , CompilationTarget -> "C"
     ]
    
    ]
   ];

Here the timing:

RepeatedTiming[  compileResult2 = usingCompiled2[-7, 5, 0.5, 3];  ]

{0.0000944208, Null}

And by leaving out Flatten and by doing the rearrangement with ArrayFlatten outside the compiled function, I get even a tiny bit more:

usingCompiled3 = Block[{zP, zM, x, y, d},
   
   zP = 1 - 1/(x + d + I y)^2;
   zM = 1 - 1/(x + d - I y)^2;
   
   With[{code = N@ComplexExpand[{{{x, y}, {Re[zP], -Im[zP]}}, {{x, -y}, {Re[zM], -Im[zM]}}}]},
    
    Compile[{{x0, _Real}, {x1, _Real}, {y0, _Real}, {y1, _Real}},
     Table[code, {d, 0., 0.25 3., 0.25 0.05}, {x, x0, x1}, {y, y0, y1}]
     , CompilationTarget -> "C"
     ]
    ]
   ];

And here the experiment:

RepeatedTiming[
 compileResult3 = usingCompiled3[-7, 5, 0.5, 3];
 dim = Dimensions[compileResult3];
 compileResult3 = ArrayReshape[compileResult3, {dim[[1]], dim[[2]] dim[[3]] 2, 2, 2}];
 ]

{0.0000802671, Null}

This is 38 times faster than FunctionCompile. And it needs only 0.45 seconds to compile, while FunctionCompile needs 20 seconds.

So, if Compile solves your problem, is easier to code with, compiles faster, and produces this much faster code, then it is really hard to make the case for FunctionCompile...

To be fair, we have to note that FunctionCompile is marked as experimental. So it is still in development. And it can also do a couple of things that Compile cannot.

$\endgroup$
2
  • 1
    $\begingroup$ Unfortunately, at the moment, FunctionCompile is basically unusable, since it is so unreliable and unpredictable. This "experimental" feature is not ready for any sort of a release. There are fundamental implementation problems, and FunctionCompile can easily take a couple hours to "compile" (and fail sometimes) code that I can manually convert to vectorized assembly by hand faster than that, and I suck at it (have to look up all the opcodes, try things, etc.). At a point where doing it by hand is more reliable and faster, there's no reason for FunctionCompile to even be mentioned :( $\endgroup$ Feb 23 at 3:47
  • 1
    $\begingroup$ Now, the promise of FunctionCompile is great - portable LLVM IR is a good thing, functions that can be compiled and easily reused, wow great. The user-facing functionality has great design. The implementation seems to be a flaming waste dump. I'm a bit sour since it has wasted me a lot of time, and after a bit digging into the sources via PrintDefinitions and fixing bugs in code we ostensibly pay good money for proved to be too aggravating :/ $\endgroup$ Feb 23 at 3:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.