Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>Looking at what <code>Compile</code> does to <code>Do</code> loops is instructive. Consider this:</p> <pre><code>L=1200; Do[.7, {i, 1, 2 L}, {j, 1, i}] // Timing Do[.3 + .4, {i, 1, 2 L}, {j, 1, i}] // Timing Do[.3 + .4 + .5, {i, 1, 2 L}, {j, 1, i}] // Timing Do[.3 + .4 + .5 + .8, {i, 1, 2 L}, {j, 1, i}] // Timing (* {0.390163, Null} {1.04115, Null} {1.95333, Null} {2.42332, Null} *) </code></pre> <p>First, it seems safe to assume that <code>Do</code> does not automatically compile its argument if it's over some length (as <code>Map</code>, <code>Nest</code> etc do): you can keep adding constants and the derivative of time taken vs number of constants is constant. This is further supported by the nonexistence of such an option in <code>SystemOptions["CompileOptions"]</code>. </p> <p>Next, since this loops around <code>n(n-1)/2</code> times with <code>n=2*L</code>, so around 3*10^6 times for our <code>L=1200</code>, the time taken for each addition indicates that there is a lot more going on than is necessary.</p> <p>Next let us try</p> <pre><code>Compile[{{L,_Integer}},Do[.7,{i,1,2 L},{j,1,i}]]@1200//Timing Compile[{{L,_Integer}},Do[.7+.7,{i,1,2 L},{j,1,i}]]@1200//Timing Compile[{{L,_Integer}},Do[.7+.7+.7+.7,{i,1,2 L},{j,1,i}]]@1200//Timing (* {0.032081, Null} {0.032857, Null} {0.032254, Null} *) </code></pre> <p>So here things are more reasonable. Let's take a look:</p> <pre><code>Needs["CompiledFunctionTools`"] f1 = Compile[{{L, _Integer}}, Do[.7 + .7 + .7 + .7, {i, 1, 2 L}, {j, 1, i}]]; f2 = Compile[{{L, _Integer}}, Do[2.8, {i, 1, 2 L}, {j, 1, i}]]; CompilePrint[f1] CompilePrint[f2] </code></pre> <p>the two <code>CompilePrint</code>s give the same output, namely,</p> <pre><code> 1 argument 9 Integer registers Underflow checking off Overflow checking off Integer overflow checking on RuntimeAttributes -&gt; {} I0 = A1 I5 = 0 I2 = 2 I1 = 1 Result = V255 1 I4 = I2 * I0 2 I6 = I5 3 goto 8 4 I7 = I6 5 I8 = I5 6 goto 7 7 if[ ++ I8 &lt; I7] goto 7 8 if[ ++ I6 &lt; I4] goto 4 9 Return </code></pre> <p><code>f1==f2</code> returns <code>True</code>.</p> <p>Now, do</p> <pre><code>f5 = Compile[{{L, _Integer}}, Block[{t = 0.}, Do[t = Sin[i*j], {i, 1, 2 L}, {j, 1, i}]; t]]; f6 = Compile[{{L, _Integer}}, Block[{t = 0.}, Do[t = Sin[.45], {i, 1, 2 L}, {j, 1, i}]; t]]; CompilePrint[f5] CompilePrint[f6] </code></pre> <p>I won't show the full listings, but in the first there is a line <code>R3 = Sin[ R1]</code> while in the second there is an assignment to a register <code>R1 = 0.43496553411123023</code> (which, however, is reassigned in the innermost part of the loop by <code>R2 = R1</code>; perhaps if we output to C this will be optimized by gcc eventually).</p> <p>So, in these very simple cases, uncompiled <code>Do</code> just blindly executes the body without inspecting it, while <code>Compile</code> does do various simple optimizations (in addition to outputing byte code). While here I am choosing examples that exaggerate how literally <code>Do</code> interprets its argument, this kind of thing partly explains the large speedup after compiling.</p> <p>As for the <a href="https://stackoverflow.com/questions/6853928/more-efficient-way-of-calculating-this-recurrence-relation-in-mathematica/6865423#6865423">huge speedup in Mike Bantegui's question yesterday</a>, I think the speedup in such simple problems (just looping and multiplying things) is because there is no reason that automatically produced C code can't be optimized by the compiler to get things running as fast as possible. The C code produced is too hard to understand for me, but the bytecode is readable and I don't think that there is anything all that wasteful. So it is not that shocking that it is so fast when compiled to C. Using built-in functions shouldn't be any faster than that, since there shouldn't be any difference in the algorithm (if there is, the <code>Do</code> loop shouldn't have been written that way). </p> <p>All this should be checked case by case, of course. In my experience, <code>Do</code> loops usually are the fastest way to go for this kind of operation. However, compilation has its limits: if you are producing large objects and trying to pass them around between two compiled functions (as arguments), the bottleneck can be this transfer. One solution is to simply put everything into one giant function and compile that; this ends up being harder and harder to do (you are forced to write C in mma, so to speak). Or you can try compiling the individual functions and using <code>CompilationOptions -&gt; {"InlineCompiledFunctions" -&gt; True}]</code> in the <code>Compile</code>. Things can get tricky very fast, though.</p> <p>But this is getting too long. </p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload