我将在前言中说C ++不是我典型的工作领域,我更常用于C#和Matlab。 我也不假装能够阅读x86汇编代码。 最近看过一些关于“现代c ++”的视频和最新处理器的新说明,我想我会更多地看一下,看看我能学到什么。 我确实有一些现有的C ++ DLL可以从速度改进中受益 - 这些DLL使用来自<cmath>许多触发和电源操作。

所以我在VS2013 Express / Desktop中编写了一个简单的基准程序。 我的机器上的处理器是Intel i7-4800MQ(Haswell)。 程序非常简单,将一些std::vector<double>分配给500万随机条目的大小,然后循环执行组合这些值的一些数学运算。 我测量在循环之前和之后使用std::chrono::high_resolution_clock::now()花费的时间:

[编辑:包括完整的程序代码]

#include "stdafx.h"
#include <chrono>
#include <random>
#include <cmath>
#include <iostream>
#include <string>

int _tmain(int argc, _TCHAR* argv[])
{

    // Set up random number generator
    std::tr1::mt19937 eng;
    std::tr1::normal_distribution<float> dist;

    // Number of calculations to do
    uint32_t n_points = 5000000;

    // Input vectors
    std::vector<double> x1;
    std::vector<double> x2;
    std::vector<double> x3;

    // Output vectors
    std::vector<double> y1;

    // Initialize
    x1.reserve(n_points);
    x2.reserve(n_points);
    x3.reserve(n_points);
    y1.reserve(n_points);

    // Fill inputs
    for (size_t i = 0; i < n_points; i++)
    {
        x1.push_back(dist(eng));
        x2.push_back(dist(eng));
        x3.push_back(dist(eng));
    }

    // Start timer
    auto start_time = std::chrono::high_resolution_clock::now();

    // Do math loop
    for (size_t i = 0; i < n_points; i++)
    {
        double result_value; 

        result_value = std::sin(x1[i]) * x2[i] * std::atan(x3[i]);

        y1.push_back(result_value);
    }

    auto end_time = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
    std::cout << "Duration: " << duration.count() << " ms";

    return 0;
}

我使用标准选项(例如/ O2)将VS置于Release配置中。 我使用/ arch执行一次构建:IA32并运行几次,另一次运行/ arch:AVX并运行几次。 一致地说,放置AVX选项比IA32选择慢约3.6倍。 在这个具体的例子中,与之相比,调整为773毫秒。

作为一个完整性检查,我确实尝试了一些其他非常基本的操作..结合了mults并添加..将一些数字带到第8个电源......并且在两个AVX之间至少同样快,如果不是更快一点。 那么为什么上面的代码会受到太多影响呢? 或者我可以在哪里找到?

编辑2:根据Reddit上某人的建议,我将代码更改为更可矢量化的内容......这使得SSE2和AVX运行得更快,但AVX仍然比SSE2慢得多:

#include "stdafx.h"
#include <chrono>
#include <random>
#include <cmath>
#include <iostream>
#include <string>

int _tmain(int argc, _TCHAR* argv[])
{

    // Set up random number generator
    std::tr1::mt19937 eng;
    std::tr1::normal_distribution<double> dist;

    // Number of calculations to do
    uint32_t n_points = 5000000;

    // Input vectors
    std::vector<double> x1;
    std::vector<double> x2;
    std::vector<double> x3;

    // Output vectors
    std::vector<double> y1;

    // Initialize
    x1.reserve(n_points);
    x2.reserve(n_points);
    x3.reserve(n_points);
    y1.reserve(n_points);

    // Fill inputs
    for (size_t i = 0; i < n_points; i++)
    {
        x1.push_back(dist(eng));
        x2.push_back(dist(eng));
        x3.push_back(dist(eng));
        y1.push_back(0.0);
    }

    // Start timer
    auto start_time = std::chrono::high_resolution_clock::now();

    // Do math loop
    for (size_t i = 0; i < n_points; i++)
    {
        y1[i] = std::sin(x1[i]) * x2[i] * std::atan(x3[i]);
    }

    auto end_time = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
    std::cout << "Duration: " << duration.count() << " ms";

    return 0;
}

IA32:209 ms SSE:205 ms SSE2:75 ms AVX:371 ms

至于Visual Studio的特定版本,这是2013 Express for Desktop Update 1(版本12.0.30110.00 Update 1)

===============>>#1 票数:2

当CPU在使用AVX和SSE指令之间切换时,它需要保存/恢复ymm寄存器的上半部分,并且可能导致相当大的损失

通常使用/arch:AVX编译/arch:AVX将为您自己的代码修复此问题,因为它将尽可能使用AVX128指令而不是SSE指令。 但是在这种情况下,可能是您的标准库的数学函数没有使用AVX指令实现,在这种情况下,您将获得每个函数调用的转换惩罚。 你必须发布一个反汇编的版本才能确定。

您经常会看到VZEROUPPER在转换之前被调用,表示CPU不需要保存寄存器的上半部分,但编译器不够聪明,无法知道它调用的函数是否也需要它。

===============>>#2 票数:0

所以基于@LưuVĩnhPhúc我调查了一下,你可以很好地实现矢量化但不使用std::vectorstd::valarray ,当我使用std::unique_ptr时我也不得不对指针进行别名会阻止矢量化。

#include <chrono>
#include <random>
#include <math.h>
#include <iostream>
#include <string>
#include <valarray>
#include <functional>
#include <memory>

#pragma intrinsic(sin, atan)
int wmain(int argc, wchar_t* argv[])
{

    // Set up random number generator
    std::random_device rd;
    std::mt19937 eng(rd());
    std::normal_distribution<double> dist;

    // Number of calculations to do
    const uint32_t n_points = 5000000;

    // Input vectors
    std::unique_ptr<double[]> x1 = std::make_unique<double[]>(n_points);
    std::unique_ptr<double[]> x2 = std::make_unique<double[]>(n_points);
    std::unique_ptr<double[]> x3 = std::make_unique<double[]>(n_points);

    // Output vectors
    std::unique_ptr<double[]> y1 = std::make_unique<double[]>(n_points);
    auto random = std::bind(dist, eng);
    // Fill inputs
    for (size_t i = 0; i < n_points; i++)
    {
        x1[i] = random();
        x2[i] = random();
        x3[i] = random();
        y1[i] = 0.0;
    }

    // Start timer
    auto start_time = std::chrono::high_resolution_clock::now();

    // Do math loop
    double * x_1 = x1.get(), *x_2 = x2.get(), *x_3 = x3.get(), *y_1 = y1.get();
    for (size_t i = 0; i < n_points; ++i)
    {
        y_1[i] = sin(x_1[i]) * x_2[i] * atan(x_3[i]);
    }

    auto end_time = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
    std::cout << "Duration: " << duration.count() << " ms";
    std::cin.ignore();
    return 0;
}

在用/arch:avx编译的机器上,这需要103ms, /arch:IA32 :252ms,没有设置:98ms

看看生成的程序集,似乎矢量函数是使用SSE实现的,因此使用它们周围的AVX指令会导致阻抗并减慢速度。 希望MS将来能够实现AVX版本。

相关的asm缺少vzeroupper

$LL3@wmain:
    vmovupd xmm0, XMMWORD PTR [esi]
    call    ___vdecl_sin2
    mov eax, DWORD PTR tv1250[esp+10212]
    vmulpd  xmm0, xmm0, XMMWORD PTR [eax+esi]
    mov eax, DWORD PTR tv1249[esp+10212]
    vmovaps XMMWORD PTR tv1240[esp+10212], xmm0
    vmovupd xmm0, XMMWORD PTR [eax+esi]
    call    ___vdecl_atan2
    dec DWORD PTR tv1260[esp+10212]
    lea esi, DWORD PTR [esi+16]
    vmulpd  xmm0, xmm0, XMMWORD PTR tv1240[esp+10212]
    vmovupd XMMWORD PTR [edi+esi-16], xmm0
    jne SHORT $LL3@wmain

与SSE2 asm注意相同的矢量sinatan调用:

$LL3@wmain:
    movupd  xmm0, XMMWORD PTR [esi]
    call    ___vdecl_sin2
    mov eax, DWORD PTR tv1250[esp+10164]
    movupd  xmm1, XMMWORD PTR [eax+esi]
    mov eax, DWORD PTR tv1249[esp+10164]
    mulpd   xmm0, xmm1
    movaps  XMMWORD PTR tv1241[esp+10164], xmm0
    movupd  xmm0, XMMWORD PTR [eax+esi]
    call    ___vdecl_atan2
    dec DWORD PTR tv1260[esp+10164]
    lea esi, DWORD PTR [esi+16]
    movaps  xmm1, XMMWORD PTR tv1241[esp+10164]
    mulpd   xmm1, xmm0
    movupd  XMMWORD PTR [edi+esi-16], xmm1
    jne SHORT $LL3@wmain

其他注意事项:

  • VS仅使用AVX寄存器的底部128位,尽管宽度为256位
  • AVX的矢量函数没有重载
  • AVX2尚不支持

  ask by Tom S translate from so

未解决问题?本站智能推荐:

2回复

256位AVX向量中32位浮点数的水平和

这个问题已经在这里有了答案: 如何将__m256水平求和? 2个答案 我有两个浮点数组,我想使用SSE和AVX以最小的延迟来计算点积。 我知道浮点数有一个256位固有的乘积,但是我读过SO,它比以下技术慢:( https://stackoverflow.com/
1回复

使用AVX指令实现_mm256_permutevar8x32_ps

AVX2内在_mm256_permutevar8x32_ps可以在通道上执行混洗,这对于排序长度为8的数组非常有用。 现在我只有AVX(Ivy Bridge)并希望在最小周期内做同样的事情。 请注意,数据和索引在编译时都是输入和未知的。 例如,数组为[1,2,3,4,5,6,7,8
1回复

AVX2 SIMD添加无效

我正在尝试使用AVX2 SIMD指令添加这两个向量。 代码编译时没有错误和警告,但是在运行时崩溃。 为什么? 无论在main方法中初始化的数组有多大,它都应打印使用AVX2添加SIMD的结果。
2回复

SSE和AVX内在混合物

除了SSE-copy,AVX-copy和std :: copy性能 。 假设我们需要以下列方式对一些循环进行矢量化:1)通过AVX向量化第一个循环批次(多次乘8)。 2)将循环的剩余部分分成两批。 通过SSE矢量化批次为4的倍数。 3)通过串行程序处理整个循环的剩余批次。 让我们考虑复
1回复

将SSE矩阵向量乘法代码转换为AVX

我正在尝试将SSE函数转换为AVX。 该函数执行矢量矩阵乘法,这是我正在使用的SSE代码: 这就是我对AVX提出的建议: 但是,AVX代码崩溃( Access violation reading location 0xFFFFFFFFFFFFFFFF )。 有人可以帮助
1回复

使用SSE / AVX的整数点积?

我正在看英特尔内在指南: https://software.intel.com/sites/landingpage/IntrinsicsGuide/ 虽然他们有_mm_dp_ps和_mm_dp_pd用于计算浮点数和双精度的点积,但我看不出任何用于计算整数点积的东西。 我有两个u
1回复

借助英特尔AVX通过遮罩改组

我是AVX编程的新手。 我有一个需要改组的寄存器。 我想将一个256位寄存器R1的几个字节改组为一个空寄存器R2。 我想定义一个掩码,该掩码告诉洗牌操作应将旧寄存器(R1)中的哪个字节复制到新寄存器中的哪个位置。 掩码应如下所示(R1中的Src:Byte Pos,R2中的Target
1回复

编写std :: copysign的便携式SSE / AVX版本

我目前正在使用SSE和AVX内部函数编写矢量化版本的QR分解(线性系统求解器)。 子步骤之一需要选择与另一个值相反/相等的值的符号。 在串行版本中,我为此使用了std :: copysign。 现在,我想为SSE ​​/ AVX寄存器创建一个类似的功能。 不幸的是,STL为此使用了内置函
1回复

跨越AVX车道的最佳方式?

有类似标题的问题,但我的问题涉及其他地方没有涉及的一个非常具体的用例。 我有4个__128d寄存器(x0,x1,x2,x3),我想在5 __256d寄存器(y0,y1,y2,y3,y4)中重新组合它们的内容,如下所示,准备其他计算: 我在下面的实现很慢。 有没有更好的办法?
2回复

AVX2浮点比较并获得0.0或1.0而不是全0或全1位

基本上,在结果向量中,我想为所有输入浮点值> 1保留1.0,而为所有输入浮点值<= 1保存0.0。这是我的代码, 但是我没有得到正确的结果。 这就是我得到的。 为什么AVX2关系操作对我来说不正常?