3
$\begingroup$

Consider the following code:

TableVals = RandomReal[{0, 1}, {10^6, 2}];
cond1 = Hold@
   Compile[{{TableVals, _Real, 2}, {i, _Integer}}, 
    Boole[TableVals[[i]][[1]]^2 > 0.5]*
     Boole[0.7 > TableVals[[i]][[2]]], CompilationTarget -> "C", 
    RuntimeOptions -> "Speed"] // ReleaseHold
BlockPars[parameter_] := Block[{},
  cond2 = 
   If[parameter > 1, 
    Hold@Compile[{{TableVals, _Real, 2}, {i, _Integer}}, 
       Boole[TableVals[[i]][[1]]*TableVals[[i]][[2]] > 0.2], 
       CompilationTarget -> "C", RuntimeOptions -> "Speed"] // 
     ReleaseHold, 
    Hold@Compile[{{TableVals, _Real, 2}, {i, _Integer}}, 1, 
       CompilationTarget -> "C", RuntimeOptions -> "Speed"] // 
     ReleaseHold];
  Summ = Hold@
       Compile[{{TableVals, _Real, 2}}, 
        Sum[cond1[TableVals, i]*cond2[TableVals, i], {i, 1, 
          Length[TableVals], 1}], CompilationTarget -> "C", 
        RuntimeOptions -> "Speed"] /. DownValues@cond1 /. 
     DownValues@cond2 // ReleaseHold;
  Summ[TableVals]
  ]

Its purpose is to evaluate some sum Summ with each term being the product of two conditions cond1, cond2. The second condition is controlled by some parameter parameter. If the latter is < 1, then cond2 is just identity.

To compile Summ, I defined cond2 in a very stupid way. Namely, if parameter < 1, I define its value ($\equiv 1$) via compiled function. This would decrease the performance of the evaluation compared to just the sum evaluation without cond2:

BlockPars[0.7] // AbsoluteTiming

{1.91724, 204667}

Summ2 = Hold@
     Compile[{{TableVals, _Real, 2}}, 
      Sum[cond1[TableVals, i], {i, 1, Length[TableVals], 1}], 
      CompilationTarget -> "C", RuntimeOptions -> "Speed"] /. 
    DownValues@cond1 // ReleaseHold;
Summ2[TableVals] // AbsoluteTiming

{0.0203522, 204667}

The alternative to the definition of Summ in Block would be just to use If, not including cond2 if parameter< 1:

Summ = If[parameter>1,Hold@
       Compile[{{TableVals, _Real, 2}}, 
        Sum[cond1[TableVals, i]*cond2[TableVals, i], {i, 1, 
          Length[TableVals], 1}], CompilationTarget -> "C", 
        RuntimeOptions -> "Speed"] /. DownValues@cond1 /. 
     DownValues@cond2 // ReleaseHold, Compile[{{TableVals, _Real, 2}}, 
        Sum[cond1[TableVals, i], {i, 1, 
          Length[TableVals], 1}], CompilationTarget -> "C", 
        RuntimeOptions -> "Speed"] /. DownValues@cond1 /. 
     DownValues@cond2 // ReleaseHold];

However, in my real situation this If would multiply the code length too significantly (as it just doubles the code length), and I would like to preserve a more compact way of defining Summ (like in the block above).

Could you please tell me how to preserve the compactness of the code while keep using Compile in this case?

$\endgroup$

1 Answer 1

5
$\begingroup$

I'd recommend to leave all the fluff away and to simply write two loops with a single branching to decide which loop to evaluate. Moreover, it might be a good idea to use boolean arithmetic (i.e. &&) instead of integer multiplication for the simple reason that the former allows for short circuiting. However, sum += Boole[...] seems to be faster than If[..., ++sum];, probably for the reason that this can go without branching.

cf = With[{c = Sqrt[0.5]},

  Compile[{{TableVals, _Real, 2}, {parameter, _Real}},
    Block[{n, sum, a, b},
     
     n = Length[TableVals];
     sum = 0;
     
     If[parameter > 1.,
      
      Do[
       a = Compile`GetElement[TableVals, i, 1];
       b = Compile`GetElement[TableVals, i, 2];
       sum += Boole[(0.7 > b) && (Abs[a] > c ) && (a b > 0.2)];
       , {i, 1, n}
       ]
      ,
      Do[
        a = Compile`GetElement[TableVals, i, 1];
        b = Compile`GetElement[TableVals, i, 2];
        sum += Boole[(0.7 > b) && (Abs[a] > c )];
        , {i, 1, n}
        ];
      ];
     sum
     ],
    CompilationTarget -> "C",
    RuntimeOptions -> "Speed"
    ]
   ];

Not only does this convey the intent more clearly to your fellow developers; it is also 100 times faster in execution. Probably because it compiles a function only once -- and not for every single call to BlockPars.

Actually CompiledFunctionTools`CompilePrint[Summ] reveals this:

        1 argument
        11 Integer registers
        1 Tensor register
        Underflow checking off
        Overflow checking off
        Integer overflow checking off
        RuntimeAttributes -> {}

        T(R2)0 = A1
        I5 = 0
        I0 = 1
        Result = I3

1   I3 = I5
2   I6 = Length[ T(R2)0]
3   I9 = I5
4   goto 10
5   I7 = MainEvaluate[ Hold[cond1][ T(R2)0, I9]]
6   I10 = MainEvaluate[ Hold[cond2][ T(R2)0, I9]]
7   I7 = I7 * I10
8   I10 = I3 + I7
9   I3 = I10
10  if[ ++ I9 <= I6] goto 5
11  Return

As you can see, cond1 and cond2 have not been inlined, defeating the whole purpose of compilation.

If you really want to preevaluate some bits of code at compile time, you can do it like this:

ClearAll[cg]
cg[boolean : True | False] := cg[boolean] = Block[{a, b, c},
    
    c = Sqrt[0.5];
    
    With[{
      code = Boole[(0.7 > b) && (Abs[a] > c ) && (boolean || a b > 0.2)]
      },
     
     Compile[{{TableVals, _Real, 2}},
      Block[{n, sum, a, b},
       
       n = Length[TableVals];
       sum = 0;
       
       Do[
        a = Compile`GetElement[TableVals, i, 1];
        b = Compile`GetElement[TableVals, i, 2];
        sum += code
        , {i, 1, n}
        ];

       sum

       ],
      CompilationTarget -> "C",
      RuntimeOptions -> "Speed"
      ]
     ]
    ];

You then would call it like this:

ch[0.7 > 1.][TableVals]

ch uses memoization to lazily compile a function only for two cases: boolean == True and boolean == False. "Lazily" because ch will invoke only compilation for one of the values when it is really called. I let Mathematica simplify the logical expression in code and inline the simplified result with With. (I was a bit disappointed that the compiler was not able to simplify the expression Boole[(0.7 > b) && (Abs[a] > c ) && (boolean || a b > 0.2)] although boolean is a compile-time constant...)

$\endgroup$

Your Answer

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

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