Конференция "Начинающим" » Опкоды AVX
 
  • dmk © (08.06.18 13:29) [0]
    Всем привет! Есть ли у кого возможность проверить AVX-опкоды?
    Взял опкоды из FreePascal и вставил в Delphi. Вроде все работает, но очень медленно. Может опкоды некорректные или Windows эмулятором их исполняет.
    На самом деле скорость должна быть очень высокая,
    т.к. XMM-версия этого кода работает раз в 20 быстрее.


     //vmovdqu ymm10, [R8]
     db $C4, $41, $7E, $6F, $10

     //vaddpd ymm10, ymm10, ymm13
     db $C4, $41, $2D, $58, $D5

     //vandpd ymm0, ymm10, ymm15
     db $C4, $C1, $2D, $54, $C7

     //vxorpd ymm0, ymm0, ymm15
     db $C4, $C1, $7D, $57, $C7

     //vptest ymm0, ymm0
     db $C4, $E2, $7D, $17, $C0
     jz @Inside
     dec X1
     jnz @X
     ret

    @Inside:
     //vmovdqa ymm1, ymm12
     db $C5, $7D, $7F, $E1

     //vmulpd ymm1, ymm1, ymm10
     db $C4, $C1, $75, $59, $CA

     //vmovdqa ymm4, ymm1
     db $C5, $FD, $7F, $CC

     //vmulpd ymm1, ymm1, ymm14
     db $C4, $C1, $75, $59, $CE

     //vextractf128 xmm2, ymm1, 01b
     db $C4, $E3, $7D, $19, $CA, $01

     addsd xmm2, xmm1
     psrldq xmm1, 8
     addsd xmm1, xmm2

     //Загружаем единицу
     movq xmm0, R13

     //интерполяция
     divsd xmm0, xmm1

     //Конвертируем в single
     cvtsd2ss xmm0, xmm0

     //fZ (тип Single)
     comiss xmm0, dword ptr [r10]
     jb @Below
     dec X1
     jnz @X
     ret

    @Below:
     movd dword ptr [r10], xmm0
     shufps xmm0, xmm0, 00b

     //vcvtpd2ps xmm4, ymm4
     db $C5, $FD, $5A, $E4

  • dmk © (08.06.18 15:07) [1]
    В общем проверил сам с помощью IDA Pro:
    https://hostingkartinok.com/show-image.php?id=e306db3aa4ea490e397dc50b818ced53

    IDA показывает правильный код.
    Даже не знаю куда теперь копать.

    Почему код может исполняется медленно?
  • Прохосый (10.06.18 03:23) [2]
    Так а можно полностью функцию-то? Обе версии? И пример входящих/результирующих данных?
    Тогда и запустим с радостью. :)
  • dmk © (10.06.18 11:03) [3]
    Процедура просто закрашивает полигон. Ничего интересного.
    Она вообще-то работает, но очень медленно.
    Такое ощущение, что код пропускается через эмулятор.


    procedure ScanLineVecD256(X0, X1: Integer; P: TVertexD);
    asm
     .NOFRAME

     sub X1, X0
     inc X1

     //Маска сборки ABGR-пиксела
     movq2dq xmm3, mm6

     //Множитель 255.0. Перед циклом загрузить в R12d
     movd xmm11, r12d
     shufps xmm11, xmm11, 00b

     //координаты точки
     //vmovdqu ymm10, [R8]
     db $C4, $41, $7E, $6F, $10

    @X:

     add r10, 4
     add r11, 4

     //vaddpd ymm10, ymm10, ymm13
     db $C4, $41, $2D, $58, $D5

     //vandpd ymm0, ymm10, ymm15
     db $C4, $C1, $2D, $54, $C7

     //vxorpd ymm0, ymm0, ymm15
     db $C4, $C1, $7D, $57, $C7

     //vptest ymm0, ymm0
     db $C4, $E2, $7D, $17, $C0

     jz @Inside
     dec X1
     jnz @X
     ret

    @Inside:

     //Копируем для умножения
     //vmovdqa ymm1, ymm12
     db $C5, $7D, $7F, $E1

     //vmulpd ymm1, ymm1, ymm10
     db $C4, $C1, $75, $59, $CA

     //vmovdqa ymm4, ymm1
     db $C5, $FD, $7F, $CC

     //vmulpd ymm1, ymm1, ymm14
     db $C4, $C1, $75, $59, $CE

     //Извлекаем (X+Y)
     //vextractf128 xmm2, ymm1, 01b
     db $C4, $E3, $7D, $19, $CA, $01
     //Складываем (X+Y)+Z
     addsd xmm2, xmm1
     psrldq xmm1, 8
     addsd xmm1, xmm2

     //Загружаем единицу
     movq xmm0, R13

     //интерполяция
     divsd xmm0, xmm1

     //Конвертируем в single
     cvtsd2ss xmm0, xmm0 //fZ = xmm0

     //На входе в XMM0 должен быть fZ (тип Single)
     comiss xmm0, dword ptr [r10] //Читаем Z-Buffer
     jb @Below
     dec X1
     jnz @X
     ret

    @Below:
     movd dword ptr [r10], xmm0 //Пишем fZ

     //Преобразуем цвет по вектору из Double в Single
     //vcvtpd2ps xmm4, ymm4
     db $C5, $FD, $5A, $E4

     cvtps2dq xmm4, xmm4 //Округляем сразу 4 значения
     pshufb xmm4, xmm3 //Собираем пиксел
     movd dword ptr [r11], xmm4 //Результат пишем по адресу пиксела

     dec X1
     jnz @X

    @Pass:
    end;
  • Pavia © (10.06.18 14:46) [4]

    > Такое ощущение, что код пропускается через эмулятор.

    А почему нет? Интел как раз таки выпускала эмулятор для AVX. Может по ошибки установили? А ваш проц точно поддерживает AVX?

    Вы бы код целиком выложили тогда потестировать можно было-бы.
  • dmk © (10.06.18 15:57) [5]
    У меня и AVX и AVX2 поддерживается. Проц i7-6950X.

    Вот тоже самое, только XMM с типом Single.
    AVX нужен, чтобы полностью помещался тип Double, т.е. 256 бит.


    procedure ScanLineVecS(X0, X1: Integer; P: TVertexD);
    asm
     .NOFRAME

     sub X1, X0
     inc X1

     cvtsd2ss xmm10, [P]
     cvtsd2ss xmm0, [P + 8]
     cvtsd2ss xmm1, [P + 16]

     insertps xmm10, xmm0, 00010000b
     insertps xmm10, xmm1, 00100000b

    @X:

     add r10, 4
     add r11, 4

     addps xmm10, xmm13
     movdqa xmm12, xmm10

     pand xmm12, xmm15
     pxor xmm12, xmm15
     ptest xmm12, xmm12
     jz @Inside
     dec X1
     jnz @X
     ret

    @Inside:

     movdqa xmm1, xmm11

     mulps xmm1, xmm10

     movdqa xmm4, xmm1

     mulps xmm1, xmm14
     haddps xmm1, xmm1
     haddps xmm1, xmm1

     movd xmm0, R13d //Single-Единица должна быть в R13
     divss xmm0, xmm1

     comiss xmm0, dword ptr [r10]
     jb @Below
     dec X1
     jnz @X
     ret

    @Below:
     movd dword ptr [r10], xmm0
     movd dword ptr [r11], xmm0

     dec X1
     jnz @X
    end;

  • dmk © (10.06.18 21:00) [6]
    В общем вот рабочий код. Только под 64 бита.

    У меня получаются такие значения:
    Test XMM: 2,36 сек. (4 239 084,36 в сек.)
    Test AVX: 149,95 сек. (66 687,56 в сек.)
    10 млн. сканлиний с очисткой буфера.
    В коде константа N.
    Похоже, что исполняется на эмуляторе, а не на железе.
    Код одинаковый. Разница лишь в типах данных: 128 бит и 256 бит.
  • dmk © (10.06.18 21:01) [7]

    program AVX_test;

    {$APPTYPE CONSOLE}

    {$R *.res}

    uses
     System.SysUtils, Winapi.Windows;

    type
     TVertex = packed record
       X, Y, Z, W: Single;
     end;

    type
     TVertexD = packed record
       X, Y, Z, W: Double;
     end;

    type
     TTripleVertex = packed record
       C0, C1, C2: TVertex;
     end;

    type
     XMM128D = array[0..3] of UInt32;

    type
     AVX256Q = array[0..3] of UInt64;

    type
     QWord = UInt64;

    const
     M255: Single = 255.0;
     GatherColors: UInt32 = $FF000408;
     SingleOne: Single = 1.0;
     DoubleOne: Double = 1.0;

     //-----------------------------------------------------

    procedure LoadDataS(CV, bStep, Z: Pointer; Area: Double);
    const
     InsideMask: XMM128D = ($80000000, $80000000, $80000000, $0);

    asm
     .NOFRAME

     movdqu xmm7, dqword ptr [CV]
     movdqu xmm8, dqword ptr [CV + 16]
     movdqu xmm9, dqword ptr [CV + 32]

     pxor xmm2, xmm2

     //bStep -> rdx
     cvtsd2ss xmm13, qword ptr [bStep]
     cvtsd2ss xmm0, qword ptr [bStep + 8]
     cvtsd2ss xmm1, qword ptr [bStep + 16]

     insertps xmm13, xmm0, 00010000b // Y
     insertps xmm13, xmm1, 00100000b // Z
     insertps xmm13, xmm2, 00110000b // Ноль

     //Z -> r8
     cvtsd2ss xmm14, qword ptr [Z]
     cvtsd2ss xmm0, qword ptr [Z + 8]
     cvtsd2ss xmm1, qword ptr [Z + 16]

     insertps xmm14, xmm0, 00010000b // Y
     insertps xmm14, xmm1, 00100000b // Z
     insertps xmm14, xmm2, 00110000b // Ноль

     mov R13, [DoubleOne]
     mov R12d, [M255]
     movd mm6, [GatherColors]
     movdqu xmm15, [InsideMask]

     movq xmm11, R13
     divsd xmm11, Area
     cvtsd2ss xmm11, xmm11
     shufps xmm11, xmm11, 00b
     insertps xmm11, xmm2, 00110000b // Ноль

     mov R13d, [SingleOne]
    end;

    //-----------------------------------------------------

    procedure LoadDataD256(CV, bStep, Z: Pointer; Area: Double);
    const
     InsideMask: AVX256Q = ($8000000000000000, $8000000000000000, $8000000000000000, $0000000000000000);

    asm
     .NOFRAME

     //vzeroupper
     //db $C5, $F8, $77

     mov R12d, [M255]
     movd mm6, [GatherColors]

     movdqu xmm7, dqword ptr [CV]
     movdqu xmm8, dqword ptr [CV + 16]
     movdqu xmm9, dqword ptr [CV + 32]

     //bStep находится в rdx
     //vmovdqu ymm13, [rdx][bStep]
     db $C5, $7E, $6F, $2A

     //InsideMask
     lea rax, qword ptr [InsideMask]
     //vmovdqu ymm15, [rax]
     db $C5, $7E, $6F, $38

     //Z находится в r8
     //vmovdqu ymm14, [r8]
     db $C4, $41, $7E, $6F, $30

     movsd xmm12, Area
     //vinsertf128 ymm12, ymm12, xmm12, 01b
     db $C4, $43, $1D, $18, $E4, $01
     shufpd xmm12, xmm12, 00b
    end;

    //-----------------------------------------------------

    procedure ScanLineVecS(X0, X1: Integer; P: TVertexD);
    asm
     .NOFRAME

     sub X1, X0
     inc X1

     cvtsd2ss xmm10, [P]
     cvtsd2ss xmm0, [P + 8]
     cvtsd2ss xmm1, [P + 16]

     insertps xmm10, xmm0, 00010000b
     insertps xmm10, xmm1, 00100000b

    @X:

     add r10, 4
     add r11, 4

     addps xmm10, xmm13
     movdqa xmm12, xmm10

     pand xmm12, xmm15
     pxor xmm12, xmm15
     ptest xmm12, xmm12
     jz @Inside

     dec X1
     jnz @X
     ret

    @Inside:

     movdqa xmm1, xmm11
     mulps xmm1, xmm10
     movdqa xmm4, xmm1

     mulps xmm1, xmm14
     movdqa xmm0, xmm1
     psrldq xmm0, 4
     addss xmm1, xmm0
     psrldq xmm0, 4
     addss xmm1, xmm0

     movd xmm0, R13d //Единица должна быть в R13
     divss xmm0, xmm1

     comiss xmm0, dword ptr [r10]
     jb @Below
     dec X1
     jnz @X
     ret

    @Below:
     movd dword ptr [r10], xmm0 //Пишем fZ
     movd dword ptr [r11], xmm0 //Результат пишем по адресу пиксела

     dec X1
     jnz @X
    end;

    //-----------------------------------------------------

    procedure ScanLineVecD256(X0, X1: Integer; P: TVertexD);
    asm
     .NOFRAME

     sub X1, X0
     inc X1

     //Маска сборки ABGR-пиксела
     movq2dq xmm3, mm6

     //Множитель 255.0
     movd xmm11, [M255]
     shufps xmm11, xmm11, 00b

     //Координаты точки
     //vmovdqu ymm10, (R8) P
     db $C4, $41, $7E, $6F, $10

    @X:

     add r10, 4
     add r11, 4

     //vaddpd ymm10, ymm10, ymm13
     db $C4, $41, $2D, $58, $D5

     //vandpd ymm0, ymm10, ymm15
     db $C4, $C1, $2D, $54, $C7
     //vxorpd ymm0, ymm0, ymm15
     db $C4, $C1, $7D, $57, $C7
     //vptest ymm0, ymm0
     db $C4, $E2, $7D, $17, $C0

     jz @Inside
     dec X1
     jnz @X
     ret

    @Inside:

     //vmovdqa ymm1, ymm12
     db $C5, $7D, $7F, $E1

     //vmulpd ymm1, ymm1, ymm10
     db $C4, $C1, $75, $59, $CA

     //vmovdqa ymm4, ymm1
     db $C5, $FD, $7F, $CC

     //vmulpd ymm1, ymm1, ymm14
     db $C4, $C1, $75, $59, $CE

     //vextractf128 xmm2, ymm1, 01b
     db $C4, $E3, $7D, $19, $CA, $01

     //Складываем (X+Y)+Z
     addsd xmm2, xmm1
     psrldq xmm1, 8
     addsd xmm1, xmm2

     //Загружаем единицу
     movq xmm0, R13 //Единица должна быть в R13

     //Перспективная Z-интерполяция
     //1.0 / (PL.X * Z.X + PL.Y * Z.Y + PL.Z * Z.Z)
     divsd xmm0, xmm1

     //Конвертируем в single
     cvtsd2ss xmm0, xmm0 //fZ = xmm0

     //На входе в XMM0 должен быть fZ (тип Single)
     comiss xmm0, dword ptr [r10] //Читаем Z-Buffer
     jb @Below
     dec X1
     jnz @X
     ret

    @Below:
     movd dword ptr [r10], xmm0 //Пишем fZ
     //vcvtpd2ps xmm4, ymm4
     db $C5, $FD, $5A, $E4
     movd dword ptr [r11], xmm4 //Результат пишем по адресу пиксела

     dec X1
     jnz @X

    @Pass:
    end;

    //-----------------------------------------------------

    procedure LoadAddr(A1, A2: QWord);
    asm
     .NOFRAME

     mov R10, A1
     mov R11, A2
    end;

    //-----------------------------------------------------

    procedure StoreQWords(dest, count: qword; q: qword);
    asm
     .NOFRAME
     mov r9, rdi

     mov rdi, dest
     mov rcx, count
     mov rax, q
     rep stosq

     mov rdi, r9
    end;

    //-----------------------------------------------------

    function SingleAsQWord(S: Single): UInt64;
    asm
     .NOFRAME

     movd eax, S
     mov r8d, eax
     shl rax, 32
     or rax, r8
    end;

    //-----------------------------------------------------
  • dmk © (10.06.18 21:01) [8]
    Вторая часть:


    const
     Len: Integer = 100;
     N: Integer = 10000000;

    var
     P1, P2: Pointer;
     Q1, Q2: QWord;
     QV1, QV2: QWord;
     i: Integer;
     P, Step, Z: TVertexD;
     CV: TTripleVertex;
     Area, tt: Double;
     ts, te: QWord;

    begin
     GetMem(P1, Len * SizeOf(Single));
     GetMem(P2, Len * SizeOf(Single));

     P.X := -25.0;
     P.Y := -121.0;
     P.Z := -21.0;
     P.W := 1.0;

     Step.X := -1.0;
     Step.Y := -1.0;
     Step.Z := -1.0;
     Step.W := 0.0;

     CV.C0.X := 1.0;
     CV.C0.Y := 1.0;
     CV.C0.Z := 1.0;
     CV.C0.W := 1.0;

     CV.C1.X := 0.0;
     CV.C1.Y := 0.0;
     CV.C1.Z := 0.0;
     CV.C1.W := 1.0;

     CV.C2.X := 1.0;
     CV.C2.Y := 1.0;
     CV.C2.Z := 1.0;
     CV.C2.W := 1.0;

     Z.X := 100.57;
     Z.Y := 98.15;
     Z.Z := 101.3;
     Z.W := 1.0;

     Area := -0.00340202034;

     Q1 := QWord(P1);
     Q2 := QWord(P2);

     QV1 := SingleAsQWord(5000.0);

     try
       // XMM-сканлиния ----------------------------------

       LoadDataS(@CV, @Step, @Z, Area);

       ts := GetTickCount;

       //Рисуем сканлинии
       for i := 0 to N do
       begin
         //Обновим адрес
         LoadAddr(Q1, Q2);
         //Рисуем сканлинию
         ScanLineVecS(0, Len - 1, P);
         //Очистка буфера
         StoreQWords(Q1, Len shr 1, QV1);
       end;

       te := GetTickCount;
       tt := (te - ts) / 1000.0;

       Writeln('Test XMM: ' + FloatToStrF(tt, ffNumber, 18, 2) + ' сек. (' + FloatToStrF(N / tt, ffNumber, 18, 2) + ' в сек.)');

       // AVX-сканлиния ----------------------------------

       LoadDataD256(@CV, @Step, @Z, Area);

       ts := GetTickCount;

       //Рисуем сканлинии
       for i := 0 to N do
       begin
         //Обновим адрес
         LoadAddr(Q1, Q2);
         //Рисуем сканлинию
         ScanLineVecD256(0, Len - 1, P);
         //Очистка буфера
         StoreQWords(Q1, Len shr 1, QV1);
       end;

       te := GetTickCount;
       tt := (te - ts) / 1000.0;

       Writeln('Test AVX: ' + FloatToStrF(tt, ffNumber, 18, 2) + ' сек. (' + FloatToStrF(N / tt, ffNumber, 18, 2) + ' в сек.)');
       Readln;

     except
     end;

     FreeMem(P1);
     FreeMem(P2);
    end.
  • dmk © (10.06.18 23:51) [9]
    Перенес во FreePascal - то же самое. XMM шустро, а AVX тормоза жуткие.
  • invis © (11.06.18 03:06) [10]
    Добавь vzeroupper после каждого перехода от AVX к SSE, и нормальную загрузку единицы (которая DoubleOne) в R13 для AVX.
    Вообще сомнительная практика - грузить регистры в одной функции, использовать в другой. Даже если "вроде работает".
    Ну и традиционное пожелание: не маяться дурью и переходить на Си.
  • dmk © (11.06.18 11:55) [11]
    И с vzeroupper пробовал и без - никакой разницы.
    Предзагрузка переменных в регистры - это обычная практика еще со времен DOS'а.
    Зачем мне счетчик в памяти, если у меня куча пустых регистров простаивает?
    ScanLine - это всего лишь часть большей процедуры. ~3Kb кода.

    >не маяться дурью и переходить на Си.
    Студия 2018 уже стоит. Дизассемблирование кода c++ показывает, что у c++ преимуществ перед Pascal нет.Только всякие вкусняшки на уровне языка. К ним еще привыкнуть надо.
  • Pavia © (11.06.18 13:29) [12]
    У меня тоже тормозит
    Test XMM: 9,50 сек. (1 052 631,58 в сек.)
    Test AVX: 427,84 сек. (23 373,01 в сек.)
  • dmk © (11.06.18 13:53) [13]
    >У меня тоже тормозит
    А у Вас OS какая?
  • dmk © (11.06.18 13:58) [14]
    Самое интересное, что если отключить AVX-вычисления и оставить просто загрузку в YMM-регистры, то скорость не падает. Т.е. работает железка, причем очень шустро.

    Просто оставил вот этот кусок кода:
     //vmovapd ymm10, [r8]
     db $C4, $41, $7D, $28, $10


    Никаких тормозов. Очень быстро. Только массив пришлось выравнивать на 32 байта.
    Рекомендация Intel.
  • invis © (11.06.18 17:50) [15]
    https://drive.google.com/open?id=1HcYPkg-b2WXtijVDDpWNrNuNsAQXy0GU


    > Дизассемблирование кода c++ показывает, что у c++ преимуществ
    > перед Pascal нет


    Надо уметь так писать, чтобы были преимущества. Например, (полу)автоматическая векторизация в Си хоть как-то работает, а в Дельфи её совсем нет.
  • dmk © (11.06.18 19:12) [16]
    >автоматическая векторизация в Си хоть как-то работает
    Это просто библиотеки. Они к языку никакого отношения не имеют.
    У меня своя векторная 2D/3D библиотека для Delphi, так что и Delphi теперь умеет :)
    Это дело наживное. Просто для C++ действительно много библиотек написано.
    А термин автоматическая векторизация для меня мало понятен.

    Если речь о таком (см. заголовки), то у меня есть немного.


    type
     PVertex = ^TVertex;
     TVertex = packed record
       X, Y, Z, W: Single;
     public
       class operator Add(A, B: TVertex): TVertex; overload;
       class operator Add(A: TVertex; N: Single): TVertex; overload;
       class operator Subtract(A, B: TVertex): TVertex; overload;
       class operator Subtract(A: TVertex; N: Single): TVertex; overload;
       class operator Negative(A: TVertex): TVertex;
       class operator Positive(A: TVertex): TVertex;
       class operator Inc(A: TVertex): TVertex; inline;
       class operator Dec(A: TVertex): TVertex; inline;
       class operator Trunc(A: TVertex): TVertex; inline;
       class operator Round(A: TVertex): TVertex; inline;
       class operator Multiply(A, B: TVertex): TVertex; overload;
       class operator Multiply(A: Single; B: TVertex): TVertex; overload;
       class operator Multiply(A: TVertex; B: Single): TVertex; overload;
       class operator Multiply(A: TVertex; M: TMatrix4): TVertex; overload;
       class operator Divide(A: TVertex; B: Single): TVertex; overload;
       class operator Divide(A: Single; B: TVertex): TVertex; overload;
       function Angle(A, B: TVertex): Single; inline; //Угол между двумя точками
       function Cross(A, B: TVertex): TVertex; inline; //Перпендикуляр к вектору
       function Distance(A, B: TVertex): Single; inline; //Расстояние между точками
       function Dot(A, B: TVertex): Single; inline; //Скалярное произведение векторов
       function Length(A: TVertex): Single; inline;
       function Middle(A, B: TVertex): TVertex; inline; //Середина вектора
       function Normalize(A: TVertex): TVertex; inline;
       procedure NormalizeAngles(A: TVertex); inline;
       function Scale(A, B: TVertex; F: Single): TVertex; inline; //Масштабирует вектор F = [0..1]
       procedure Split(var X, Y, Z: Single); inline;
     end;
  • dmk © (11.06.18 19:15) [17]
    >Надо уметь так писать, чтобы были преимущества.
    Asssembler = Assembler.
    Какие у ассемблера перед ассемблером могут быть преимущества?
  • invis © (11.06.18 22:27) [18]
    Преимущество в том, что пишем на Си (ну может с незначительными интринсиковыми вставками), а получаем ту же скорость, что и на ассемблере.
    Есть внешние библиотеки, есть встроенные в язык средства. Но даже внешние работают куда лучше чем в Дельфи по причине более эффективного inline.
  • invis © (11.06.18 23:19) [19]
    Про инлайн (FPC, но в Дельфи то же самое):
    http://www.sql.ru/forum/1281542-3/itogi-2017-goda?mid=21132202#21132202
    Автовекторизация:
    https://godbolt.org/g/Uv318K
    компилятору никто не говорил, что там можно применить SIMD (цикл vmovupd/vcmpltpd), но он сам догадался.
 
Конференция "Начинающим" » Опкоды AVX
Есть новые Нет новых   [134427   +34][b:0][p:0.003]