Инструкции SSE 4, созданные Visual Studio 2013 Update 2 и Update 3
Если я скомпилирую этот код в VS 2013 Update 2 или Update 3: (ниже приведено обновление 3)
#include "stdafx.h"
#include <iostream>
#include <random>
struct Buffer
{
long* data;
int count;
};
#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif
long Code(long* data, int count)
{
long nMaxY = data[0];
for (int nNode = 0; nNode < count; nNode++)
{
nMaxY = max(data[nNode], nMaxY);
}
return(nMaxY);
}
int _tmain(int argc, _TCHAR* argv[])
{
#ifdef __AVX__
static_assert(false, "AVX should be disabled");
#endif
#ifdef __AVX2__
static_assert(false, "AVX2 should be disabled");
#endif
static_assert(_M_IX86_FP == 2, "SSE2 instructions should be enabled");
Buffer buff;
std::mt19937 engine;
engine.seed(std::random_device{}());
std::uniform_int_distribution<int> distribution(0, 100);
buff.count = 1;
buff.data = new long[1];
buff.data[0] = distribution(engine);
long result = Code(buff.data, buff.count);
std::cout << result; // ensure result is used
return result;
}
с включенными инструкциями SSE2, но не AVX/AVX2, компилятор в выпуске генерирует:
{
nMaxY = max(data[nNode], nMaxY);
010612E1 movdqu xmm0,xmmword ptr [eax]
010612E5 add esi,8
010612E8 lea eax,[eax+20h]
010612EB pmaxsd xmm1,xmm0
010612F0 movdqu xmm0,xmmword ptr [eax-10h]
010612F5 pmaxsd xmm2,xmm0
010612FA cmp esi,ebx
010612FC jl Code+41h (010612E1h)
010612FE pmaxsd xmm1,xmm2
01061303 movdqa xmm0,xmm1
01061307 psrldq xmm0,8
0106130C pmaxsd xmm1,xmm0
01061311 movdqa xmm0,xmm1
01061315 psrldq xmm0,4
0106131A pmaxsd xmm1,xmm0
0106131F movd eax,xmm1
01061323 pop ebx
long nMaxY = data[0];
который содержит, помимо прочего, инструкции pmaxsd
.
pmaxsd
инструкции инструкции SSE4_1 или инструкции AVX, насколько я могу судить, а не инструкции SSE2.
Intel Core2S поддерживает sse3, но не sse4, а не pmaxsd
.
Это не происходит в обновлении VS2013 1 или обновлении 0.
Есть ли способ заставить Visual Studio генерировать инструкции SSE2, но не инструкции SSE4, такие как pmaxsd
? Является ли это известной ошибкой в обновлении Visual Studio 2/3? Есть ли обходной путь? Visual Studio больше не поддерживает процессоры Core2?
Вот более сложная версия приведенного выше кода, который компилирует (в соответствии с настройками по умолчанию) код, который выдает CPU Core2:
#include "stdafx.h"
#include <iostream>
#include <random>
#include <array>
enum unused_name {
_nNumPolygons = 10,
};
#ifndef max
#define max(a,b) (((a) > (b)) ? (a) : (b))
#endif
struct Buffer
{
std::array<long*, _nNumPolygons> data;
std::array<int, _nNumPolygons> count;
};
long Code(Buffer* buff)
{
long nMaxY = buff->data[0][0];
for (int nPoly = 0; nPoly < _nNumPolygons; nPoly++)
{
for (int nNode = 0; nNode < buff->count[nPoly]; nNode++)
{
nMaxY = max(buff->data[nPoly][nNode], nMaxY);
}
}
return(nMaxY);
}
extern "C" __int32 __isa_available;
int _tmain(int argc, _TCHAR* argv[])
{
#ifdef __AVX__
static_assert(false, "AVX should be disabled");
#endif
#ifdef __AVX2__
static_assert(false, "AVX2 should be disabled");
#endif
#if !( defined( _M_AMD64 ) || defined( _M_X64 ) )
static_assert(_M_IX86_FP == 2, "SSE2 instructions should be enabled");
#endif
// __isa_available = 1; // to force code to act as if SSE4_2 is not available
Buffer buff;
std::mt19937 engine;
engine.seed(std::random_device{}());
std::uniform_int_distribution<int> distribution(0, 100);
for (int i = 0; i < _nNumPolygons; ++i) {
buff.count[i] = 10;
buff.data[i] = new long[10];
for (int k = 0; k < 10; ++k)
{
buff.data[i][k] = distribution(engine);
}
}
long result = Code(&buff);
std::cout << result; // ensure result is used
return result;
}
Вот ссылка на ошибку для этой проблемы, которую кто-то открыл в то же время, я разместил этот вопрос.
Вот сгенерированный .asm:
[email protected]@[email protected]@@Z PROC ; Code2, COMDAT
; _buff$ = ecx
; File c:\users\adam.nevraumont.corelcorp.000\documents\visual studio 2013\projects\consoleapplication1\consoleapplication1\consoleapplication1.cpp
; Line 22
push ebp
mov ebp, esp
sub esp, 12 ; 0000000cH
push ebx
push esi
push edi
mov edi, ecx
; Line 26
xor ebx, ebx
mov DWORD PTR _buff$1$[ebp], edi
mov DWORD PTR _nPoly$1$[ebp], ebx
mov eax, DWORD PTR [edi]
mov edx, DWORD PTR [eax]
; Line 28
movd xmm0, edx
pshufd xmm1, xmm0, 0
movdqa xmm2, xmm1
npad 12
[email protected]:
lea ecx, DWORD PTR [ebx*4]
xor eax, eax
mov esi, DWORD PTR [ecx+edi+40]
mov DWORD PTR tv443[ebp], ecx
test esi, esi
jle SHORT [email protected]
cmp esi, 8
jb SHORT [email protected]
cmp DWORD PTR ___isa_available, 2
jl SHORT [email protected]
; Line 26
mov ebx, DWORD PTR [ecx+edi]
mov ecx, esi
and ecx, -2147483641 ; 80000007H
jns SHORT [email protected]
dec ecx
or ecx, -8 ; fffffff8H
inc ecx
[email protected]:
mov edi, esi
sub edi, ecx
npad 8
[email protected]:
; Line 30
movdqu xmm0, XMMWORD PTR [ebx+eax*4]
pmaxsd xmm1, xmm0
movdqu xmm0, XMMWORD PTR [ebx+eax*4+16]
add eax, 8
pmaxsd xmm2, xmm0
cmp eax, edi
jl SHORT [email protected]
mov ebx, DWORD PTR _nPoly$1$[ebp]
mov ecx, DWORD PTR tv443[ebp]
mov edi, DWORD PTR _buff$1$[ebp]
[email protected]:
; Line 28
cmp eax, esi
jge SHORT [email protected]
; Line 26
mov edi, DWORD PTR [ecx+edi]
npad 4
[email protected]:
; Line 30
cmp DWORD PTR [edi+eax*4], edx
cmovg edx, DWORD PTR [edi+eax*4]
inc eax
cmp eax, esi
jl SHORT [email protected]
[email protected]:
; Line 26
mov edi, DWORD PTR _buff$1$[ebp]
inc ebx
mov DWORD PTR _nPoly$1$[ebp], ebx
cmp ebx, 10 ; 0000000aH
jl [email protected]
; Line 28
movd xmm0, edx
pshufd xmm0, xmm0, 0
pmaxsd xmm1, xmm0
pmaxsd xmm1, xmm2
movdqa xmm0, xmm1
psrldq xmm0, 8
pmaxsd xmm1, xmm0
movdqa xmm0, xmm1
pop edi
psrldq xmm0, 4
pmaxsd xmm1, xmm0
pop esi
movd eax, xmm1
pop ebx
; Line 35
mov esp, ebp
pop ebp
ret 0
Здесь:
cmp esi, 8
jb SHORT [email protected]
cmp DWORD PTR ___isa_available, 2
jl SHORT [email protected]
у нас есть тест, который переходит в версию "одного шага", если либо (A) цикл меньше 8 длинный, либо (B) у нас нет поддержки SSE3/SSE4.
Одноэтапная версия:
[email protected]:
; Line 26
mov edi, DWORD PTR _buff$1$[ebp]
inc ebx
mov DWORD PTR _nPoly$1$[ebp], ebx
cmp ebx, 10 ; 0000000aH
jl [email protected]
который не имеет инструкций SSE. Однако важной частью является падение. Если eax
(параметр итерации) проходит 10
, он проваливается в:
; Line 28
movd xmm0, edx
pshufd xmm0, xmm0, 0
pmaxsd xmm1, xmm0
который представляет собой код, который находит максимальный результат как результатов одноступенчатой версии, так и результатов SSE4. Третья инструкция pmaxsd
, которая является инструкцией SSE4_1 и не защищена __isa_available
.
Есть ли параметр компилятора или обходной путь, который может оставить автоматическую векторию неповрежденной, не вызывая инструкции SSE4_1 на компьютерах с поддержкой Core2 SSE2? Есть ли ошибка в моем коде, которая вызывает это?
Обратите внимание, что мои попытки удалить двойной вложенный характер цикла, похоже, заставляют проблему уйти.
Ответы
Ответ 1
Это документальное поведение:
В Auto-Vectorizer также используется новый набор инструкций SSE4.2, если ваш компьютер поддерживает его.
Если вы посмотрите на код, который генерирует компилятор, вы увидите, что использование инструкций SSE4.2 зависит от теста времени выполнения:
cmp DWORD PTR ___isa_available, 2
jl SHORT [email protected]
Значение 2 здесь по-видимому, означает SSE4.2.
Однако я смог подтвердить ошибку во втором примере. Оказывается, ядро Core 2, которое я использовал, поддерживает SSE4.1 и инструкцию PMAXSD
, поэтому мне пришлось протестировать ее на ПК с процессором Pentium 4, чтобы получить исключение из-за незаконных команд. Вы должны отправить отчет об ошибке в Microsoft Connect. Не забудьте упомянуть конкретную модель процессора Core 2, в которой ваш примерный код не работает.
Что касается обходного пути, я могу предложить только изменить уровень оптимизации для затронутой функции. Переход от оптимизации скорости к оптимизации для размера, похоже, генерирует тот же самый код, который будет использоваться только с инструкциями SSE2. Вы можете использовать #pragma optimize
для переключения уровня оптимизации следующим образом:
#pragma optimize("s", on)
long Code(Buffer* buff)
{
...
}
#pragma optimize("", on)
Как зарегистрированный в этом отчете об ошибке, /d2Qvec-sse2only
- это недокументированный флаг, который работает с обновлением 3 (и, возможно, обновлением 2), чтобы предотвратить компилятор выводит команды SSE4. Естественно, это может предотвратить некоторые векторы. /d2Qvec-sse2only
может перестать работать в любой момент ( "подлежит будущему изменению без уведомления" ), возможно, в будущих версиях VC.
Microsoft утверждает, что эта проблема исправлена в обновлении 4 и в обновлении 4 CTP 2 (не для использования в производстве).