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;
}
}
}