Skip to main content

performance tuning - How to compile a diagonal array efficiently?


For example, if we use some functions defined prior to Compile, we usually have the main evaluators in the compiled codes, pointing to the definition of the function.


f[t_] := If[t <= 1., Cos[t]*Sin[t], 0.]

CompilePrint@
Compile[{{t, _Real}}, IdentityMatrix[2] - f[t],
CompilationOptions -> {"InlineCompiledFunctions" -> True,

"InlineExternalDefinitions" -> True}]


      1 argument
1 Integer register
3 Real registers
2 Tensor registers
Underflow checking off
Overflow checking off
Integer overflow checking on

RuntimeAttributes -> {}

R0 = A1
I0 = 2
Result = T(R2)1

1 T(I2)0 = MainEvaluate[ Hold[IdentityMatrix][ I0]]
2 R1 = MainEvaluate[ Hold[f][ R0]]
3 R2 = - R1
4 T(R2)1 = R2 + T(I2)0

5 Return

We can avoid main evaluator by using Evaluate in the function, which specifically substidue the definition of the function. However, sometimes this introduces repeated code in the compiled results. For instance, in the following example, Evaluate simplify expands a number into a matrix and repeatedly calculated this expression for two times. We can see that the 26-50 lines of the compiled code are essentially the same as 1-25 lines.


CompilePrint@
Compile[{{t, _Real}}, Evaluate[IdentityMatrix[2] - f[t + 1./2.]],
CompilationOptions -> {"InlineCompiledFunctions" -> True,
"InlineExternalDefinitions" -> True}]


      1 argument

1 Boolean register
1 Integer register
12 Real registers
3 Tensor registers
Underflow checking off
Overflow checking off
Integer overflow checking on
RuntimeAttributes -> {}

R0 = A1

I0 = 1
R3 = 1.
R4 = 7.
R1 = 0.5
R7 = 0.
Result = T(R2)2

1 R2 = R1 + R0
2 B0 = R2 <= R3 (tol R4)
3 if[ !B0] goto 10

4 R2 = R1 + R0
5 R5 = Cos[ R2]
6 R6 = Sin[ R2]
7 R5 = R5 * R6
8 R6 = R5
9 goto 11
10 R6 = R7
11 R5 = - R6
12 R6 = I0
13 R6 = R6 + R5

14 R5 = R1 + R0
15 B0 = R5 <= R3 (tol R4)
16 if[ !B0] goto 23
17 R5 = R1 + R0
18 R8 = Cos[ R5]
19 R9 = Sin[ R5]
20 R8 = R8 * R9
21 R9 = R8
22 goto 24
23 R9 = R7

24 R8 = - R9
25 T(R1)0 ={ R6, R8 }
26 R6 = R1 + R0
27 B0 = R6 <= R3 (tol R4)
28 if[ !B0] goto 35
29 R6 = R1 + R0
30 R8 = Cos[ R6]
31 R9 = Sin[ R6]
32 R8 = R8 * R9
33 R9 = R8

34 goto 36
35 R9 = R7
36 R8 = - R9
37 R9 = R1 + R0
38 B0 = R9 <= R3 (tol R4)
39 if[ !B0] goto 46
40 R9 = R1 + R0
41 R10 = Cos[ R9]
42 R11 = Sin[ R9]
43 R10 = R10 * R11

44 R11 = R10
45 goto 47
46 R11 = R7
47 R10 = - R11
48 R11 = I0
49 R11 = R11 + R10
50 T(R1)1 ={ R8, R11 }
51 T(R2)2 ={ T(R1)0, T(R1)1 }
52 Return


So is there a way to fix this repeating?


Note that change only the argument of the external function, the behavior changes, why?


CompilePrint@
Compile[{{t, _Real}}, Evaluate[IdentityMatrix[2] - f[t]],
CompilationOptions -> {"InlineCompiledFunctions" -> True,
"InlineExternalDefinitions" -> True}]


      1 argument
1 Boolean register

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

R0 = A1
I0 = 1

R1 = 1.
R2 = 7.
R5 = 0.
Result = T(R2)2

1 B0 = R0 <= R1 (tol R2)
2 if[ !B0] goto 8
3 R3 = Cos[ R0]
4 R4 = Sin[ R0]
5 R3 = R3 * R4

6 R4 = R3
7 goto 9
8 R4 = R5
9 R3 = - R4
10 R4 = I0
11 R4 = R4 + R3
12 T(R1)0 ={ R4, R3 }
13 T(R1)1 ={ R3, R4 }
14 T(R2)2 ={ T(R1)0, T(R1)1 }
15 Return



Answer



For nicer inlining techniques, see my answer here


In what IdentityMatrix[2] - f[t] evaluates to, there is pretty much four times the same code.


Clear[t];
IdentityMatrix[2] - f[t]


{{1 - If[t <= 1., Cos[t] Sin[t], 0.], -If[t <= 1., Cos[t] Sin[t], 0.]}, {-If[t <= 1., Cos[t] Sin[t], 0.], 1 - If[t <= 1., Cos[t] Sin[t], 0.]}}




I will inline f using DownValues. Throughout this answer, you should read a construction like g@@(Hold[...f[t]...]/.DownValues[f]) as g[...f[t]...], only realising that now f has been inlined. Sadly the syntax highlighter now makes the colours of the variables a bit confusing, but what can you do.


Note that a call to MainEvaluate to compute a big IdentityMatrix is not bad. It is also not so bad to calculate f[t] using MainEvaluate only once if the matrix is very large. But I am not sure if you have large matrices in mind, or if you are interested in generating small matrices quickly (a lot of times?).


Big matrices


The following code makes one call to MainEvaluate to get the IdentitiyMatrix. It only calculates the Sin and the Cos once.


f[t_] := If[t <= 1., Cos[t]*Sin[t], 0.]

cfu =
Function[Null, Compile[{{t, _Real}}, #], HoldAll] @@
(
Hold[

IdentityMatrix[2] - f[t - 0.5]
] /. DownValues[f]
)

You could also write this like this if you prefer


specialReleaseHold[expr_] := Delete[expr, {0, 0}]

cfu2 =
specialReleaseHold@
Hold[Compile][

Unevaluated[{{t, _Real}}]
,
Unevaluated @@
(
Hold[
IdentityMatrix[2] - f[t - 0.5]
] /. DownValues[f]
)
]


And we have


CompilePrint@cfu == CompilePrint@cfu2


True



ReleaseHold would actually do the same thing as specialReleaseHold here (in fact, everywhere in this answer), but in similar cases using ReleaseHold could be bad.


Another alternative (focussed on large matrices), using ConstantArray


cfu3 =
specialReleaseHold@

Hold[Compile][
Unevaluated[{{t, _Real}}],

Unevaluated @@
Hold[
Block[
{res, i},
res =
ConstantArray[
-f[t - 0.5], {2, 2}

];
For[i = 1, i <= 2, i++,
res[[i, i]] += 1

];
res
]
] /. DownValues[f]
]


Small matrices


For small matrices we want to avoid MainEvaluate altogether. We could do


cfu4 =
specialReleaseHold@
Hold[Compile][
Unevaluated[{{t, _Real}}],
Unevaluated @@
(
Hold[
Block[{res, term},

term = -f[t - 0.5];
res = {{1., 0.}, {0., 1.}};
res + term
]
] /. DownValues[f]
)

];

Which somehow turns out to be faster than



cfu5 =
specialReleaseHold@
Hold[Compile][
Unevaluated[{{t, _Real}}],
Unevaluated @@
(
Hold[
Block[{term},
term = -f[t - 0.5];
{{1. + term, 0. + term}, {0. + term, 1. + term}}

]
] /. DownValues[f]
)
];

As we will see further below, neither of these functions use MainEvaluate.


Uncompiled (small matrices)


We will see indeed that compiling pays off. For reference, we define the following function


fu =
ReleaseHold@

Hold[Function][
Hold[t],
Hold[
Block[{term},
term = -f[t - 0.5];
{{1. + term, 0. + term}, {0. + term, 1. + term}}
]
] /. DownValues[f]
];


Comparison


cfu[2.] == cfu2[2.] == cfu3[2.] == cfu4[2.] == cfu5[2.] == fu[2.]==
IdentityMatrix[2]


True



cfu[0.1] == cfu2[0.1] == cfu3[0.1] == cfu4[0.1] == cfu5[0.1] == fu[0.1]



True



StringFreeQ[CompilePrint@#, "MainEvaluate"] & /@
{cfu, cfu2, cfu3,
cfu4, cfu5}

{False, False, False, True, True}


Function[Do[#[0.1], {10000}] // Timing // First] /@ {cfu, cfu2, cfu3, 
cfu4, cfu5, fu}



{0.023010, 0.020006, 0.024175, 0.009206, 0.011438, 0.077503}



Comments

Popular posts from this blog

front end - keyboard shortcut to invoke Insert new matrix

I frequently need to type in some matrices, and the menu command Insert > Table/Matrix > New... allows matrices with lines drawn between columns and rows, which is very helpful. I would like to make a keyboard shortcut for it, but cannot find the relevant frontend token command (4209405) for it. Since the FullForm[] and InputForm[] of matrices with lines drawn between rows and columns is the same as those without lines, it's hard to do this via 3rd party system-wide text expanders (e.g. autohotkey or atext on mac). How does one assign a keyboard shortcut for the menu item Insert > Table/Matrix > New... , preferably using only mathematica? Thanks! Answer In the MenuSetup.tr (for linux located in the $InstallationDirectory/SystemFiles/FrontEnd/TextResources/X/ directory), I changed the line MenuItem["&New...", "CreateGridBoxDialog"] to read MenuItem["&New...", "CreateGridBoxDialog", MenuKey["m", Modifiers-...

How to thread a list

I have data in format data = {{a1, a2}, {b1, b2}, {c1, c2}, {d1, d2}} Tableform: I want to thread it to : tdata = {{{a1, b1}, {a2, b2}}, {{a1, c1}, {a2, c2}}, {{a1, d1}, {a2, d2}}} Tableform: And I would like to do better then pseudofunction[n_] := Transpose[{data2[[1]], data2[[n]]}]; SetAttributes[pseudofunction, Listable]; Range[2, 4] // pseudofunction Here is my benchmark data, where data3 is normal sample of real data. data3 = Drop[ExcelWorkBook[[Column1 ;; Column4]], None, 1]; data2 = {a #, b #, c #, d #} & /@ Range[1, 10^5]; data = RandomReal[{0, 1}, {10^6, 4}]; Here is my benchmark code kptnw[list_] := Transpose[{Table[First@#, {Length@# - 1}], Rest@#}, {3, 1, 2}] &@list kptnw2[list_] := Transpose[{ConstantArray[First@#, Length@# - 1], Rest@#}, {3, 1, 2}] &@list OleksandrR[list_] := Flatten[Outer[List, List@First[list], Rest[list], 1], {{2}, {1, 4}}] paradox2[list_] := Partition[Riffle[list[[1]], #], 2] & /@ Drop[list, 1] RM[list_] := FoldList[Transpose[{First@li...

dynamic - How can I make a clickable ArrayPlot that returns input?

I would like to create a dynamic ArrayPlot so that the rectangles, when clicked, provide the input. Can I use ArrayPlot for this? Or is there something else I should have to use? Answer ArrayPlot is much more than just a simple array like Grid : it represents a ranged 2D dataset, and its visualization can be finetuned by options like DataReversed and DataRange . These features make it quite complicated to reproduce the same layout and order with Grid . Here I offer AnnotatedArrayPlot which comes in handy when your dataset is more than just a flat 2D array. The dynamic interface allows highlighting individual cells and possibly interacting with them. AnnotatedArrayPlot works the same way as ArrayPlot and accepts the same options plus Enabled , HighlightCoordinates , HighlightStyle and HighlightElementFunction . data = {{Missing["HasSomeMoreData"], GrayLevel[ 1], {RGBColor[0, 1, 1], RGBColor[0, 0, 1], GrayLevel[1]}, RGBColor[0, 1, 0]}, {GrayLevel[0], GrayLevel...