11
votes

Here is a SSCCE:

class Vec final {
    public:
        float data[4];

        inline Vec(void) {}
        inline ~Vec(void) {}
};
Vec operator*(float const& scalar, Vec const& vec) {
    Vec result;
    #if 1
        for (int k=0;k<4;++k) result.data[k]=scalar*vec.data[k];
    #else
        float const*__restrict src =    vec.data;
        float      *__restrict dst = result.data;
        for (int k=0;k<4;++k) dst[k]=scalar*src[k];
    #endif
    return result;
}

int main(int /*argc*/, char* /*argv*/[]) {
    Vec vec;
    Vec scaledf = 2.0f * vec;
    return 0;
}

When compiling, MSVC 2013 informs me (/Qvec-report:2) that

main.cpp(11) : info C5002: loop not vectorized due to reason '1200'

This means that the "[l]oop contains loop-carried data dependences".

I have noticed that commenting either the constructor or the destructor for Vec (edit: or defaulting them, e.g. Vec()=default;) causes it to vectorize successfully. My question: why?


Note: Toggling the #if will also make it work. The __restrict is important.
Note: Changing float const& scalar to float const scalar causes the vectorization to report 1303 (vectorization wouldn't be a win), I suspect because the reference can be passed directly into an SSE register while the pass-by-value needs another copy.

1
What happens if you default (Vec() = default;) the constructor and destructor instead? The way you have them defined right now makes them non-trivial, maybe the optimizer doesn't like that for some reason. - Praetorian
@Praetorian Defaulting at least one of them causes the vectorization to succeed. - imallett
Looks (to me) like if the code for the target type is sufficiently complex, it isn't quite "smart" enough to ensure that you don't have aliasing (i.e., it can't prove that src and dst necessarily refer to distinct objects). - Jerry Coffin
Cautious back-end guys do ignore the const keyword. Write this in the "normal" form and pass the 1st argument as const float scalar instead of passing it by reference. So it can completely dismiss the possibility that scalar is an element in the float[]. - Hans Passant
What happens if you add the noexcept qualifier to the ctor/dtor (as the = default version does) - kfsone

1 Answers

1
votes

Why do you declare an empty non virtual destructor inline ~Vec(void) {} with an empty default constructor inline Vec(void) {}?

As a result the compiler does not generate default copy constructor. Thus the code return result; can't be compiled without it because this requires to copy result into a temporary returned object (that is may not what you want).

Either define a copy constructor, or don't define the empty constructor and destructor at all.