C++ double*bool乘法有多快,可以矢量化吗?

C++ double*bool乘法有多快,可以矢量化吗?,c++,vectorization,C++,Vectorization,我将常数向量乘以不同的向量很多次。我想知道这有多快,先把它转换成向量,这样就可以使用sse,不是更快吗 void applyMask(std::vector<double>& frame, const std::vector<bool>& mask) { std::transform(frame.begin(), frame.end(), mask.begin(), frame.begin(), [](const doubl

我将常数
向量
乘以不同的
向量
很多次。我想知道这有多快,先把它转换成向量,这样就可以使用sse,不是更快吗

    void applyMask(std::vector<double>& frame, const std::vector<bool>& mask)
    {
        std::transform(frame.begin(), frame.end(), mask.begin(), frame.begin(), [](const double& x, const bool& m)->double{ return x*m;});
    }
void applyMask(std::vector&frame,const std::vector&mask)
{
std::transform(frame.begin(),frame.end(),mask.begin(),frame.begin(),[](constdouble&x,constbool&m)->double{return x*m;});
}

似乎您正试图使用
向量
的掩码将
向量
的部分归零

目前,它是不可矢量化的。此外,
vector
模板专门化将阻碍编译器执行任何类型的自动向量化

所以你基本上有两个选择:

简单的方法是将
向量
转换为相应的0和1的
向量。然后问题简化为相同数据类型的向量到向量乘法,这是完全可向量化的。(甚至可以自动矢量化)

更难的方法(可能更快)是使用或内部函数/指令进行一些破解。但这需要做更多的工作,因为您必须手动对代码进行矢量化



我建议你走简单的路。除非您确实需要,否则无需深入手动矢量化。

我尝试了这两种方法,您的功能如您的问题所示,这一种:

void applyMask(std::vector<double>& frame, const std::vector<bool>& mask)
{
    std::transform(frame.begin(), frame.end(), mask.begin(), frame.begin(), [](const double& x, const bool& m)->double{ return m?x:0.0;});
}
这是大约22条指令。
jne
是循环分支。它重复了8次,因为循环被展开了那么多次。这也是为什么我说“大约22条指令”。它将根据重复的次数而变化

  • 三值运算符

    在本例中,我们选择布尔值为true时的值。这增加了一个分支,这意味着代码可能以不同的速度运行,这取决于有多少标志是真或假

     a83:       83 c1 01                add    $0x1,%ecx
     a86:       ba 01 00 00 00          mov    $0x1,%edx
     a8b:       48 d3 e2                shl    %cl,%rdx
     a8e:       48 85 10                test   %rdx,(%rax)
     a91:       66 0f ef c0             pxor   %xmm0,%xmm0
     a95:       74 05                   je     a9c <main+0xbc>
     a97:       f2 0f 10 45 08          movsd  0x8(%rbp),%xmm0
     a9c:       83 f9 3f                cmp    $0x3f,%ecx
     a9f:       f2 0f 11 45 08          movsd  %xmm0,0x8(%rbp)
     aa4:       48 8d 55 10             lea    0x10(%rbp),%rdx
     aa8:       0f 84 d4 01 00 00       je     c82 <main+0x2a2>
    
    这是8个说明!我们看不到树枝。但这是优化的一部分。应该至少有一个分支,所以应该是9条指令

    看来神秘的找到了正确的答案。不过,我并没有试着去看每一套指令的速度有多快。这不是并行的。如果您想要完全并行化,您肯定必须在汇编中编写它,或者至少使用intrinsic

  • 组装

    使用AVX,您可以一次加载8个双重掩码:

      8a3:   b8 a5 ff ff ff          mov    $0xffffffa5,%eax
      8a8:   c5 f9 92 c8             kmovb  %eax,%k1
      8ac:   62 f1 fd 49 28 85 50    vmovapd -0xb0(%rbp),%zmm0{%k1}
      8b3:   ff ff ff 
    
    在本例中,我在
    %eax
    (0xA5)中放置了一个8位掩码,在
    %k1
    中复制该掩码,然后从
    -0xb0(%rbp)
    %zmm0
    加载一个值,在相应掩码位为
    0
    的位置屏蔽双精度(将它们设置为全零)

    您还需要一条指令将
    %zmm0
    保存回内存,两条指令增加指针,以及一个计数器和一个分支。因此,在C++中最好的情况下,8指令而不是9×8=72。无乘法,一次非常快的转换(
    kmovb
    )。一个限制:数组的大小必须是8的倍数

    你也可以使用内在的,比如:

     #include <immintrin.h>
    
         __mmask8 mask = 0xA5;
         __m512d a, b;
    
         __m512d res = _mm512_mask_blend_pd( mask, a, b );
    
    在您的情况下,您需要两个输入,并且
    新的\u值是已知的(
    0.0
    ),因此这不是必需的,尽管如果存在,它肯定会被优化。现在我们可以像这样重写您的
    applyMask()
    函数:

     template<class ForwardValuesIt, class ForwardMaskIt, class T>
     void mask_if(ForwardValuesIt first, ForwardValuesIt last,
                  ForwardMaskIt mf, ForwardMaskIt ml,
                  const T& new_value = T())
     {
         for (; first != last && mf != ml; ++first, ++ml) {
             if(!*ml) {
                 *first = new_value;
             }
         }
     }
    
    模板
    如果(ForwardValuesIt在前,ForwardValuesIt在后,
    ForwardMaskIt mf、ForwardMaskIt ml、,
    常数T&新值=T()
    {
    对于(;first!=last&&mf!=ml;++first,++ml){
    如果(!*ml){
    *第一个=新的_值;
    }
    }
    }
    
    这里的一个缺点是
    中的code>*ml
    。我觉得它不干净。但就算法而言,它使速度大大加快。如果你只屏蔽了很少的两倍,那么它将比执行完整的读/修改/写循环的
    transform()
    更快地通过最后一个


  • 你能告诉我们你的代码现在是什么样子吗?对不起,我没有注意到向量和向量被剥离为向量。你如何将一个
    double
    与一个
    bool
    相乘?如果为false,则设置为零?或者我遗漏了什么?这里的问题是
    向量
    专门化可能会妨碍编译器对其进行向量化的任何尝试…@user383522:如果您有足够的内存,那么通常根本不值得使用
    向量
    。它占用较少的缓存,但最终会遇到一些打包问题:这是一个例子,但还有其他一些与
    vector
    不是容器这一事实有关。使用具有true和false值的任何其他容器。通常您会选择
    vector
    vector
    deque
    ,但
    vector
    在这方面有明显的优势。
     #include <immintrin.h>
    
         __mmask8 mask = 0xA5;
         __m512d a, b;
    
         __m512d res = _mm512_mask_blend_pd( mask, a, b );
    
     template<class ForwardIt, class UnaryPredicate, class T>
     void replace_if(ForwardIt first, ForwardIt last,
                     UnaryPredicate p, const T& new_value)
     {
         for (; first != last; ++first) {
             if(p(*first)) {
                 *first = new_value;
             }
         }
     }
    
     template<class ForwardValuesIt, class ForwardMaskIt, class T>
     void mask_if(ForwardValuesIt first, ForwardValuesIt last,
                  ForwardMaskIt mf, ForwardMaskIt ml,
                  const T& new_value = T())
     {
         for (; first != last && mf != ml; ++first, ++ml) {
             if(!*ml) {
                 *first = new_value;
             }
         }
     }