【问题标题】:Performance with SSE is the same与 SSE 的性能相同
【发布时间】:2014-04-25 23:28:59
【问题描述】:

我对以下循环进行了矢量化,该循环出现在我正在开发的应用程序中:

void vecScl(Node** A, Node* B, long val){

    int fact = round( dot / const);

    for(i=0; i<SIZE ;i++)
        (*A)->vector[i] -= fact * B->vector[i];

}

这是 SSE 代码:

void vecSclSSE(Node** A, Node* B, long val){

    int fact = round( dot / const);

    __m128i vecPi, vecQi, vecCi, vecQCi, vecResi;

    int sseBound = SIZE/4;

    for(i=0,j=0;  j<sseBound  ; i+=4,j++){

        vecPi = _mm_loadu_si128((__m128i *)&((*A)->vector)[i] );
        vecQi = _mm_set_epi32(fact,fact,fact,fact);
        vecCi = _mm_loadu_si128((__m128i *)&((B)->vector)[i] );
        vecQCi = _mm_mullo_epi32(vecQi,vecCi);
        vecResi = _mm_sub_epi32(vecPi,vecQCi);               
        _mm_storeu_si128((__m128i *) (((*A)->vector) + i), vecResi );

    }

    //Compute remaining positions if SIZE % 4 != 0 
    for(; i<SIZE ;i++)
        (*A)->vector[i] -= q * B->vector[i];

}

虽然这在正确性方面有效,但使用和不使用 SSE 的性能完全相同。我正在编译代码:

 g++ *.cpp *.h -msse4.1 -march=corei7-avx -mtune=corei7-avx -mno-avx -mno-aes -Warray-bounds -O2

这是因为我没有分配(并相应地使用 SSE 函数)对齐的内存吗?代码更改起来非常复杂,所以我暂时避免这样做。

顺便说一句,在进一步改进方面,考虑到我受限于 Sandy Bridge 架构,我能做的最好的事情是什么?

编辑:编译器尚未对代码进行矢量化。首先,我将向量的数据类型更改为shorts,这不会改变性能。然后,我用-fno-tree-vectorize编译,性能是一样的。

非常感谢

【问题讨论】:

  • 可能编译器已经对代码进行了向量化,检查asm代码。
  • 我不能,我正在编译多个 .c 和 .cpp 文件。显然,当您只编译一个文件时它工作正常。我也确信它没有对代码进行向量化,因为如果我将数据类型更改为 short(而不是 int),性能必然会更好,但事实并非如此。
  • 我不太了解如何编写矢量化代码,但我知道对齐是至关重要的。输入数组需要对齐到更大的尺寸,(我猜)可以使用属性来表达。
  • @a3mlord 您可以在任何现代 IDE 中检查任何文件、任何函数甚至表达式的反汇编。如果没有编译器生成的代码,就没什么好讨论的了。为什么你认为短裤“一定会更好”?您如何描述绩效?
  • @leemes:对齐对于最近的 Intel CPU(Ivy Bridge、Sandy Bridge、Haswell 等)来说并不是很重要。不过,它仍然可以产生很小的影响。

标签: c++ performance vectorization sse


【解决方案1】:

我想说的是,对于相同的 SIZE,我能够对发生在第一篇文章之前的内核进行矢量化处理。这一次,我有很大的加速(我不会说这个因素,因为它是无关紧要的,除非我量化整个应用程序中内核花费的时间)。内核计算两个向量的点积,即:

for(i=0;i<SIZE;i++)
    dot += A->vector[i] * B->vector[i];

由此我可以得出结论,SIZE 小不是问题。反过来,这表明我在第一个内核中可能做错了。有人可以为第一个内核建议一组不同的 SSE 操作吗?我认为值得一试。下一步是分配对齐的内存,但如前所述,这在 Sandy Bridge 和其他新架构中并不重要。

这再次证明编译器没有对代码进行向量化。

谢谢

【讨论】:

    【解决方案2】:

    如果您的数据很大,那么您可能只是受内存限制,因为每次加载/存储只执行很少的 ALU 操作。

    不过,您可以尝试一些小的改进:

    inline void vecSclSSE(Node** A, Node* B, long val){
                                                // make function inline, for cases where `val` is small
    
        const int fact = (dot + const / 2 - 1) / const;
                                                // use integer arithmetic here if possible
    
        const __m128i vecQi = _mm_set1_epi32(fact);
                                                // hoist constant initialisation out of loop
    
        int32_t * const pA = (*A)->vector;      // hoist invariant de-references out of loop
        int32_t * const pB = B->vector;
    
        __m128i vecPi, vecCi, vecQCi, vecResi;
    
        for(int i = 0; i < SIZE - 3; i += 4) {   // use one loop variable
            vecPi = _mm_loadu_si128((__m128i *)&(pA[i]));
            vecCi = _mm_loadu_si128((__m128i *)&(pB[i]));
            vecQCi = _mm_mullo_epi32(vecQi,vecCi);
            vecResi = _mm_sub_epi32(vecPi,vecQCi);
            _mm_storeu_si128((__m128i *)&(pA[i]), vecResi);
        }
    
        //Compute remaining positions if SIZE % 4 != 0
        for(; i<SIZE ;i++)
            pA[i] -= q * pB[i];
    
    }
    

    【讨论】:

    • 嗨,保罗,谢谢你的回答,我想知道你什么时候会加入。我的“大小”从 20 到 150 不等。顺便说一句,你认为分配对齐的内存有多重要?在您看来,这是问题的根本原因吗?谢谢。
    • 好的 - 对于这种优化,您的数据非常小,因此您可能需要考虑减少开销。创建函数 inline 并去掉 round() 函数调用 - 使用整数运算代替。如果可以,请使用对齐内存,但由于您使用的是 Sandy Bridge,它不会产生太大的影响。
    • @PaulR,我的目标是在 SIZE > 100 时获得性能。实际上,我希望 SIZE 尽可能高,但是计算 SIZE = 150 需要几天时间,并且整个应用程序具有指数复杂性。
    • @AlexFarber,作者报告(在著名会议的双重评审论文中)将此内核向量化为短裤(而不是整数)时增益为 2.7 倍。在这一点上,这只是“过早的优化”。
    • 是的 - 我的错 - 我的错字 - 我会修复它。
    【解决方案3】:

    正如 Paul 所说,每次数据访问的计算量相对较少,而且您的代码可能受 IO 限制。由于未对齐的存储/加载比对齐的存储/加载慢,因此您确实应该对齐数据。

    您应该将 16 个字节与 SSE 对齐,这也是一个高速缓存行,(我认为)32 个字节与 AVX 对齐。如果您自己分配数据,只需使用_aligned_alloc。如果您使用std::vector,最简单的对齐方式是使用自定义分配器而不是std::allocator。这个分配器会调用_aligned_alloc 或类似的东西,而不是malloc/new。另见this question

    然后您可以切换到加载/存储的对齐指令。

    另外,我不确定&amp;((*A)-&gt;vector)[i]生成的代码是什么,最好使用本地指针来存储数据,但一定要注解__restrict

    但在进行所有这些操作之前,请确保值得您花时间和承担维护负担。您可以使用oprofile for linux 或AMD's code analyst for windows 进行配置。

    【讨论】:

    • 我没有使用来自 std:: 的任何东西。我会照你说的做 - 在这一点上听起来是不可避免的。
    • 是的,抱歉,与您的vector 混淆了...不过,请务必进行个人简介。在答案中添加了一些链接。
    猜你喜欢
    • 2016-08-18
    • 2018-03-06
    • 1970-01-01
    • 2017-08-07
    • 2015-05-19
    • 2016-03-07
    • 2021-11-18
    • 2016-10-19
    • 2013-10-21
    相关资源
    最近更新 更多