Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/164.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 为什么两条连续的采集指令的性能比等效的基本运算差?_C++_Performance_Vectorization_Sse_Avx2 - Fatal编程技术网

C++ 为什么两条连续的采集指令的性能比等效的基本运算差?

C++ 为什么两条连续的采集指令的性能比等效的基本运算差?,c++,performance,vectorization,sse,avx2,C++,Performance,Vectorization,Sse,Avx2,我正在将一些代码从SSE升级到AVX2。总的来说,我可以看到聚集指令非常有用,并且有利于性能。但是,我遇到了这样一种情况:与将聚集操作分解为更简单的操作相比,聚集指令的效率更低 在下面的代码中,我有一个int32b的向量,一个doublexi的向量和4个int32索引,它们被压缩在一个128位寄存器bidx中。我需要首先从vectorb收集数据,而不是从vectorxi收集数据。即,在伪代码中,我需要执行以下操作: __m128i i = b[idx]; __m256d x = xi[i];

我正在将一些代码从SSE升级到AVX2。总的来说,我可以看到聚集指令非常有用,并且有利于性能。但是,我遇到了这样一种情况:与将聚集操作分解为更简单的操作相比,聚集指令的效率更低

在下面的代码中,我有一个int32
b
的向量,一个double
xi
的向量和4个int32索引,它们被压缩在一个128位寄存器
bidx
中。我需要首先从vector
b
收集数据,而不是从vector
xi
收集数据。即,在伪代码中,我需要执行以下操作:

__m128i i = b[idx];
__m256d x = xi[i];
在下面的函数中,我使用
#ifdef
以两种方式实现了这一点:通过聚集指令,产生290米/秒的吞吐量;通过基本操作,产生325米/秒的吞吐量

有人能解释一下发生了什么事吗?谢谢

inline void resolve( const __m256d& z, const __m128i& bidx, int32_t j
                    , const int32_t *b, const double *xi, int32_t* ri )
{

    __m256d x;
    __m128i i;

#if 0  // this code uses two gather instructions in sequence

    i = _mm_i32gather_epi32(b, bidx, 4));  // i = b[bidx]
    x = _mm256_i32gather_pd(xi, i, 8);     // x = xi[i]

#else  // this code does not use gather instructions

    union {
            __m128i vec;
            int32_t i32[4];
    } u;
    x = _mm256_set_pd
            ( xi[(u.i32[3] = b[_mm_extract_epi32(bidx,3)])]
            , xi[(u.i32[2] = b[_mm_extract_epi32(bidx,2)])]
            , xi[(u.i32[1] = b[_mm_extract_epi32(bidx,1)])]
            , xi[(u.i32[0] = b[_mm_cvtsi128_si32(bidx)  ])]
            );
    i = u.vec;

#endif

    // here we use x and i
    __m256  ps256 = _mm256_castpd_ps(_mm256_cmp_pd(z, x, _CMP_LT_OS));
    __m128  lo128 = _mm256_castps256_ps128(ps256);
    __m128  hi128 = _mm256_extractf128_ps(ps256, 1);
    __m128  blend = _mm_shuffle_ps(lo128, hi128, 0 + (2<<2) + (0<<4) + (2<<6));
    __m128i lt    = _mm_castps_si128(blend);  // this is 0 or -1
    i = _mm_add_epi32(i, lt);
    _mm_storeu_si128(reinterpret_cast<__m128i*>(ri)+j, i);
}
内联无效解析(常量m256d&z、常量m128i&bidx、int32&t j
,常数int32_t*b,常数double*xi,int32_t*ri)
{
__m256d-x;
__m128i;
#如果0//此代码按顺序使用两条聚集指令
i=_mm_i32gather_epi32(b,bidx,4));//i=b[bidx]
x=_mm256_i32gather_pd(xi,i,8);//x=xi[i]
#else//此代码不使用聚集指令
联合{
__m128i-vec;
int32_t i32[4];
}u;
x=_mm256_set_pd
(xi[(u.i32[3]=b[(bidx,3)])
,xi[(u.i32[2]=b[_-mm\u-extract\u-epi32(bidx,2)])]
,xi[(u.i32[1]=b[_-mm\u-extract\u-epi32(bidx,1)])]
,xi[(u.i32[0]=b[(bidx)]]
);
i=u.vec;
#恩迪夫
//这里我们使用x和i
__m256 ps256=_mm256_castpd_ps(_mm256_cmp_pd(z,x,_cmp_LT_OS));
__m128 lo128=_mm256_castps256_ps128(ps256);
__m128 hi128=_mm256_extractf128_ps(ps256,1);

__m128混合=mm_shuffle_ps(lo128,hi128,0+(2由于“resolve”函数被标记为内联,我认为它是在高频循环中调用的。然后,您还可以查看“resolve”函数外部输入参数之间的依赖关系。使用标量代码变量时,编译器可能能够更好地跨循环边界优化内联代码

Gather指令非常慢。我不知道你们有什么处理器,但在Haswell上,我从来没有发现过Gather实际上比手动标量代码更快的情况。Broadwell和Skylake的情况应该更好,但此后我就没有测试过。我正在Haswell上测试。根据我的测试,当我有一个独立的Gather时指令而不是两个连续的指令,性能有所提高。例如,_mm256_i32gather_ps比手动版本快,两者都使用提取(这是最差的)或使用联合。在《英特尔内部指南》中无法获得采集的反向吞吐量,但Agner Fog测量到
\u mm\u i32gather\u epi32
为9个时钟,尚未列出
\u mm256\u i32gather\u pd
。我的猜测是,可能需要约18个时钟才能进行两次连续采集,而您下面的代码不足以隐藏latency。您可以使用编译器
-S
,检查为不同版本生成的内容。在执行
\u mm256\u i32gather\u pd
指令之前,它必须等待
\u mm\u i32gather\u epi32
指令完成,这会导致较大的延迟。没有gather的较长方式不会有此延迟,因为更多的指令可以更好地调度,因为它可以处理更大问题中的较小部分。如果您看到愤怒雾,则gather指令分别使用
P0P1 p23 p5
。这会造成很大的延迟。我使用
skylake
,并测试了许多带有和不带有收集的程序。如果Intel为gatheri提供单独的执行核心ng或增加洗牌执行可能会提高聚集性能。但现在,聚集只是一个新名称,使用洗牌和置换作为虚拟名称。最佳选择是使用
shuffle
insert
permute
,以产生预期性能。