首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >SSE性能特征

SSE性能特征
EN

Stack Overflow用户
提问于 2017-09-25 14:12:23
回答 1查看 2K关注 0票数 3

我试着用本征写一些SSE代码,有些行为让我无法理解。

给定的代码:

代码语言:javascript
复制
#ifndef EIGEN_DONT_VECTORIZE // Not needed with Intel C++ Compiler XE 15.0
#define EIGEN_VECTORIZE_SSE4_2
#define EIGEN_VECTORIZE_SSE4_1
#define EIGEN_VECTORIZE_SSSE3
#define EIGEN_VECTORIZE_SSE3
#endif

#include "stdafx.h"
#include <iostream>
#include <unsupported/Eigen/AlignedVector3>
#include <Eigen/StdVector>
#include <chrono>

int _tmain(int argc, _TCHAR* argv[]) {
    static const int SIZE = 4000000;
    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE(1, 1, 1);
    //EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> B_SSE(2, 2, 2);
    //std::vector<Eigen::AlignedVector3<float>> C_SSE(SIZE, Eigen::AlignedVector3<float>(0,0,0));


    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE1(1, 1, 1);
    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE2(1, 1, 1);
    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE3(1, 1, 1);
    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> A_SSE4(1, 1, 1);

    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> B_SSE(2, 2, 2);
    EIGEN_ALIGNED_VECTOR3 Eigen::AlignedVector3<float> B_SSE_increment_unroll(16, 16, 16);

    A_SSE2 += B_SSE;
    A_SSE3 = A_SSE2 + B_SSE;
    A_SSE4 = A_SSE3 + B_SSE;


    std::vector<Eigen::AlignedVector3<float>> C_SSE(SIZE, Eigen::AlignedVector3<float>(0, 0, 0));

    auto start2 = std::chrono::system_clock::now();

    // no unroll
    for (int iteration = 0; iteration < SIZE; ++iteration) {
        A_SSE += B_SSE;
        C_SSE[iteration] = A_SSE;
    }

    //// own unroll
    //for (int iteration = 0; iteration < SIZE / 8; ++iteration){
    //  A_SSE1 += B_SSE_increment_unroll;
    //  A_SSE2 += B_SSE_increment_unroll;
    //  A_SSE3 += B_SSE_increment_unroll;
    //  A_SSE4 += B_SSE_increment_unroll;

    //  C_SSE[iteration * 2] = A_SSE1;
    //  C_SSE[iteration * 2 + 1] = A_SSE2;
    //  C_SSE[iteration * 2 + 2] = A_SSE3;
    //  C_SSE[iteration * 2 + 3] = A_SSE4;

    //}

    auto end2 = std::chrono::system_clock::now();
    auto elapsed2 = end2 - start2;
    std::cout << "Eigen aligned vector " << elapsed2.count() << '\n';

    Eigen::Matrix3Xf A = Eigen::Matrix3Xf::Zero(3, SIZE);
    Eigen::Vector3f B(3, 3, 3);
    Eigen::Vector3f C(2, 2, 2);

    auto start1 = std::chrono::system_clock::now();

    for (int iteration = 0; iteration < SIZE; ++iteration) {
        B += C;
        A.col(iteration) = B;
    }
    auto end1 = std::chrono::system_clock::now();
    auto elapsed1 = end1 - start1;
    std::cout << "Eigen matrix " << elapsed1.count() << '\n';


    float *pResult = (float*)_aligned_malloc(SIZE * sizeof(float) * 4, 16); // align to 16-byte for SSE
    auto start3 = std::chrono::system_clock::now();

    __m128 x;
    __m128 xDelta = _mm_set1_ps(2.0f);      // Set the xDelta to (4,4,4,4)
    __m128 *pResultSSE = (__m128*) pResult;

    x = _mm_set_ps(1.0f, 1.0f, 1.0f, 1.0f); // Set the initial values of x to (4,3,2,1)

    for (int iteration = 0; iteration < SIZE; ++iteration)
    {
        x = _mm_add_ps(x, xDelta);
        pResultSSE[iteration] = x;
    }

    auto end3 = std::chrono::system_clock::now();
    auto elapsed3 = end3 - start3;
    std::cout << "Own sse " << elapsed3.count() << '\n';

}

在我的电脑上,时间似乎很奇怪。

  • 特征对齐矢量展开: 20057
  • 本征对齐矢量无展开:~120320
  • 特征矩阵:~120207 (与对齐不展开相同)
  • 自己的上证考试: 160784

当我检查程序集、对齐版本和自己的SSE时,使用addps movaps,但在手动展开循环之前,我不会获得额外的性能,即使不是在所有运行(50%)中都这样做,我也不会得到任何提升。使用本征矩阵的版本不使用sse,实现相同的性能,内联组装显示在16次迭代中展开。手动展开那么有影响吗?我们是否应该手动为SSE执行此操作,如果使用CPU属性,则取决于此?

编辑:所以总结一下。SSE指令性能不佳,因为无法证明展开循环将保持与未展开循环相同的结果,因此无法隐藏内存存储延迟。但是在汇编代码中,“单一”指令只使用一个寄存器,并在展开循环中递增。如果SSE成瘾是垂直执行的(对齐向量中的单个浮点数累积了相同的加法运算量),编译器应该能够证明展开的相等性。默认情况下,SSE操作是否不经编译器优化?如果展开循环保持执行顺序,那么保留非关联的数学,那么自动展开应该是可能的,为什么它不会发生,以及如何强制编译器这样做呢?

编辑:正如我所建议的那样,我运行测试,但是来自艾根的工作台单元不能在visual studio 2017下工作,所以它被替换为

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <unsupported/Eigen/AlignedVector3>
#include <chrono>
#include <numeric>

EIGEN_DONT_INLINE
void vector_no_unroll(std::vector<Eigen::AlignedVector3<float>>& out)
{
    Eigen::AlignedVector3<float> A_SSE(1, 1, 1);
    Eigen::AlignedVector3<float> B_SSE(2, 2, 2);
    for (auto &x : out)
    {
        A_SSE += B_SSE;
        x = A_SSE;
    }
}

EIGEN_DONT_INLINE
void vector_unrolled(std::vector<Eigen::AlignedVector3<float>>& out)
{
    Eigen::AlignedVector3<float> A_SSE1(1, 1, 1);
    Eigen::AlignedVector3<float> A_SSE2(1, 1, 1);
    Eigen::AlignedVector3<float> A_SSE3(1, 1, 1);
    Eigen::AlignedVector3<float> A_SSE4(1, 1, 1);

    Eigen::AlignedVector3<float> B_SSE(2, 2, 2);
    Eigen::AlignedVector3<float> B_SSE_increment_unroll(16, 16, 16);

    A_SSE2 += B_SSE;
    A_SSE3 = A_SSE2 + B_SSE;
    A_SSE4 = A_SSE3 + B_SSE;
    for (size_t i = 0; i<out.size(); i += 4)
    {
        A_SSE1 += B_SSE_increment_unroll;
        A_SSE2 += B_SSE_increment_unroll;
        A_SSE3 += B_SSE_increment_unroll;
        A_SSE4 += B_SSE_increment_unroll;
        out[i + 0] = A_SSE1;
        out[i + 1] = A_SSE2;
        out[i + 2] = A_SSE3;
        out[i + 3] = A_SSE4;
    }
}

EIGEN_DONT_INLINE
void eigen_matrix(Eigen::Matrix3Xf& out)
{
    Eigen::Vector3f B(1, 1, 1);
    Eigen::Vector3f C(2, 2, 2);

    for (int i = 0; i < out.cols(); ++i) {
        B += C;
        out.col(i) = B;
    }
}

template<int unrolling> EIGEN_DONT_INLINE
void eigen_matrix_unrolled(Eigen::Matrix3Xf& out)
{
    Eigen::Matrix<float, 3, unrolling> B = Eigen::Matrix<float, 1, unrolling>::LinSpaced(3.f, 1 + 2 * unrolling).template replicate<3, 1>();

    for (int i = 0; i < out.cols(); i += unrolling) {
        out.middleCols<unrolling>(i) = B;
        B.array() += float(2 * unrolling);
    }
}

int main() {
    static const int SIZE = 4000000;

    int tries = 30;
    int rep = 10;


    std::vector<int> Timings(tries, 0);
    {
        Eigen::Matrix3Xf A(3, SIZE);
#pragma loop( 1 )
        for (int iter = 0; iter < tries; ++iter)
        {
            auto start1 = std::chrono::system_clock::now();
            eigen_matrix(A);
            Timings[iter] = (std::chrono::system_clock::now() - start1).count();
        }
    }
    std::cout << "eigen matrix Min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n";
    std::cout << "eigen matrix Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0) / tries << " ms\n";

    {
        Eigen::Matrix3Xf A(3, SIZE);
#pragma loop( 1 )
        for (int iter = 0; iter < tries; ++iter)
        {
            auto start1 = std::chrono::system_clock::now();
            eigen_matrix_unrolled<4>(A);
            Timings[iter] = (std::chrono::system_clock::now() - start1).count();
        }
    }
    std::cout << "eigen matrix unrolled 4 min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n";
    std::cout << "eigen matrix unrolled 4 Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0) / tries << " ms\n";

    {
        Eigen::Matrix3Xf A(3, SIZE);
#pragma loop( 1 )
        for (int iter = 0; iter < tries; ++iter)
        {
            auto start1 = std::chrono::system_clock::now();
            eigen_matrix_unrolled<8>(A);
            Timings[iter] = (std::chrono::system_clock::now() - start1).count();
        }
    }
    std::cout << "eigen matrix unrolled 8 min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n";
    std::cout << "eigen matrix unrolled 8 Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0) / tries << " ms\n";

    {
        std::vector<Eigen::AlignedVector3<float>> A(SIZE, Eigen::AlignedVector3<float>(0, 0, 0));
#pragma loop( 1 )
        for (int iter = 0; iter < tries; ++iter)
        {
            auto start1 = std::chrono::system_clock::now();
            vector_no_unroll(A);
            Timings[iter] = (std::chrono::system_clock::now() - start1).count();
        }
    }
    std::cout << "eigen vector min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n";
    std::cout << "eigen vector Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0) / tries << " ms\n";

    {
        std::vector<Eigen::AlignedVector3<float>> A(SIZE, Eigen::AlignedVector3<float>(0, 0, 0));
#pragma loop( 1 )
        for (int iter = 0; iter < tries; ++iter)
        {
            auto start1 = std::chrono::system_clock::now();
            vector_unrolled(A);
            Timings[iter] = (std::chrono::system_clock::now() - start1).count();
        }
    }
    std::cout << "eigen vector unrolled min: " << *std::min_element(Timings.begin(), Timings.end()) << " ms\n";
    std::cout << "eigen vector unrolled Mean: " << std::accumulate(Timings.begin(), Timings.end(), 0) / tries << " ms\n";

}

并在8台不同的机器(所有窗口)上检查结果,并得到以下结果。

特征矩阵Min: 110477 ms

特征矩阵均值: 131691 ms

特征矩阵展开4分钟: 40099 ms

特征矩阵展开4平均: 54812 ms

特征矩阵展开8 min: 40001 ms

特征矩阵展开8平均: 51482 ms

特征向量最小: 100270 ms

特征向量均值: 117316 ms

特征向量展开最小: 59966 ms

特征向量展开平均: 65847 ms

在我测试的每台机器上,最古老的一台。看起来,在新的机器上,小型展开可能是非常有益的(结果不同,从1.5倍到3.5倍的速度在4倍展开,即使展开是8,16,32,或256次,也不会增加)。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2017-09-27 21:55:48

您的时间非常不准确(当多次运行您的代码时,我得到了很多变化)。为了获得更好的重现性,您应该多次运行每个变体,并且花费最少的时间。我使用作为特征的一部分的BenchUtils编写了一个基准测试:

代码语言:javascript
复制
#include <iostream>
#include <unsupported/Eigen/AlignedVector3>
#include <bench/BenchUtil.h>

EIGEN_DONT_INLINE
void vector_no_unroll(std::vector<Eigen::AlignedVector3<float>>& out)
{
    Eigen::AlignedVector3<float> A_SSE(1, 1, 1);
    Eigen::AlignedVector3<float> B_SSE(2, 2, 2);
    for(auto &x : out)
    {
        A_SSE += B_SSE;
        x = A_SSE;
    }
}

EIGEN_DONT_INLINE
void vector_unrolled(std::vector<Eigen::AlignedVector3<float>>& out)
{
    Eigen::AlignedVector3<float> A_SSE1(1, 1, 1);
    Eigen::AlignedVector3<float> A_SSE2(1, 1, 1);
    Eigen::AlignedVector3<float> A_SSE3(1, 1, 1);
    Eigen::AlignedVector3<float> A_SSE4(1, 1, 1);

    Eigen::AlignedVector3<float> B_SSE(2, 2, 2);
    Eigen::AlignedVector3<float> B_SSE_increment_unroll(16, 16, 16);

    A_SSE2 += B_SSE;
    A_SSE3 = A_SSE2 + B_SSE;
    A_SSE4 = A_SSE3 + B_SSE;
    for(size_t i=0; i<out.size(); i+=4)
    {
        A_SSE1 += B_SSE_increment_unroll;
        A_SSE2 += B_SSE_increment_unroll;
        A_SSE3 += B_SSE_increment_unroll;
        A_SSE4 += B_SSE_increment_unroll;
        out[i + 0] = A_SSE1;
        out[i + 1] = A_SSE2;
        out[i + 2] = A_SSE3;
        out[i + 3] = A_SSE4;
    }
}

EIGEN_DONT_INLINE
void eigen_matrix(Eigen::Matrix3Xf& out)
{
    Eigen::Vector3f B(1, 1, 1);
    Eigen::Vector3f C(2, 2, 2);

    for (int i = 0; i < out.cols(); ++i) {
        B += C;
        out.col(i) = B;
    }
}

template<int unrolling> EIGEN_DONT_INLINE
void eigen_matrix_unrolled(Eigen::Matrix3Xf& out)
{
    Eigen::Matrix<float,3,unrolling> B = Eigen::Matrix<float, 1, unrolling>::LinSpaced(3.f, 1+2*unrolling).template replicate<3,1>();

    for (int i = 0; i < out.cols(); i+=unrolling) {
        out.middleCols<unrolling>(i) = B;
        B.array() += float(2*unrolling);
    }
}

int main() {
    static const int SIZE = 4000000;

    int tries = 10;
    int rep = 10;
    BenchTimer t;

    std::cout.precision(4);
    {
        std::vector<Eigen::AlignedVector3<float>> A(SIZE, Eigen::AlignedVector3<float>(0, 0, 0));
        BENCH(t, tries, rep, vector_no_unroll(A));
        std::cout << "no unroll:    " << 1e3*t.best(CPU_TIMER) << "ms\n";
    }
    {
        std::vector<Eigen::AlignedVector3<float>> A(SIZE, Eigen::AlignedVector3<float>(0, 0, 0));
        BENCH(t, tries, rep, vector_unrolled(A));
        std::cout << "unrolled:     " << 1e3*t.best(CPU_TIMER) << "ms\n";
    }
    {
        Eigen::Matrix3Xf A(3, SIZE);
        BENCH(t, tries, rep, eigen_matrix(A));
        std::cout << "eigen matrix: " << 1e3*t.best(CPU_TIMER) << "ms\n";
    }
    {
        Eigen::Matrix3Xf A(3, SIZE);
        BENCH(t, tries, rep, eigen_matrix_unrolled<4>(A));
        std::cout << "eigen unrd<4>: " << 1e3*t.best(CPU_TIMER) << "ms\n";
    }
    {
        Eigen::Matrix3Xf A(3, SIZE);
        BENCH(t, tries, rep, eigen_matrix_unrolled<8>(A));
        std::cout << "eigen unrd<8>: " << 1e3*t.best(CPU_TIMER) << "ms\n";
    }
}

我得到了非常相似的时间,几乎独立于使用-msse2-msse4.2-mavx2编译

无展开: 66.72ms展开: 66.83ms特征矩阵: 57.56ms特征unrd<4>:50.39ms特征unrd<8>:51.19ms

值得注意的是,AligenedVector3变体始终是最慢的,在展开和不展开之间没有显着性差异。矩阵变体花费大约7/8的时间,手动展开矩阵变量(每次迭代处理4或8列),将时间减少到原来时间的3/4左右。

这表明内存带宽可能是所有向量化变体的瓶颈。展开矩阵变量可能受到实际操作(或单个标量的手动复制)的限制。

基准测试是在IntelCorei5-4210Ucpu @1.70GHz上进行的,使用的是Ubuntu16.04上的g++5.4.1,并且最近对Eigen开发分支进行了检查。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/46407332

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档