<html><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space;" class="">When playing around with reassociate I noticed a seemingly obvious optimization that was not getting done anywhere in llvm… nor in gcc or ICC.<br class=""><br class="">Consider the following trivial function:<br class=""><br class="">void foo(int a, int b, int c, int d, int e, int *res) {<br class="">  res[0] = (e * a) * d;<br class="">  res[1] = (e * b) * d;<br class="">  res[2] = (e * c) * d;<br class="">}<br class=""><br class="">This function can be optimized down to 4 multiplies instead of 6 by reassociating such that (e*d) is the common subexpresion. However, no compiler I’ve tested does this. I wrote a slightly hacky heuristic algorithm to augment reassociate to do this and tested it.<div class=""><br class=""></div><div class=""><b class="">First, before the details, the results: on a large offline test suite of graphics shaders it cut down total instruction count by ~0.9% (!) and float math instruction count by 1.5% (!). </b>I ran it on SPEC out of curiosity and the one standout was that with fast-math, it cut down 470.lbm by ~6% runtime (see Note 1 at the bottom for the probable reason why). Not coincidentally, this is probably the SPEC program that looks most like a graphics shader.<div class=""><br class=""></div><div class="">Here’s how it works:<div class=""><br class=""></div><div class="">1. Do reassociate once as normal. Keep track of the linear chains of operations that are saved to the final IR for later.</div><div class="">2. Create a “pair map” consisting of a mapping from <Instr, Instr> to <unsigned>. Have one pair map for each type of BinaryOperation. This map represents how common a given operand pair occurs in the source code for a given BinaryOperation. But in addition to counting each actual instruction, we also count each possible O(N^2) pair of each linear operand chain. So for example, if the operand chain is this:</div><div class=""><br class=""></div><div class="">a*b*c</div><div class=""><br class=""></div><div class="">we do:</div><div class=""><br class=""></div><div class="">PairMap[Multiply][{a, b}]++;</div><div class="">PairMap[Multiply][{b, c}]++;</div><div class="">PairMap[Multiply][{a, c}]++;</div><div class=""><br class=""></div><div class="">Obviously if runtime is an issue we can prohibit this on extremely long chains or such that are unlikely to occur in real programs to avoid asymptotically quadratic behavior.</div><div class="">3. Run reassociate again. All the information is saved from the first time around so hopefully this won’t be very expensive except for the changes we actually make. But this time, whenever emitting a linear operand chain, pick the operand pair that’s *most common* in the source code (using PairMap) and make that one the first operation. Thus, for example:</div><div class=""><br class=""></div><div class="">(((a*b)*c)*d)*e</div><div class=""><br class=""></div><div class="">if “b*e” is the most common, this becomes:</div><div class=""><br class=""></div><div class="">(((b*e)*a)*c)*d</div><div class=""><br class=""></div><div class="">Now b*e can be CSE’d later! Magic!</div><div class=""><br class=""></div><div class="">This could probably be enhanced to allow for multiple operations to be CSE’d, e.g. by doing something like ((b*e)*(a*c))*d, but that would change the canonicalization regime and make it a more invasive change, I think.</div><div class=""><br class=""></div><div class="">Also, as a tiebreaker, the current one I’m using is the “pair which has the lowest max rank of the two operands”, which makes sense because in this example, “a*b” is the first operation in the chain, so we want to pick the duplicates which are also higher up in the program vs closer to the leaf. No other tiebreaker I tried seemed to work as well.</div><div class=""><br class=""></div><div class="">This is not the cleanest implementation or algorithm, but it’s the most straightforward I could come up with and it gives us some unreasonably large perf gains, and of course, optimizes “foo” correctly down to 4 multiplies.</div><div class=""><br class=""></div><div class="">Any thoughts? The gains here are… well, more than I expected, to say the least ;-)</div><div class=""><br class=""></div><div class="">—escha</div><div class=""><br class=""></div><div class="">Note 1: see how (1.0 - u2) is a common factor here.</div><div class="">                DST_C ( dstGrid ) = (1.0-OMEGA)*SRC_C ( srcGrid ) + DFL1*OMEGA*rho*(1.0                                 - u2);<br class="">                DST_N ( dstGrid ) = (1.0-OMEGA)*SRC_N ( srcGrid ) + DFL2*OMEGA*rho*(1.0 +       uy*(4.5*uy       + 3.0) - u2);<br class="">                DST_S ( dstGrid ) = (1.0-OMEGA)*SRC_S ( srcGrid ) + DFL2*OMEGA*rho*(1.0 +       uy*(4.5*uy       - 3.0) - u2);<br class="">                DST_E ( dstGrid ) = (1.0-OMEGA)*SRC_E ( srcGrid ) + DFL2*OMEGA*rho*(1.0 +       ux*(4.5*ux       + 3.0) - u2);</div><div class="">(etc)<br class=""><br class=""><br class=""></div></div></div></body></html>