1
$\begingroup$

Consider the following Switch function for two variables:

IPcut = 25.;
IPcutID1[id1_, id2_] := 
 Switch[{id1, id2}, {11., -11.}, IPcut, {-11., 11.}, 
  IPcut, {-11., 13.}, IPcut, {13., -11.}, IPcut, {11., -13.}, 
  IPcut, {-13., 11.}, IPcut, {11., 211.}, IPcut, {211., 11.}, 
  IPcut, {-11., -211.}, IPcut, {-211., -11.}, IPcut, {11., 321.}, 
  IPcut, {321., 11.}, IPcut, {-11., -321.}, IPcut, {-321., -11.}, 
  IPcut, {11., 2.}, IPcut, {2., 11.}, IPcut, {-11., -2.}, 
  IPcut, {-2., -11.}, IPcut, {11., 21.}, IPcut, {21., 11.}, 
  IPcut, {-11., -21.}, IPcut, {-21., -11.}, IPcut, {-13., 13.}, 
  IPcut, {13., -13.}, IPcut, {13., 211.}, IPcut, {211., 13.}, 
  IPcut, {-13., -211.}, IPcut, {-211., -13.}, IPcut, {13., 321.}, 
  IPcut, {321., 13.}, IPcut, {-13., -321.}, IPcut, {-321., -13.}, 
  IPcut, {13., 2.}, IPcut, {2., 13.}, IPcut, {-13., -2.}, 
  IPcut, {-2., -13.}, IPcut, {13., 21.}, IPcut, {21., 13.}, 
  IPcut, {-13., -21.}, IPcut, {-21., -13.}, IPcut, {-211., 211.}, 
  IPcut, {211., -211.}, IPcut, {-211., 321.}, IPcut, {321., -211.}, 
  IPcut, {211., -321.}, IPcut, {-321., 211.}, IPcut, {-211., 2.}, 
  IPcut, {2., -211.}, IPcut, {211., -2.}, IPcut, {-2., 211.}, 
  IPcut, {-211., 21.}, IPcut, {21., -211.}, IPcut, {211., -21.}, 
  IPcut, {-21., 211.}, IPcut, {-321., 321.}, IPcut, {321., -321.}, 
  IPcut, {321., -2.}, IPcut, {-2., 321.}, IPcut, {-321., 2.}, 
  IPcut, {2., -321.}, IPcut, {321., -21.}, IPcut, {-21., 321.}, 
  IPcut, {-321., 21.}, IPcut, {21., -321.}, IPcut, {2., -2.}, 
  IPcut, {-2., 2.}, IPcut, {2., -21.}, IPcut, {-21., 2.}, 
  IPcut, {-2., 21.}, IPcut, {21., -2.}, IPcut, {21., -21.}, 
  IPcut, {-21., 21.}, IPcut, {22., 22.}, IPcut, {130., 130.}, 
  IPcut, {130., 22.}, IPcut, {22., 130.}, IPcut, _, 0]

Next, consider some test data:

IPrand = RandomReal[{0, 1000}, {10^7, 2}];
pairs = RandomInteger[{0, 500}, {100, 2}] // N;

Next, consider some test routine:

routine1 = 
 Hold@Compile[{{IPrand, _Real, 1}, {j, _Integer}, {pairs, _Real, 2}}, 
      Module[{ips, id1, id2, ipcut, count, len},
       count = 0.;
       ips = Compile`GetElement[IPrand, 1];
       len = Length[pairs];
       id1 = Compile`GetElement[pairs, j, 1];
       id2 = Compile`GetElement[pairs, j, 2];
       ipcut = IPcutID1[id1, id2](*(id1+id2)*);
       Do[
        count += Boole[ips > ipcut], {i, 1, len, 1}];
       count
       ], CompilationTarget -> "C", RuntimeOptions -> "Speed", 
      RuntimeAttributes -> {Listable}] /. DownValues@IPcutID1 /. 
   OwnValues@IPcut // ReleaseHold
<< CompiledFunctionTools`
CompilePrint@routine1;

where in this string:

ipcut = IPcutID1[id1, id2](*(id1 + id2)*);

we may comment either id1 + id2 or IPcutID1[id1, id2]. CompilePrint does not show MainEvaluate, but a lot of CompareTensor calls. The latter may suggest that IPcutID1[id1, id2] is a very dumb realization.

However, my stupid expectation was that IPcutID1[id1, id2] evaluates only once, and it does not care whether IPcutID1 is fast or slow. These expectations failed though, as the runtime with uncommented IPcutID1[id1, id2] is 20 times larger than with commented IPcutID1[id1, id2], which suggests that it evaluates many times.

Could you please tell me how (without modifying the structure of routine1) to evaluate it really once still inside Module, or to optimize the initial function IPcutID1?

Edit

Following the suggestion of MichaelE2, I rewrote IPcutID1 as

IPcutID2[id1_, id2_] := 
 Switch[id1, 11., 
  Switch[id2, -11., IPcut, -13., IPcut, 211., IPcut, 321., IPcut, 2., 
   IPcut, 21., IPcut, _, 0], -11., 
  Switch[id2, 11., IPcut, 13., IPcut, -211., IPcut, -321., IPcut, -2.,
    IPcut, -21., IPcut, _, 0], 13., 
  Switch[id2, -11., IPcut, -13., IPcut, 211., IPcut, 321., IPcut, 2., 
   IPcut, 21., IPcut, _, 0], -13., 
  Switch[id2, 11., IPcut, 13., IPcut, -211., IPcut, -321., IPcut, -2.,
    IPcut, -21., IPcut, _, 0], 211., 
  Switch[id2, 11., IPcut, 13., IPcut, -211., IPcut, -321., IPcut, -2.,
    IPcut, -21., IPcut, _, 0], -211., 
  Switch[id2, -11., IPcut, -13., IPcut, 211., IPcut, 321., IPcut, 2., 
   IPcut, 21., IPcut, _, 0], 2., 
  Switch[id2, 11., IPcut, 13., IPcut, -211., IPcut, -321., IPcut, -2.,
    IPcut, -21., IPcut, _, 0], -2., 
  Switch[id2, -11., IPcut, -13., IPcut, 211., IPcut, 321., IPcut, 2., 
   IPcut, 21., IPcut, _, 0], 21., 
  Switch[id2, 11., IPcut, 13., IPcut, -211., IPcut, -321., IPcut, -2.,
    IPcut, -21., IPcut, _, 0], -21., 
  Switch[id2, -11., IPcut, -13., IPcut, 211., IPcut, 321., IPcut, 2., 
   IPcut, 21., IPcut, _, 0], 130., 
  Switch[id2, 130., IPcut, 22., IPcut, _, 0], 22., 
  Switch[id2, 130., IPcut, 22., IPcut, _, 0], _, 0]

This function is much faster. However, it is still interesting for me how to call the function once.

$\endgroup$
1
  • 1
    $\begingroup$ That sure is some ugly code you have there $\endgroup$
    – Jason B.
    May 20, 2023 at 11:14

1 Answer 1

3
$\begingroup$

I don't understand the purpose of all this. And frankly, I am quite confused by your elaborate way of code injection (which IMHO just asks for trouble). So I might get something wrong. But why don't you just use Integers for pairs?

routine2 = With[{IPcut = 25},
   Compile[{{IPrand, _Real, 1}, {j, _Integer}, {pairs, _Integer, 2}},
    Module[{ips, id1, id2, ipcut, count, len},
     count = 0;
     ips = Compile`GetElement[IPrand, 1];
     len = Length[pairs];
     id1 = Compile`GetElement[pairs, j, 1];
     id2 = Compile`GetElement[pairs, j, 2];
     
     ipcut = Switch[
       id1,
       11,
       Switch[id2, -11, IPcut, -13, IPcut, 211, IPcut, 321, IPcut, 2, IPcut, 21, IPcut, _, 0],
       -11,
       Switch[id2, 11, IPcut, 13, IPcut, -211, IPcut, -321, IPcut, -2, IPcut, -21, IPcut, _, 0],
       13,
       Switch[id2, -11, IPcut, -13, IPcut, 211, IPcut, 321, IPcut, 2, IPcut, 21, IPcut, _, 0],
       -13,
       Switch[id2, 11, IPcut, 13, IPcut, -211, IPcut, -321, IPcut, -2, IPcut, -21, IPcut, _, 0],
       211,
       Switch[id2, 11, IPcut, 13, IPcut, -211, IPcut, -321, IPcut, -2, IPcut, -21, IPcut, _, 0],
       -211,
       Switch[id2, -11, IPcut, -13, IPcut, 211, IPcut, 321, IPcut, 2, IPcut, 21, IPcut, _, 0],
       2,
       Switch[id2, 11, IPcut, 13, IPcut, -211, IPcut, -321, IPcut, -2, IPcut, -21, IPcut, _, 0],
       -2,
       Switch[id2, -11, IPcut, -13, IPcut, 211, IPcut, 321, IPcut, 2, IPcut, 21, IPcut, _, 0],
       21,
       Switch[id2, 11, IPcut, 13, IPcut, -211, IPcut, -321, IPcut, -2, IPcut, -21, IPcut, _, 0],
       -21,
       Switch[id2, -11, IPcut, -13, IPcut, 211, IPcut, 321, IPcut, 2, IPcut, 21, IPcut, _, 0],
       130,
       Switch[id2, 130, IPcut, 22, IPcut, _, 0],
       22,
       Switch[id2, 130, IPcut, 22, IPcut, _, 0],
       _, 
       0
       ];
     
     Do[count += Boole[ips > N[ipcut]], {i, 1, len, 1}];
     
     count
     ],
    CompilationTarget -> "C",
    RuntimeOptions -> "Speed",
    RuntimeAttributes -> {Listable}
    ]
   ];

Then this runs much faster:

IPrand = RandomReal[{0, 1000}, {10^7, 2}];
pairs = RandomInteger[{0, 500}, {100, 2}];
a1 = routine1[IPrand, 1, pairs]; // AbsoluteTiming // First
a2 = routine2[IPrand, 1, pairs]; // AbsoluteTiming // First
a1 == a2

3.5444

0.053489

True

This might have to do with the fact that the outcome is pretty boring:

MinMax[a2]

{100, 100}

So the C compiler might have been able to optimize away all logic at compile time.

Another potential reason why this is so much faster: The C compiler is proablaby able to build a jump table or some other fast lookup structure if all the cases in the Switchs are of integral type. (I have not checked the generated assembler code, though.)

Btw.: Mathematica does not convert Switch to C' s switch, but the compiler may generate a jump table nonetheless.

$\endgroup$

Your Answer

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

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