-
Всем привет! Есть ли у кого возможность проверить AVX-опкоды? Взял опкоды из FreePascal и вставил в Delphi. Вроде все работает, но очень медленно. Может опкоды некорректные или Windows эмулятором их исполняет. На самом деле скорость должна быть очень высокая, т.к. XMM-версия этого кода работает раз в 20 быстрее.
db $C4, $41, $7E, $6F, $10
db $C4, $41, $2D, $58, $D5
db $C4, $C1, $2D, $54, $C7
db $C4, $C1, $7D, $57, $C7
db $C4, $E2, $7D, $17, $C0
jz @Inside
dec X1
jnz @X
ret
@Inside:
db $C5, $7D, $7F, $E1
db $C4, $C1, $75, $59, $CA
db $C5, $FD, $7F, $CC
db $C4, $C1, $75, $59, $CE
db $C4, $E3, $7D, $19, $CA, $01
addsd xmm2, xmm1
psrldq xmm1, 8
addsd xmm1, xmm2
movq xmm0, R13
divsd xmm0, xmm1
cvtsd2ss xmm0, xmm0
comiss xmm0, dword ptr [r10]
jb @Below
dec X1
jnz @X
ret
@Below:
movd dword ptr [r10], xmm0
shufps xmm0, xmm0, 00b
db $C5, $FD, $5A, $E4
-
-
Так а можно полностью функцию-то? Обе версии? И пример входящих/результирующих данных? Тогда и запустим с радостью. :)
-
Процедура просто закрашивает полигон. Ничего интересного. Она вообще-то работает, но очень медленно. Такое ощущение, что код пропускается через эмулятор.
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;
-
> Такое ощущение, что код пропускается через эмулятор.
А почему нет? Интел как раз таки выпускала эмулятор для AVX. Может по ошибки установили? А ваш проц точно поддерживает AVX?
Вы бы код целиком выложили тогда потестировать можно было-бы.
-
У меня и 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;
-
В общем вот рабочий код. Только под 64 бита.
У меня получаются такие значения: Test XMM: 2,36 сек. (4 239 084,36 в сек.) Test AVX: 149,95 сек. (66 687,56 в сек.) 10 млн. сканлиний с очисткой буфера. В коде константа N. Похоже, что исполняется на эмуляторе, а не на железе. Код одинаковый. Разница лишь в типах данных: 128 бит и 256 бит.
-
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;
//-----------------------------------------------------
-
Вторая часть:
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.
-
Перенес во FreePascal - то же самое. XMM шустро, а AVX тормоза жуткие.
-
Добавь vzeroupper после каждого перехода от AVX к SSE, и нормальную загрузку единицы (которая DoubleOne) в R13 для AVX. Вообще сомнительная практика - грузить регистры в одной функции, использовать в другой. Даже если "вроде работает". Ну и традиционное пожелание: не маяться дурью и переходить на Си.
-
И с vzeroupper пробовал и без - никакой разницы. Предзагрузка переменных в регистры - это обычная практика еще со времен DOS'а. Зачем мне счетчик в памяти, если у меня куча пустых регистров простаивает? ScanLine - это всего лишь часть большей процедуры. ~3Kb кода.
>не маяться дурью и переходить на Си. Студия 2018 уже стоит. Дизассемблирование кода c++ показывает, что у c++ преимуществ перед Pascal нет.Только всякие вкусняшки на уровне языка. К ним еще привыкнуть надо.
-
У меня тоже тормозит Test XMM: 9,50 сек. (1 052 631,58 в сек.) Test AVX: 427,84 сек. (23 373,01 в сек.)
-
>У меня тоже тормозит А у Вас OS какая?
-
Самое интересное, что если отключить AVX-вычисления и оставить просто загрузку в YMM-регистры, то скорость не падает. Т.е. работает железка, причем очень шустро.
Просто оставил вот этот кусок кода:
//vmovapd ymm10, [r8] db $C4, $41, $7D, $28, $10
Никаких тормозов. Очень быстро. Только массив пришлось выравнивать на 32 байта. Рекомендация Intel.
-
-
>автоматическая векторизация в Си хоть как-то работает Это просто библиотеки. Они к языку никакого отношения не имеют. У меня своя векторная 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;
-
>Надо уметь так писать, чтобы были преимущества. Asssembler = Assembler. Какие у ассемблера перед ассемблером могут быть преимущества?
-
Преимущество в том, что пишем на Си (ну может с незначительными интринсиковыми вставками), а получаем ту же скорость, что и на ассемблере. Есть внешние библиотеки, есть встроенные в язык средства. Но даже внешние работают куда лучше чем в Дельфи по причине более эффективного inline.
-
|