Инструкции 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 (не для использования в производстве).