为啥这个自动向量器关心构造函数/析构函数?

Posted

技术标签:

【中文标题】为啥这个自动向量器关心构造函数/析构函数?【英文标题】:Why Does This Auto-Vectorizer Care About Constructors/Destructors?为什么这个自动向量器关心构造函数/析构函数? 【发布时间】:2015-05-15 22:49:16 【问题描述】:

这是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;


编译时,MSVC 2013 通知我 (/Qvec-report:2)

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

这意味着“[l]oop 包含循环携带的数据依赖”。

我注意到注释Vec 的构造函数或析构函数(编辑:或默认它们,例如Vec()=default;)会使其向量化成功。我的问题:为什么?


注意:切换#if 也会使其工作。 __restrict 很重要。 注意:将float const&amp; scalar 更改为float const scalar 会导致矢量化报告1303(矢量化不会成功),我怀疑是因为引用可以直接传递到SSE 寄存器,而传递值需要另一个复制。

【问题讨论】:

如果你默认 (Vec() = default;) 构造函数和析构函数会发生什么?您现在定义它们的方式使它们变得不平凡,也许优化器出于某种原因不喜欢那样。 @Praetorian 默认其中至少一个会导致矢量化成功。 看起来(在我看来)目标类型的代码是否足够复杂,但它不够“智能”以确保您没有别名(即,它不能证明srcdst 必然指向不同的对象)。 谨慎的后端人员会忽略 const 关键字。以“正常”形式编写它并将第一个参数作为const float scalar 传递,而不是通过引用传递它。所以它可以完全排除 scalar 是 float[] 中的元素的可能性。 如果将noexcept 限定符添加到ctor/dtor 会发生什么(就像= default 版本一样) 【参考方案1】:

为什么要声明一个空的非虚拟析构函数inline ~Vec(void) 和一个空的默认构造函数inline Vec(void)

因此编译器不会生成默认的复制构造函数。因此,没有它就无法编译代码return result;,因为这需要将结果复制到一个临时返回的对象中(这可能不是您想要的)。

要么定义一个复制构造函数,要么根本不定义空的构造函数和析构函数。

【讨论】:

空的构造函数/析构函数是因为这个玩具示例的玩具性。此外,它必须生成一个复制构造函数,因为它编译得很好。无论如何,添加inline Vec(Vec const&amp; other) = default; 似乎没有任何效果。

以上是关于为啥这个自动向量器关心构造函数/析构函数?的主要内容,如果未能解决你的问题,请参考以下文章

为啥自动对象的析构函数被调用两次?

析构函数为啥能释放对象内存?

如何在不破坏移动和复制构造函数的情况下声明虚拟析构函数

为啥C++里面,析构函数会被调用两次

为啥析构函数被调用两次而构造函数只被调用一次?

构造函数拷贝构造函数析构函数