back

by jeffreyrogers·3y ago·view on hn ↗
That's basically the problem the article describes although he's using vector intrinsics too and it still reloads and broadcasts the constant before each loop.
2 comments
There are three reasons it reloads constants:

1. It thinks it is cheaper than keeping them in a register ( this is known as rematerialization). It will reload constants that it lets it keep something else in a register, and it's cheaper to do this.

2. It thinks something could affect the constant.

3. It thinks it must move it through memory to use it, and then it thinks the memory was clobbered.

In this case, it definitely knows it is a constant, and it can't prove that both loops always execute, so it places it in the path where it is only executed once per loop, because it believes it will be cheaper.

I can still make at least gcc do weird things if i prove to it the loop executes once.

In that case, what is happening in gcc is that constant propagation is propagating the vector constant forward into both loops. Something later (that has a machine cost model) is expected to commonize it if it is cheaper, but never does.

You can see it get propagated through as a constant at the high level here: https://godbolt.org/z/jxWKcnTT1

That is normal and what i would expect to happen.

You can see in lower level RTL dumps, nothing chooses to commonize it. That seems a bit weird, since it should have a cost model that says this isn't free.

I believe it's possible to report this as a "missed optimisation" bug.
Yes.

A lot of the intrinsics also look like (at least in gcc), they are marked always inline but not pure/const.

it's probably worthwhile marking those that take memory as pure/const. That way, it's obvious the value is readonly even when it's inlined.

(otherwise, the compiler will have to prove it in the inlined code, which is not always possible)

When I have used intrinsics, the compiler at least has a hope of getting this right, particularly when you use patterns like:

__m256i mask = _mm256_set1_epi8(0x0f)

If you just used the intrinsic that sets the register to a constant over and over, it often repeats the instruction.

The compilers just aren't that smart about SIMD yet.

He sets it once like this before the loops.

        __m256i c = _mm256_set1_epi32(10001);
And then the disassembly has

        mov     eax, 10001
        vpbroadcastd    ymm1, eax
before each loop.
Yeah, if the compiler runs out of registers it will do this - it's better than spilling the constant to memory. Register allocation is one thing that compilers are still much worse at than humans, and you see it in SIMD code a lot.
Eh. Compilers are only worse because we don't care.

Optimal register allocation + spilling is easily doable now on today's machines/algorithms.

We just don't bother because it's not truly worth it in most cases (IE we get most of the performance for a very low cost)