3
$\begingroup$

Consider the following two codes - compiledcode and compiledcode1:

tab1 = RandomReal[{0, 1}, {10^4, 2}];
tab2 = RandomReal[{0, 1}, {10^3, 68}];

compiledcode = 
  Compile[{{tab1, _Real, 1}, {tab2, _Real, 2}}, 
   Module[{acc = 0., val1, val2, val3 = 0., len = Length[tab2], 
     len1 = Length[tab2[[1]]], val4 = 0., cond = 0., condval3 = 0., 
     condval4 = 0.}, val1 = Compile`GetElement[tab1, 1];
    val2 = Compile`GetElement[tab1, 2];
    Do[cond = 0.;
     Do[val3 = val1*val2*Compile`GetElement[tab2, i, j];
      condval3 = Boole[val3*val2 > 0.6];
      If[condval3 == 1., Do[val4 = Compile`GetElement[tab2, i, j + k];
         condval4 = Boole[val4*val2 > 0.6];
         If[condval4 == 1., cond = Boole[val4*val3 > 0.6]];
         If[cond == 1., Break[]];, {k, 1, len1 - j}];];
      If[cond == 1., Break[]];, {j, 1, len1 - 1, 1}];
     acc += cond;, {i, 1, len}];
    acc/len], CompilationTarget -> "C", RuntimeOptions -> "Speed", 
   RuntimeAttributes -> {Listable}, Parallelization -> True];
compiledcode1 = 
  Compile[{{tab1, _Real, 1}, {tab2, _Real, 2}}, 
   Module[{acc = 0., val1, val2, val3 = 0., len = Length[tab2], 
     len1 = Length[tab2[[1]]], val4 = 0., cond = 0., condval3 = 0., 
     condval4 = 0., condval4Array, condval4Array1}, 
    condval4Array = Table[1., len1];
    val1 = Compile`GetElement[tab1, 1];
    val2 = Compile`GetElement[tab1, 2];
    Do[cond = 0.;
     condval4Array1 = condval4Array;
     Do[
      If[Compile`GetElement[condval4Array1, j] == 1., 
       val3 = val1*val2*Compile`GetElement[tab2, i, j];
       condval3 = Boole[val3*val2 > 0.6];
       If[condval3 == 1.,
        Do[
          val4 = Compile`GetElement[tab2, i, j + k];
          condval4 = Boole[val4*val2 > 0.6];
          If[condval4 == 0., condval4Array1[[j + k]] = 0.];
          If[condval4 == 1., cond = Boole[val4*val3 > 0.6]];
          If[cond == 1., Break[]];
          , {k, 1, len1 - j}];];
       If[cond == 1., Break[]];]
      , {j, 1, len1 - 1, 1}];
     acc += cond;, {i, 1, len}];
    acc/len], CompilationTarget -> "C", RuntimeOptions -> "Speed", 
   RuntimeAttributes -> {Listable}, Parallelization -> True];

They both iterate over possible column combinations of tab2: jth and j+kth, with k > 0, and evaluate some condition only if the first and then the second loop iteration meets some conditions condval3 and condval4 correspondingly (they are the same for both loops).

The difference is that compiledcode1 calls multiple times the list condval4array1, whose purpose is memorizing the values of condval4, such that there is no need to iterate over j for which condval4 = 0.

Of course, for this toy example, compiledcode1 is slower due to multiple using of CopyTensor:

pt = compiledcode[tab1, tab2]; // RepeatedTiming // First
pt1 = compiledcode1[tab1, tab2]; // RepeatedTiming // First
pt==pt1

0.37

0.65

True

However, in my real case, I am dealing with much more time-consuming evaluations, and it becomes crucial not to iterate if it is possible. With the implementation as in compiledcode1, the "memoized" version of the compiled code becomes slightly faster than the "non-memoized".

Is it possible to further improve the memoization algorithm to reach an even better speedup?

Edit

I made a simple improvement motivated by the fact that the most time-consuming action is CopyTensor: the string condval4Array1 = condval4Array; is evaluated only if there has been at least one change in condval4Array1 inside the loop:

compiledcode2 = 
  Compile[{{tab1, _Real, 1}, {tab2, _Real, 2}}, 
   Module[{acc = 0., val1, val2, val3 = 0., len = Length[tab2], 
     len1 = Length[tab2[[1]]], val4 = 0., cond = 0., condval3 = 0., 
     condval4 = 0., condval4Array, condval4Array1, jth = 0},
    condval4Array = condval4Array1 = Table[1., len1];
    val1 = Compile`GetElement[tab1, 1];
    val2 = Compile`GetElement[tab1, 2];
    Do[cond = 0.;
     If[jth > 0, condval4Array1 = condval4Array];
     jth = 0;
     Do[
      If[Compile`GetElement[condval4Array1, j] == 1., 
       val3 = val1*val2*Compile`GetElement[tab2, i, j];
       condval3 = Boole[val3*val2 > 0.6];
       If[condval3 == 1.,
        Do[
          val4 = Compile`GetElement[tab2, i, j + k];
          condval4 = Boole[val4*val2 > 0.6];
          If[condval4 == 0., jth += 1; condval4Array1[[j + k]] = 0.];
          If[condval4 == 1., cond = Boole[val4*val3 > 0.6]];
          If[cond == 1., Break[]];
          , {k, 1, len1 - j}];];
       If[cond == 1., Break[]];]
      , {j, 1, len1 - 1, 1}];
     acc += cond;, {i, 1, len}];
    acc/len], CompilationTarget -> "C", RuntimeOptions -> "Speed", 
   RuntimeAttributes -> {Listable}, Parallelization -> True];

It is faster than compiledcode1:

t = t1 = t2 = {};
Do[
 tab1 = RandomReal[{0, 1}, {10^4, 2}];
 tab2 = RandomReal[{0, 1}, {10^3, 68}];
 pt = compiledcode[tab1, tab2] // RepeatedTiming // First;
 pt1 = compiledcode1[tab1, tab2] // RepeatedTiming // First;
 pt2 = compiledcode2[tab1, tab2] // RepeatedTiming // First;
 (*pt==pt1\[Equal]pt2*)
 t = Join[t, {pt}];
 t1 = Join[t1, {pt1}];
 t2 = Join[t2, {pt2}];
 , {i, 1, 10, 1}]
{t // Mean, t1 // Mean, t2 // Mean}

{0.42, 0.60, 0.53}

Now, in my real case, the speedup becomes significant. However, still, I am wondering whether there exists another way of memorizing inside Compile that does not involve CopyTensor.

$\endgroup$
3
  • $\begingroup$ Is this really the minimal working example for your problem? $\endgroup$
    – Domen
    Commented Dec 27, 2023 at 14:50
  • $\begingroup$ @Domen : yes, I think so. It deals with a mother table tab1 and computes for each row some averaged value from tab2. I have not found a simpler example without allowing too much simplifications. $\endgroup$ Commented Dec 27, 2023 at 15:01
  • $\begingroup$ @Domen : the real case also does not use just the elements of the tables, it instead computes some quantities which enter the Boolean conditions. $\endgroup$ Commented Dec 27, 2023 at 15:06

0

Your Answer

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