Eigen: Coding style's effect on performance
<p>From what I've read about Eigen (<a href="https://eigen.tuxfamily.org/dox/TopicInsideEigenExample.html">here</a>), it seems that <code>operator=()</code> acts as a "barrier" of sorts for lazy evaluation -- e.g. it causes Eigen to stop returning expression templates and actually perform the (optimized) computation, storing the result into the left-hand side of the <code>=</code>.</p>

<p>This would seem to mean that one's "coding style" has an impact on performance -- i.e. using named variables to store the result of intermediate computations might have a negative effect on performance by causing some portions of the computation to be evaluated "too early".</p>

<p>To try to verify my intuition, I wrote up an example and was surprised at the results (<a href="https://gist.github.com/jeremytrimble/2be79f0203f295b515ae4578ea956ebc">full code here</a>):</p>

<pre><code>using ArrayXf  = Eigen::Array &lt;float, Eigen::Dynamic, Eigen::Dynamic&gt;;
using ArrayXcf = Eigen::Array &lt;std::complex&lt;float&gt;, Eigen::Dynamic, Eigen::Dynamic&gt;;

float test1( const MatrixXcf &amp; mat )
{
    ArrayXcf arr  = mat.array();
    ArrayXcf conj = arr.conjugate();
    ArrayXcf magc = arr * conj;
    ArrayXf  mag  = magc.real();
    return mag.sum();
}

float test2( const MatrixXcf &amp; mat )
{
    return ( mat.array() * mat.array().conjugate() ).real().sum();
}

float test3( const MatrixXcf &amp; mat )
{
    ArrayXcf magc   = ( mat.array() * mat.array().conjugate() );

    ArrayXf mag     = magc.real();
    return mag.sum();
}
</code></pre>

<p>The above gives 3 different ways of computing the coefficient-wise sum of magnitudes in a complex-valued matrix.</p>

<ol>
<li><code>test1</code> sort of takes each portion of the computation "one step at a time."</li>
<li><code>test2</code> does the whole computation in one expression.</li>
<li><code>test3</code> takes a "blended" approach -- with some amount of intermediate variables.</li>
</ol>

<p>I sort of expected that since <code>test2</code> packs the entire computation into one expression, Eigen would be able to take advantage of that and globally optimize the entire computation, providing the best performance.</p>

<p>However, the results were surprising (numbers shown are in total microseconds across 1000 executions of each test):</p>

<pre><code>test1_us: 154994
test2_us: 365231
test3_us: 36613
</code></pre>

<p>(This was compiled with g++ -O3 -- see <a href="https://gist.github.com/jeremytrimble/2be79f0203f295b515ae4578ea956ebc">the gist</a> for full details.)</p>

<p>The version I expected to be fastest (<code>test2</code>) was actually slowest.  Also, the version that I expected to be slowest (<code>test1</code>) was actually in the middle.</p>

<p>So, my questions are:</p>

<ol>
<li>Why does <code>test3</code> perform so much better than the alternatives?</li>
<li>Is there a technique one can use (short of diving into the assembly code) to get some visibility into how Eigen is actually implementing your computations?</li>
<li>Is there a set of guidelines to follow to strike a good tradeoff between performance and readability (use of intermediate variables) in your Eigen code?</li>
</ol>

<p>In more complex computations, doing everything in one expression could hinder readability, so I'm interested in finding the right way to write code that is both readable and performant.</p>