Live data from Hacker News

Why does this code execute more slowly after strength-reducing multiplications?

stackoverflow.com

51–60 of 155 posts

Re: Why does this code execute more slowly after strength-reducing multiplications?

#51
post #22

In the post, multiplications and 2 additions are not faster than 2 additions. The post compares (1) loop code that can be vectorized, as loop rounds are independent and do not depend on the result from the previous round, and (2) an "optimization" that makes calculations shorter, but also makes each loop round depend on the result of the previous round, so this cannot be vectorized.

SIMD vectorization is like quantum mechanics, it becomes classical if you look.

Re: Why does this code execute more slowly after strength-reducing multiplications?

#52

Earlier quoted context omitted.

> In the post, multiplications and 2 additions are not faster than 2 additions. Reminded me of when I tried to optimize some code using assembly on x86, maybe 8-10 years go. The compiler spit out a DIV [EDI] instruction which was inside a hot loop. I saw how I could easily rewrite it to use a register instead of the indirect memory access. So I did, and it was significantly slower, like 10-15%... I even took care to…

You probably added an extra instruction or two to put the value in a register? The CPU can already split memory accesses into uops and cached accesses are fast, so there's no point in doing that because it'll just waste an additional register (vs. using one of the many the renamer generates) and add instructions to decode. x86 is fundamentally a CISC; if you treat it like a RISC, it will definitely disappoint.

Cached accesses are not fast anymore. Modern chips can retire a dozen or even more instructions in the time it takes to move a word in from L1 cache.

Re: Why does this code execute more slowly after strength-reducing multiplications?

#53
post #44

Earlier quoted context omitted.

> The second code can probably become SIMD as well, but it's beyond GCC's ability to autovectorizer it in that form. Usually, for floating point operations the compiler simply has no chance to do anything clever. NaNs, infinities and signed zero mean that even the most "obvious" identities don't actually hold. For example, x + 0 == x (where the == is bitwise) does not hold for x = -0.

> For example, x + 0 == x (where the == is bitwise) does not hold for x = -0. But when operating on floating point numbers, == is not bitwise, and -0 == +0.

That doesn’t matter for the case where a compiler would like to replace x + y by x because it knows y == 0.

The compiler would have to test for the x == -0 case, and doing that ¿rarely/never? is faster than computing x + y.

Re: Why does this code execute more slowly after strength-reducing multiplications?

#54
post #35

Earlier quoted context omitted.

> The second code can probably become SIMD as well, but it's beyond GCC's ability to autovectorizer it in that form. Usually, for floating point operations the compiler simply has no chance to do anything clever. NaNs, infinities and signed zero mean that even the most "obvious" identities don't actually hold. For example, x + 0 == x (where the == is bitwise) does not hold for x = -0.

so x+0 is 0?

No. -0.0 + 0 is not necessarily bitwise-equal to 0.0. Though it arguably could be, depending on details.

Re: Why does this code execute more slowly after strength-reducing multiplications?

#55

Earlier quoted context omitted.

You probably added an extra instruction or two to put the value in a register? The CPU can already split memory accesses into uops and cached accesses are fast, so there's no point in doing that because it'll just waste an additional register (vs. using one of the many the renamer generates) and add instructions to decode. x86 is fundamentally a CISC; if you treat it like a RISC, it will definitely disappoint.

Not really, the compiler did the round-trip into the local variable (memory), I did it via register. I asked around at the time and someone mentioned that I might have overtaxed certain execution ports or something like that, but yeah that just cemented my belief that x86 optimization is not my cup of tea anymore. Better to spend time learning how to write code the compiler can optimize well.

What made me give up was when I found that an optimization for one x86 processor was an impairment on another. Your DIV example might have had a different outcome on an AMD chip, or on the next- or previous-generation part from Intel.

Nowadays, counting cycles is a game for monks who don't actually have to get anything done.

Re: Why does this code execute more slowly after strength-reducing multiplications?

#57

Tldr: autovectorizer transforms the first code into SIMD instructions, but the second into 64 bit instructions. SIMD is very powerful, and modern compilers can sometimes simd-ify your code automatically. ------- The second code can probably become SIMD as well, but it's beyond GCC's ability to autovectorizer it in that form. I kinda want to give it a go myself but don't have time today... Autovectorizers are mysterio…

The problem is not the ability of the compiler, is that it's not numerically equivalent, so the compiler isn't allowed to do that optimization.

Re: Why does this code execute more slowly after strength-reducing multiplications?

#58
post #9

Earlier quoted context omitted.

How would the second approach be vectorized given each iteration's input has dependence on previous iteration's output??

Unroll the dependency until you are longer than the SIMD width. Ex: as long as i, i+1, i+2, i+3, ... i+7 are not dependent on each other, you can vectorize to SIMD-width 8. Or in other words: i+7 can depend on i-1 no problems.

If they were integer variables, I guess the compiler would have done that, but you can't really do that with floats because i+A+A is not necessarily i+2*A. (Of course, in this particular example, the difference doesn't matter for the programmer, but the compiler doesn't know that!)

I think there's some gcc option that enables these "dangerous" optimizations. -ffast-math, or something like that?

Re: Why does this code execute more slowly after strength-reducing multiplications?

#59

Earlier quoted context omitted.

You probably added an extra instruction or two to put the value in a register? The CPU can already split memory accesses into uops and cached accesses are fast, so there's no point in doing that because it'll just waste an additional register (vs. using one of the many the renamer generates) and add instructions to decode. x86 is fundamentally a CISC; if you treat it like a RISC, it will definitely disappoint.

Not really, the compiler did the round-trip into the local variable (memory), I did it via register. I asked around at the time and someone mentioned that I might have overtaxed certain execution ports or something like that, but yeah that just cemented my belief that x86 optimization is not my cup of tea anymore. Better to spend time learning how to write code the compiler can optimize well.

The compiler doesn’t know anything about optimizing x86 code either. The actual details there are too secret for Intel to want to accurately describe them in gcc, they’re different across different CPUs, and compilers just aren’t as good as you think they are.

(But CPUs are usually better than you think they are.)

Re: Why does this code execute more slowly after strength-reducing multiplications?

#60

Tldr: autovectorizer transforms the first code into SIMD instructions, but the second into 64 bit instructions. SIMD is very powerful, and modern compilers can sometimes simd-ify your code automatically. ------- The second code can probably become SIMD as well, but it's beyond GCC's ability to autovectorizer it in that form. I kinda want to give it a go myself but don't have time today... Autovectorizers are mysterio…

How can one go about making one's code apt for a compiler to be able to do these kinds of things?

Don’t bother. Autovectorization almost fundamentally doesn’t work.

You can write code using explicit vectors and it’s not too bad, though less cross platform than you’d like.

An actual answer involves things like restrict and alignment keywords.

Post reply on HN