On 10/13/12 6:34 PM, Marcus Brubaker wrote:
> On Sat, Oct 13, 2012 at 6:22 PM, Ben Goodrich <
goodri...@gmail.com <mailto:
goodri...@gmail.com>> wrote:
>
> ...I pointed this out to Bob many months ago, and he claimed that the C-style for loops were essentially
> what the compiler would produce for the equivalent Eigen template expression. But, from my (limited) understanding of
>
>
http://eigen.tuxfamily.org/dox/TopicInsideEigenExample.html
>
> this is not correct.
>
> They go into exhaustive detail about how Eigen (can) take aligned floating-point numbers and loop over packets of 4
> values, rather than values. In particular, to add vectors v and w of length 50 (4 * 12 + 2) and assign them to the
> vector u, their expression templates ultimately yield code like
>
> for(int i = 0; i < 4*(size/4); i+=4) u.packet(i) = v.packet(i) + w.packet(i);
>
> for(int i = 4*(size/4); i < size; i++) u[i] = v[i] + w[i];
Very cool. I retract my guess.
The above is for floats (32 bytes); doubles only do
two at a time instead of four. And it looks like
we'd need 16-byte alignment (128 bits). It looks like
Eigen does that under the hood for us.
That page was really useful in understanding other aspects
of how Eigen works. Thanks for pointing it out.
> But in Stan, there are many places where we are "Doing the C Thing" instead of "Doing the Eigen Thing".
It'd be great to fix this.
I only did it this way because I found it simpler
and didn't understand that they had deeper vectorization
inside.
> To take a
> simple example, starting at line 1007 of src/stan/math/matrix.hpp, we have
>
> |
> template <typename T>
> inline T sum(const std::vector<T>& xs) {
> if (xs.size() == 0) return 0;
> T sum(xs[0]);
> for (size_t i = 1; i < xs.size(); ++i)
> sum += xs[i];
> return sum;
> }
> |
>
> which, if I understanding the packet-aligning business that Eigen does (and have the Map / Array syntax right) should be
>
> |
> template <typename T>
> inline T sum(const std::vector<T>& xs) {
> return Eigen::Map< Eigen::Array<T, Dynamic, 1> >(&xs[0], xs.size(), 1).sum();
> }
> |
>
> And there are lots of C-style loops in Stan. But I really don't understand compilers or vectorization in the
> computer-science sense, so if I am wrong, can someone tell me why?
Nobody does! It's a rather empirical art from with a basis
partly in compilers and partly in CPU/bus/memory architecture.
The main point is that the CPU can do many operations in
parallel and it can prefetch data IF it can organize the data
into chunks properly and it can predict where it's going.
This book was really useful and I found it fun and it's
free online:
http://www.agner.org/optimize/optimizing_cpp.pdf
For example, in the Eigen-style code, you wind up constructing
new objects on the stack. Their constructors aren't free,
but they're relatively pretty cheap and should be dwarfed by
the vectorization win for larger vectors. Also, using two
loops can be worse than one or better -- sometimes the
compiler will itself do vectorization using the kind of stride
plus remainder business you saw in Eigen.
Partly it depends on context.
It also hugely changes over time as the relative speeds of
CPU, cache, memory and the availability of new instruction
sets, register organizations, etc. emerges.
I wasn't worrying too much about these issues because of
the following point from Marcus:
> You're not wrong, most of those loops will be slower than Eigens code. Sometimes compilers can be clever and use SIMD
> instructions automatically, but that is hard and doesn't always work depending on how the loop is written. That said, I
> expect that the speedup of replacing every loop like that with an appropriate call to Eigen would be relatively small
> for most models because the overhead cost is going to be dominated by computations with vars which would be unaffected.
This is why the unfolding of the auto-dif in the
matrix multiplies and in the probability function
code is such a big deal for speed.
The best way to get more speed in the other ops is to add
more analytic derivatives implemented in single chain()
functions. And then pulling out a double-based
matrix or vector to send to Eigen. At that point, all
the Eigen expression templates and vectorization should
help.
- Bob