How well does Mathematica code exported to C compare to code directly written for C?

Essentially what it says in the title. Mathematica can export its code to C. How much overhead does that inflict on the code, as compared to writing it from scratch in C?

-
wow, since when does mathematica does C ? I'm not sure a software like mathematica is made to use C language, is it ? – jokoon Jan 28 '12 at 17:31

A lot depends on how you write your code in Mathematica. In my experience, the rule of thumb is that the generated code will be efficient if the code inside Compile more or less resembles the code I would write in plain C (and it is clear why). Idiomatic (high-level) Mathematica code tends to be immutable. At the same time, Compile can handle a number of higher-level functions, such as Transpose, Partition, Map, MapThread, etc. Most of these functions return expressions, and even though these expressions are probably passed to the calling function, they must be created. For example, a call to ReplacePart which replaces a single part in a large array will necessarily lead to copying of that array. Thus, immutability generally implies creating copies.

So, if you write your code in this style and hand it to Compile, you have to keep in mind that lots of small (or large) memory allocations on the heap, and copying of lists (tensors) will be happening. Since this is not apparent for someone who is used to high-level Mathematica programming, the slowdown this may incur may be surprising. See this and this answers for examples of problems coming from many small memory allocations and copying, as well as a speed-up one can get from switching from copying to in-place modifications.

As noted by @acl, one thing worth doing is to set the SystemOptions -> "CompileOptions" as

SetSystemOptions[ "CompileOptions" -> "CompileReportExternal" -> True]


in which case you will get warnings for calling external functions etc.

A good tool to get a "high-level" but precise view on the generated code is the CompilePrint function in the CompiledFunctionTools package. It allows you to print the pseudocode version of the byte-code instructions generated by Compile. Things to watch for in the printout of CompilePrint function:

• Calls to CopyTensor
• Calls to MainEvaluate (callbacks to Mathematica, meaning that something could not be compiled down to C)

One not very widely known technique of writing even large Compile-d functions and combining them from pieces so that there is no performance penalty, is based on inlining. I consider this answer very illustrative in this respect - I actually posted it to showcase the technique. You can also see this answer and a discussion in the comments below, for another example of how this technique may be applied.

In summary - if you want your code to be as fast as possible, think about "critical" places and write those in "low-level" style (loops, assignments, etc) - the more it will resemble C the more chances you have for a speed-up (for an example of a function written in such a style and being consequently very fast, see the seqposC function from this answer). You will have to go against Mathematica ideology and use a lot of in-place modifications. Then your code can be just as fast as hand-written one. Usually, there are just a few places in the program where this matters (inner loops, etc) - in the rest of it you can use higher-level functions as well.

-
it may be worth mentioning that one may do things like SetSystemOptions[ "CompileOptions" -> "CompileReportExternal" -> True] to see some warning of calls to external functions, as well as other options in SystemOptions["CompileOptions"] – acl Jan 27 '12 at 20:53
@acl Thanks, I incorporated your remark in my answer. – Leonid Shifrin Jan 27 '12 at 21:07
Also Compile does not work for highly-optimized command as SparseArray, or DiagonalMatrix. I dont think that rewrite SparseArray or DiagonalMatrix in C-style, then Compile to C is more efficient than built-in SparseArray. So sometimes, try to quit the Compile, and use the highly-optimized command. – DaoTRINH Feb 14 '14 at 18:01
@DaoTRINH Yes, sure, I agree. I think this discussion was (intentionally) restricted to the subset of Mathematica code / structures, which Compile can handle. – Leonid Shifrin Feb 14 '14 at 18:06
@LeonidShifrin :) Weekend and SE. Do you think that rewrite the Compile could solve these problems to generate more optimized C-like code and get compiled in c compiler? Have you try the MathCode add on ? wolfram.com/products/applications/mathcode – DaoTRINH Feb 14 '14 at 19:34

In addition to the answers given, you may tweak specific commands to give better performance. For example Part[] is a candidate for this. Part has to do bound checks. In time critical inner loops you can switch that off by using CompilerGetElement[] instead. Very cautious with this one.

Another thing you might want to try (never needed this myself) is to give platform specific compile optimization options that your CPU supports:

Needs["CompiledFunctionTools"]
Compiler\$CCompilerOptions = {"SystemCompileOptions" -> "-fPIC -O3"}


I think default optimization is -O2.

Furthermore, for example basic arithmetic operations are quite optimized and linking to the runtime lib should be quite fast.

Edit:

One important point I forgot, the internal optimizer will find a good way to formulate your expressions

ExperimentalOptimizeExpression[{x^2 Sin[x^2]}]


Also, with the symbolic power you can simplify expression you could never do by hand or pen an paper....

To see what can be compiled have a look at:

CompileCompilerFunctions[]


Find get warning about external symbols not included you can alternatively use:

On[Compile::noinfo]


Also RuntimeAttributes -> Listable provides for very easy parallelization.

-
What does -fPIC do? – Szabolcs Jan 27 '12 at 21:26
@Szabolcs, man gcc is your friend: "Generate position-independent code (PIC) suitable for use in a shared library..." – user21 Jan 27 '12 at 21:31
You showed many interesting things! Is the expression optimizer used by Compile, or is it meant for manual use? Does the ExperimentalOptimizedExpression head have any special function, or it could be a simple Hold instead? – Szabolcs Jan 27 '12 at 22:03
Yes the expression optimizer is used inside of compile (and other functions in M-, if I am not mistaken). Presumably OptimizedExpression is used during parsing, but I do not know. – user21 Jan 27 '12 at 22:18
Another option you might want to pass to gcc is -march=native: That way you allow gcc to use everything the processor on your machine has to offer, at the cost of portability of the compiled code (which in this case you probably won't care about). – celtschk Mar 9 '12 at 12:01

I don't have an answer but this is a bit hard to format in a comment. If runtime speed is your goal, I'd suggest using Compile with settings

CompilationTarget->"C",
CompilationOptions ->
{"ExpressionOptimization" -> True, "InlineExternalDefinitions" -> True},
RuntimeOptions -> "Speed"


I'm not certain about the inlining, and there may be other options worth tweaking to get the best speed. Also I would imagine to some extent the speed will depend on the optimization capabilities of the C compiler.

But then, I tend to imagine lots of things.

-

If you use the setting CompilationTarget -> "C" (documentation: CompilationTarget) you get a function that is literally converted to C code and compiled:

f = Compile[{{x, _Real}}, Sin[x] + x^2 - 1/(1 + x),
CompilationTarget -> "C"];


Then you can actually export the C code and look at, or use ExportString to print it directly in Mathematica:

ExportString[f, "C"]


That call to ExportString ends with a function looking something like this:

DLLEXPORT int m-b7accf23-a350-44c5-917c-8adbcacfe423(WolframLibraryData libData, mreal A1, mreal *Res)
{
mreal R0_0;
mreal R0_1;
mreal R0_2;
mreal R0_3;
mreal R0_4;
R0_0 = A1;
R0_1 = sin(R0_0);
R0_2 = R0_0 * R0_0;
R0_3 = (mreal) I0_0;
R0_3 = R0_3 + R0_0;
R0_4 = 1 / R0_3;
R0_3 = -R0_4;
R0_1 = R0_1 + R0_2 + R0_3;
*Res = R0_1;
funStructCompile->WolframLibraryData_cleanUp(libData, 1);
return 0;
}


So you can see that with CompilationTarget -> "C"` and basic (C-like) arithmetic operations you do get fairly unadulterated C.

-
The function name looks like invalid C. Modern Mathematica versions don't seem to produce such invalid code. – Ruslan Feb 7 at 11:09