在涉及<;的简单基准测试中,AVX比IA32慢3.6倍;cmath>;行动——为什么?(VS2013) 我将通过C++来说明我的典型工作领域,我更经常在C语言和MATLAB中。我也不会假装能够阅读x86汇编代码。最近看了一些关于“现代c++”的视频和最新处理器的新指令,我想我应该多看看,看看能学到什么。我有一些现有的C++ DLL,它得益于速度的提高——那些DLL使用了很多代码和电源操作,从 。
因此,我在VS2013 Express/Desktop中编写了一个简单的基准测试程序。我机器上的处理器是英特尔i7-4800MQ(Haswell)。这个程序非常简单,将一些std::vector分配给500万个随机条目,然后循环执行一些组合值的数学运算。我使用循环前后的在涉及<;的简单基准测试中,AVX比IA32慢3.6倍;cmath>;行动——为什么?(VS2013) 我将通过C++来说明我的典型工作领域,我更经常在C语言和MATLAB中。我也不会假装能够阅读x86汇编代码。最近看了一些关于“现代c++”的视频和最新处理器的新指令,我想我应该多看看,看看能学到什么。我有一些现有的C++ DLL,它得益于速度的提高——那些DLL使用了很多代码和电源操作,从 。,c++,visual-studio,sse,simd,avx,C++,Visual Studio,Sse,Simd,Avx,因此,我在VS2013 Express/Desktop中编写了一个简单的基准测试程序。我机器上的处理器是英特尔i7-4800MQ(Haswell)。这个程序非常简单,将一些std::vector分配给500万个随机条目,然后循环执行一些组合值的数学运算。我使用循环前后的std::chrono::high\u resolution\u clock::now()测量所花费的时间: [编辑:包括完整的程序代码] #include "stdafx.h" #include <chrono> #
std::chrono::high\u resolution\u 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;
}
#包括“stdafx.h”
#包括
#包括
#包括
#包括
#包括
int _tmain(int argc,_TCHAR*argv[]
{
//设置随机数生成器
std::tr1::mt19937英语;
std::tr1::正态分布区;
//要进行的计算数量
uint32 n_点=5000000;
//输入向量
std::向量x1;
std::向量x2;
std::矢量x3;
//输出向量
std::向量y1;
//初始化
x1.储备(n_分);
x2.储备(n_分);
x3.储备(n_分);
y1.储备(n_分);
//填充输入
对于(大小i=0;i std::cout因此,基于我对@Lưu Vĩnh Phúc的研究,你可以很好地将其矢量化,但不使用std::vector
或std::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;
}
与SSE2 asm相比,注意相同的向量sin
和atan
调用:
$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还不受支持
当CPU在使用AVX和SSE指令之间切换时,它需要保存/恢复ymm寄存器的上部,并可能导致错误
通常使用/arch:AVX
编译时,您自己的代码将修复此问题,因为它将尽可能使用AVX128指令而不是SSE指令。但是,在这种情况下,您的标准库的数学函数可能不是使用AVX指令实现的,在这种情况下,每次函数调用都会受到转换惩罚必须张贴一个分解版本,以确保
您经常会看到在转换到CPU不需要保存寄存器的上部之前调用VZEROUPPER
,但编译器不够聪明,无法知道它调用的函数是否也需要它。您是否尝试指定/arch:AVX2(对于Haswell,可以)而不是/arch:AVX?结果是什么?另外,如果用C数组替换std::vector呢?我现在没有MSVS2013可供检查。旁注(只是我个人的想法):MS autovectorizer可能只进行了3-4年的积极开发。对于其他C/C++编译器,矢量器的开发时间至少为15-20年。而自动矢量化是一个非常复杂的Comp.科学和实际问题,因此编译器可能需要成熟一段时间才能开始生成具有可预测性能的二进制文件@zam AVX2在Express 2013中不可用(无论如何,目前……也许我应该检查是否有新的下载)。我确实尝试过SSE和SSE2,它们与IA32相当。我必须回去记下具体的数字。AVX2开关据称在MSDN中可用。我不知道这里的明确限制。哦,如果SSE2的性能真的比AVX好(多少?)(对于完全相同的代码版本),那么这可能表明大致有3件事a)MS编译器错误/不成熟:编译器代码生成效率低下或内部编译开关故障;您是否尝试过英特尔C/C++编译器或GCC?b)内存带宽需求(通常情况下,但您需要做更高级的工作来分类),C)较小(我忘记了第四个原因(最有可能是SSE和AVX的原因)-使用SSE内参+一些低效分派实现MS数学库的一些问题。请考虑STD::考虑到给定的帖子:警告:不要使用<代码> STD::HealthRealPrimeSythCys<代码> VS 2013或VS 2012。使用<代码> QueryPerformanceCounter < /代码>以获得预期的高分辨率。打开。请参见。注意,这是为固定的。
$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
$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