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
: j
th and j+k
th, 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
.
tab1
and computes for each row some averaged value fromtab2
. I have not found a simpler example without allowing too much simplifications. $\endgroup$