自研AVX SIMD向量加法为何慢于std::transform?是否实现有误?
SIMD加法性能异常问题排查
环境与准备
本人刚接触SIMD,若有错误恳请见谅。
开发环境:Windows Visual Studio 2022,MSVC ISO C++20标准,处理器为11th Gen Intel(R) Core(TM) i7-11370H @ 3.30GHz。
在使用AVX前,通过检查ECX寄存器第28位确认PC支持AVX:
bool supportsAVX() { int cpuInfo[4] = { 0 }; __cpuid(cpuInfo, 1); return (cpuInfo[2] & (1 << 28)) != 0; }
为确保向量缓冲区按32字节对齐,参考现代对齐内存分配方案实现了对齐分配器,据此定义适用于AVX的对齐向量:
template<typename T, std::size_t ALIGNMENT_IN_BYTES = 32> using Aligned32Vector = std::vector<T, AlignedAllocator<T, ALIGNMENT_IN_BYTES> >;
实现代码
AVX SIMD加法函数
#include "aligned_vector.hpp" #include <immintrin.h> // For SSE/AVX intrinsics void simd_add_sd_float_avx(const simd_util::Aligned32Vector<double>& a, const simd_util::Aligned32Vector<double>& b, simd_util::Aligned32Vector<double>& c) { size_t const total_size = a.size(); constexpr size_t working_width = 32 / sizeof(double); size_t i = 0; // AVX SIMD loop for (; i < total_size - working_width; i += working_width) { // Process 4 double at a time // Load __m256d va = _mm256_load_pd(&a[i]); // Load 4 double __m256d vb = _mm256_load_pd(&b[i]); // Load 4 double // Perform SIMD addition __m256d vsum = _mm256_add_pd(va, vb); // Add 4 double in parallel // Store the result back into the 'result' array _mm256_store_pd(&c[i], vsum); // Store 4 double } // Handle leftovers if (i < total_size) { size_t remaining = total_size - i; alignas(32) double mask_data[4] = { 0.0 }; __m256d mask = _mm256_set_pd( remaining > 3 ? -1.0 : 0.0, remaining > 2 ? -1.0 : 0.0, remaining > 1 ? -1.0 : 0.0, remaining > 0 ? -1.0 : 0.0 ); // over reading and adding, but who cares we good as long as we dont use them __m256d va = _mm256_loadu_pd(&a[i]); __m256d vb = _mm256_loadu_pd(&b[i]); __m256d vr = _mm256_add_pd(va, vb); __m256d existing = _mm256_setzero_pd(); // Set to zeros __m256d blended = _mm256_blendv_pd(existing, vr, mask); // Scalar write-back alignas(32) double temp[4]; _mm256_storeu_pd(temp, blended); for (int j = 0; j < remaining; ++j) { c[i + j] = temp[j]; } } }
标量加法函数
#include<vector> #include<algorithm> void add_vector_float_normal(const std::vector<double>& a, const std::vector<double>& b, std::vector<double>& c) { std::transform(a.begin(), a.end(), b.begin(), c.begin(), std::plus<double>()); }
测试方案与结果
测试用例:创建两个大小为1000000、值为1的向量,相加后赋值给第三个同大小向量,使用chrono统计耗时,循环执行10000次。
标量测试代码
std::vector<double> a(1000000,1); std::vector<double> b(1000000,1); std::vector<double> c(1000000,0); auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < iterations; ++i) { add_vector_float_normal(a,b,c); } auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start); double average_time_us = static_cast<double>(duration.count()) / iterations;
SIMD测试代码
simd_util::Aligned32Vector<double> a(1000000, 1); simd_util::Aligned32Vector<double> b(1000000, 1); simd_util::Aligned32Vector<double> c(1000000, 0); auto start = std::chrono::high_resolution_clock::now(); for (int i = 0; i < iterations; ++i) { simd_add_sd_float_avx(a, b, c); } auto end = std::chrono::high_resolution_clock::now(); auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start); double average_time_us = static_cast<double>(duration.count()) / iterations;
测试结果显示标量版本始终优于SIMD版本:
本人预期SIMD版本整体应优于标量版本,疑惑是否实现有误,或是剩余元素处理部分效率低下。
排查过程
编译器配置
Visual Studio 2022的编译器选项:
/permissive- /ifcOutput "x64\Release\" /GS /GL /W3 /Gy /Zc:wchar_t /Zi /Gm- /O2 /sdl /Fd"x64\Release\vc143.pdb" /Zc:inline /fp:precise /D "NDEBUG" /D "_CONSOLE" /D "_UNICODE" /D "UNICODE" /errorReport:prompt /WX- /Zc:forScope /std:c17 /Gd /Oi /MD /std:c++20 /FC /Fa"x64\Release\" /EHsc /nologo /Fo"x64\Release\" /Fp"x64\Release\simd_matrix.pch" /diagnostics:column
vcxproj文件中的Release|x64配置:
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> <ClCompile> <WarningLevel>Level3</WarningLevel> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> <SDLCheck>true</SDLCheck> <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> <ConformanceMode>true</ConformanceMode> <LanguageStandard>stdcpp20</LanguageStandard> <LanguageStandard_C>stdc17</LanguageStandard_C> </ClCompile> <Link> <SubSystem>Console</SubSystem> <EnableCOMDATFolding>true</EnableCOMDATFolding> <OptimizeReferences>true</OptimizeReferences> <GenerateDebugInformation>true</GenerateDebugInformation> </Link> </ItemDefinitionGroup>
汇编代码分析
开启/O2优化后,std::transform的测试代码已被向量化,但使用的是SSE寄存器而非AVX寄存器。同时自研AVX实现反而更慢,汇编代码片段:
$LL33@add_vector: ; File C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include\xutility ; Line 501 movups xmm0, XMMWORD PTR [rcx] ; File C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include\algorithm ; Line 3763 add rsi, 8 ; File C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include\xutility ; Line 501 movups xmm1, XMMWORD PTR [rax] addpd xmm1, xmm0 movups xmm0, XMMWORD PTR [rcx+16] ; File C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include\algorithm ; Line 3764 movups XMMWORD PTR [rdx], xmm1 ; File C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include\xutility ; Line 501 movups xmm1, XMMWORD PTR [rax+16] addpd xmm1, xmm0 movups xmm0, XMMWORD PTR [rcx+32] ; File C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include\algorithm ; Line 3764 movups XMMWORD PTR [rdx+16], xmm1 ; File C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include\xutility ; Line 501 movups xmm1, XMMWORD PTR [rax+32] addpd xmm1, xmm0 movups xmm0, XMMWORD PTR [rcx+48] ; File C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.42.34433\include\algorithm
发现循环展开
观察到MSVC生成的代码中存在循环展开,计划在自研AVX实现中加入循环展开,观察性能变化。
内容的提问来源于stack exchange,提问作者somewhat_clueless_developer
相关产品推荐
相关产品推荐

