Я пытаюсь загрузить 128-битный регистр xmm
с двумя UInt64
целыми числами в Delphi (XE6).
Фон
Регистр XMM 128-битный,и может быть загружен несколькими независимыми целыми числами.Затем процессор может добавить эти несколько целых чисел параллельно.
Например, вы можете загрузить xmm0 и xmm1 с четырьмя UInt32s каждый, а затем процессор может добавить все четыре пары одновременно.
xmm0: $00001000 $00000100 $00000010 $00000001
+ + + +
xmm1: $00002000 $00000200 $00000020 $00000002
= = = =
xmm0: $00003000 $00000300 $00000030 $00000003
После загрузки xmm0 и xmm0 вы выполняете сложение четырех пар, используя:
paddd xmm0, xmm1 //Add packed 32-bit integers (i.e. xmm0 := xmm0 + xmm1)
Вы также можете сделать это, используя 8 x 16-битных целых чисел:
xmm0: $001F $0013 $000C $0007 $0005 $0003 $0002 $0001
+ + + + + + + +
xmm1: $0032 $001F $0013 $000C $0007 $0005 $0003 $0002
= = = = = = = =
xmm0: $0051 $0032 $001F $0013 $000C $0007 $0005 $0003
С инструкцией
paddw xmm0, xmm1 //Add packed 16-bit integers
Теперь для 64-разрядных целых чисел
Чтобы загрузить два 64-разрядных целых числа в регистр xmm
, необходимо использовать либо:
- movdqu: переместить двойное слово (без выравнивания)
- movdqa: переместить двойное слово (выровнено)
В этом простом примере мы не будембеспокоиться о том, чтобы наши UInt64 были выровнены, и мы просто будем использовать unaligned версию (movdqu
)
Первое, с чем нам придется иметь дело, это то, что Delphi-компилятор знает, что movdqu
для загрузки требуется 128-битное что-то - оно загружает double quadwords.
Для этого мы будемсоздать 128-битную структуру, которая также позволяет нам обращаться к двум 64-битным значениям:
TDoubleQuadword = packed record
v1: UInt64; //value 1
v2: UInt64; //value 2
end;
И теперь мы можем использовать этот тип в тестовом консольном приложении:
procedure Main;
var
x, y: TDoubleQuadword;
begin
//[1,5] + [2,7] = ?
x.v1 := $0000000000000001;
x.v2 := $0000000000000005;
y.v1 := $0000000000000002;
y.v2 := $0000000000000007;
asm
movdqu xmm0, x //move unaligned double quadwords (xmm0 := x)
movdqu xmm1, y //move unaligned double quadwords (xmm1 := y)
paddq xmm0, xmm1 //add packed quadword integers (xmm0 := xmm0 + xmm1)
movdqu x, xmm0 //move unaligned double quadwords (x := xmm0)
end;
WriteLn(IntToStr(x.v1)+', '+IntToSTr(x.v2));
end;
И это работает, распечатывая:
3, 12
Взгляд на приз
С целью достижения выравнивания x и y (, но не является необходимой частью моего вопроса ), допустим, у нас есть указатель на TDoubleQuadword
структуру:
TDoubleQuadword = packed record
v1: UInt64; //value 1
v2: UInt64; //value 2
end;
PDoubleQuadword = ^TDoubleQuadword;
мы теперь изменим наш гипотетический тест функция для использования PDoubleQuadword
:
procedure AlignedStuff;
var
x, y: PDoubleQuadword;
begin
x := GetMemory(sizeof(TDoubleQuadword));
x.v1 := $0000000000000001;
x.v2 := $0000000000000005;
y := GetMemory(sizeof(TDoubleQuadword));
y.v1 := $0000000000000002;
y.v2 := $0000000000000007;
asm
movdqu xmm0, x //move unaligned double quadwords (xmm0 := x)
movdqu xmm1, y //move unaligned double quadwords (xmm1 := y)
paddq xmm0, xmm1 //add packed quadword integers (xmm0 := xmm0 + xmm1)
movdqu x, xmm0 //move unaligned double quadwords (v1 := xmm0)
end;
WriteLn(IntToStr(x.v1)+', '+IntToSTr(x.v2));
end;
Теперь это не компилируется, и имеет смысл, почему:
movdqu xmm0, x //E2107 Operand size mismatch
Это имеет смысл.Аргумент x
должен быть 128-битным, и компилятор знает, что x
действительно является только (32-битным) указателем.
Но что это должно быть?
Теперь мы подошли к моему вопросу: что это должно быть?Я случайно пюрею разные вещи на своей клавиатуре, надеясь, что боги компилятора просто примут то, что я имею в виду.Но ничего не работает.
//Don't try to pass the 32-bit pointer itself, pass the thing it points to:
movdqu xmm0, x^ //E2107 Operand size mismatch
//Try casting it
movdqu xmm0, TDoubleQuadword(x^) //E2105 Inline assembler error
//i've seen people using square brackets to mean "contents of":
movdqu xmm0, [x] //E2107 Operand size mismatch
И теперь мы отказываемся от рационального мышления
movdqu xmm0, Pointer(x)
movdqu xmm0, Addr(x^)
movdqu xmm0, [Addr(x^)]
movdqu xmm0, [Pointer(TDoubleQuadword(x))^]
Я получил одну вещь для компиляции:
movdqu xmm0, TDoubleQuadword(x)
Но конечнокоторый загружает в регистр адрес из x
, а не значения внутри x.
Так что я сдаюсь.
Complete Minimal Example
program Project3;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
TDoubleQuadword = packed record
v1: UInt64; //value 1
v2: UInt64; //value 2
end;
PDoubleQuadword = ^TDoubleQuadword;
TVectorUInt64 = array[0..15] of UInt64;
PVectorUInt64 = ^TVectorUInt64;
procedure AlignedStuff;
var
x, y: PVectorUInt64;
begin
x := GetMemory(sizeof(TVectorUInt64));
//x[0] := ...
//x[1] := ...
// ...
//x[3] := ...
x[4] := $0000000000000001;
x[5] := $0000000000000005;
y := GetMemory(sizeof(TVectorUInt64));
//y[0] := ...
//y[1] := ...
// ...
//y[3] := ...
y[4] := $0000000000000002;
y[5] := $0000000000000007;
asm
movdqu xmm0, TDoubleQuadword(x[4]) //move unaligned double quadwords (xmm0 := x)
movdqu xmm1, TDoubleQuadword(y[4]) //move unaligned double quadwords (xmm1 := y)
paddq xmm0, xmm1 //add packed quadword integers (xmm0 := xmm0 + xmm1)
movdqu TDoubleQuadword(x[4]), xmm0 //move unaligned double quadwords (v1 := xmm0)
end;
WriteLn(IntToStr(x[4])+', '+IntToSTr(x[5]));
end;
begin
try
AlignedStuff;
Writeln('Press enter to close...');
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Указатель?
Причина, по которой вопрос задается об указателях, заключается в том, что:
- вы не можете использовать переменные стека (Delphi не гарантирует выравнивание переменных стека)
- вы можете скопировать их в регистр (например, EAX), но затем вы выполняете потерянное копирование и вызов функции
- у меня уже есть данные, выровненные в памяти
Если я приведу пример кода, который просто включает в себя добавление UInt64s:
TVectorUInt64 = array[0..15] of UInt64;
PVectorUInt64 = ^TVectorUInt64;
var
v: PVectorUInt64;
begin
v := GetMemoryAligned(sizeof(TVectorUInt64), 64); //64-byte alignment
//v is initalized
for i := 0 to 15 do
begin
v[0] := v[0] + v[4];
v[1] := v[1] + v[5];
v[2] := v[2] + v[6];
v[3] := v[3] + v[7];
//..and some more changes to v0..v3
//..and some more changes to v12..v15
v[8] := v[8] + v[12];
v[9] := v[9] + v[13];
v[10] := v[10] + v[14];
v[11] := v[11] + v[15];
//...and some more changes to v4..v7
v[0] := v[0] + v[4];
v[1] := v[1] + v[5];
v[2] := v[2] + v[6];
v[3] := v[3] + v[7];
//...and some more changes to v0..v3
//...and some more changes to v12..v15
v[8] := v[8] + v[12];
v[9] := v[9] + v[13];
v[10] := v[10] + v[14];
v[11] := v[11] + v[15];
//...and some more changes to v4..v7
v[0] := v[0] + v[4];
v[1] := v[1] + v[5];
v[2] := v[2] + v[6];
v[3] := v[3] + v[7];
//..and some more changes to v0..v3
//..and some more changes to v12..v15
v[8] := v[8] + v[12];
v[9] := v[9] + v[13];
v[10] := v[10] + v[14];
v[11] := v[11] + v[15];
//...and some more changes to v4..v7
v[0] := v[0] + v[4];
v[1] := v[1] + v[5];
v[2] := v[2] + v[6];
v[3] := v[3] + v[7];
//...and some more changes to v0..v3
//...and some more changes to v12..v15
v[8] := v[8] + v[12];
v[9] := v[9] + v[13];
v[10] := v[10] + v[14];
v[11] := v[11] + v[15];
//...and some more changes to v4..v7
end;
Концептуально очень легко изменить код на:
//v[0] := v[0] + v[4];
//v[1] := v[1] + v[5];
asm
movdqu xmm0, v[0]
movdqu xmm1, v[4]
paddq xmm0, xmm1
movdqu v[0], xmm0
end
//v[2] := v[2] + v[6];
//v[3] := v[3] + v[7];
asm
movdqu xmm0, v[2]
movdqu xmm1, v[6]
paddq xmm0, xmm1
movdqu v[2], xmm0
end
//v[8] := v[8] + v[12];
//v[9] := v[9] + v[13];
asm
movdqu xmm0, v[8]
movdqu xmm1, v[12]
paddq xmm0, xmm1
movdqu v[8], xmm0
end
//v[10] := v[10] + v[14];
//v[11] := v[11] + v[15];
asm
movdqu xmm0, v[10]
movdqu xmm1, v[14]
paddq xmm0, xmm1
movdqu v[10], xmm0
end
Хитрость заключается в получении Delphiкомпилятор, чтобы принять его.
- он работает для непосредственных данных
- он не работает для poИнтер к данным
- , и вы думаете, что
[contentsOfSquareBrackets]
будет работать
Bonus Chatter
Использование решения Дэвида (накладных расходов на вызов функций) приводит к повышению производительности-7% (90 МБ / с -> 83 МБ / с пропускной способности алгоритма)
Похоже, что в компиляторе XE6 концептуально можно вызвать:
movdqu xmm0, TPackedQuadword
, ноу компилятора просто нет мозгов, чтобы позволить вам выполнить концептуальный вызов:
movdqu xmm0, PPackedQuadword^
или его моральный эквивалент.
Если это ответ, не бойтесь этого.Примите это и сформулируйте как форму ответа:
* "Компилятор не поддерживает разыменование указателя внутри блока asm
. Независимо от того, пытаетесь ли вы сделать это с помощью каретки (^
) или квадратных скобок ([...]
).Это просто невозможно.
Если это ответ, ответьте.
Если это не так, и компилятор может поддерживать указатели в asm
заблокировать, затем опубликовать ответ.