Помогите преобразовать код для Delphi

dimon72

Пытаюсь сделать функцию умножения матрицы на вектор с использованием SSE-инструкций.
Использую Delphi и в СPP/Asm'е рублю не особо.
Имею: пример функции умножения матрицы на вектор для C++ с использованием SSE.
Требуется: переделать её (функцию) в виде процедуры с ASM вставкой для Delphi.
Вот текст функции:
// MatrixMultiply3 — C++/ASM версия MatrixMultiply2, которая демонстрирует
// преимущество Intel SSE инструкций. Эта версия требует, чтобы матрица
// M была записана в порядке row-major.
//
// Выполнение: 57 циклов/вектор
void MatrixMultiply3(Matrix4f &m, Vector4f *vin, Vector4f *vout)
{
// Получить указатель на элементы m
float *row0 = m.Ref;
__asm {
mov esi, vin
mov edi, vout
// загрузить матрицу по столбцам в регистры xmm4-7
mov edx, row0
movups xmm4, [edx]
movups xmm5, [edx+0x10]
movups xmm6, [edx+0x20]
movups xmm7, [edx+0x30]
// загрузить v в xmm0.
movups xmm0, [esi]
// конечный результат будет храниться в xmm2;
// инициализируем его нулем
xorps xmm2, xmm2
// записать x в xmm1, умножить его на первый
// столбец матрицы (xmm4 и добавить его к результату
movups xmm1, xmm0
shufps xmm1, xmm1, 0x00
mulps xmm1, xmm4
addps xmm2, xmm1
// повторить для y, z и w
movups xmm1, xmm0
shufps xmm1, xmm1, 0x55
mulps xmm1, xmm5
addps xmm2, xmm1
movups xmm1, xmm0
shufps xmm1, xmm1, 0xAA
mulps xmm1, xmm6
addps xmm2, xmm1
movups xmm1, xmm0
shufps xmm1, xmm1, 0xFF
mulps xmm1, xmm7
addps xmm2, xmm1
// записать результат в vout
movups [edi], xmm2
}
}
Я, может, и сам бы допер, но пока что никак не пойму следующее место:
// Получить указатель на элементы m
float *row0 = m.Ref;
row0 - это некоторая переменная типа float, но что такое m.Ref и для чего это нужно?
Пример кода взят отсюда: http://www.democoder.ru/dcdn_article_view.php?dcid=13

okunek

m.Ref и для чего это нужно?
возвращает указатель на матрицу...

dimon72

Да, сейчас я чуточку стал понимать:
float *row0 = m.Ref значит, что объявляется указатель *row0 с модификатором Ref.
Указатель *row0 устанавливается на адрес первого элемента масcива.
Модификатор говорит о том, что элементы массива функция может изменять.
Не совсем мне понятно здесь:
mov edx, row0
movups xmm4, [edx]
movups xmm5, [edx+0x10]
movups xmm6, [edx+0x20]
movups xmm7, [edx+0x30]
Судя по всему, какая-то косвенная адресация. Т.е, сначала берется элемент первого массива. Но почему сдвиг на 16?
Как всё это на Delphi-то заваять?

okunek

float *row0 = m.Ref значит, что объявляется указатель *row0 с модификатором Ref.
чиво? здесь вызывается функция Ref для объекта m, которая (функция) возвращает указатель, который присваивается переменной row0
Но почему сдвиг на 16?
потому что xmm-регистры по 16 байт

dimon72

Итак, после ночного бдения я кое в чём разобрался.
Приведённый мною в первом посте код я почти вкурил.
Переформулирую вопрос. Решил упростить задачу и для начала получить на выходе вектор - произведение двух 4х компонентных векторов.
У меня получился код, который даёт результат (видно в режиме отладки, когда подводишь курсор но программа падает.
Не могу разобраться. В чём может быть дело? Помогите, пожалуйста!
Итак, создана форма, на которой лежат Memo1 и Botton5.
Memo1 используется для вывода результата;
при нажатии на Botton5 собственно и происходит попытка перемножения двух, тут же
создаваемых векторов.
Вот текст программы:
procedure TForm1.Button5Click(Sender: TObject);
Type
TArr = array [0..3] of single;
PArr = ^TArr;
var
i: integer;
V0: TArr;
V1: TArr;
Y2: TArr;
X1: PArr;
C1: PArr;
Y1: PArr;
begin
//создаю указатели на соответствующие массивы
new(X1);
X1 := @V0;
new(C1);
C1 := @V1;
new(Y1);
Y1 := @Y2;
//заполняю входные массивы
//для первого операнда
V0[0]:=6;
V0[1]:=3;
V0[2]:=7;
V0[3]:=3;
//для второго операнда
V1[0]:=4;
V1[1]:=5;
V1[2]:=8;
V1[3]:=7;
//для вектора- результата
Y2[0]:=0;
Y2[1]:=0;
Y2[2]:=0;
Y2[3]:=0;
asm
mov ebx, X1
mov edx, C1
mov edi, Y1
// загрузить векторы по указанным адресам в регистры xmm0-1
movups xmm0,[ebx]
movups xmm1, [edx]
mulps xmm1, xmm0
// записать результат
movups [edi], xmm1
end;
//здесь получаю падение программы (acess violation)!
//несмотря на это в режиме отладки, когда подвожу курсор к переменной Y2, вижу
//правильный результат: Y2=(24,15,56,21);
//где косяк?!!!
Memo1.Clear;
For i:=0 to High(Y2) do
Memo1.Lines.Add( FloatToStr(Y2[i]) );
end;

SPARTAK3959

В ассемблерной вставке (по умолчанию) разрешено менять только регистры eax,ecx,edx.

dimon72

В ассемблерной вставке (по умолчанию) разрешено менять только регистры eax,ecx,edx.
Да, я догадался поменять регистры и заработало. В любом случае спасибо!
Вообще, затеяна вся эта бадяга из-за необходимости сделать быструю FIR-фильтрацию с применением SSE инструкций (на Delphi).
Исходно алгоритм фильтрации выглядит так (для С++):
-----------------------------------
Y[z] = 0;
for (i = 0; i < N; i++) Y[z] = Y[z] + X[z+i-N/2]*C[i];
---------------------------

z -номер отсчета, N - длина фильтра, X - входной одномерный массив, Y - выходной одномерный массив ,
С - коэффициенты.
Вот так функция выглядит для Delphi:
type TMatrix = array of array of double;
TVector = array of double;
procedure TFmPassAj.FIRFilteringAll2(var X: TVector; var C: TVector; var Y: TVector);
var
i: integer;
z: integer;
N: integer;
W2: integer;
begin
// z -номер отсчета, N - длина фильтра, X - вход, Y - выход , С - коэффициенты.
N:=High(C);
W2:=Trunc(N/2);
Finalize(Y);
SetLength(YHigh(X)+1-W2;
For z:=W2 to High(X) do
begin
For i:=0 to N do
Y[z]:=Y[z]+X[z+i-W2]*C[i];
end;
end;
Вопрос: как здесь можно приспособить команды mulps и addps SSE?
Как я понял, они обработывают одновременно по 4 пары входных значений. А если у меня имеется большой одномерный массив (вектор который нужно, скажем, перемножить с некоторым другим большим вектором, или, как в случае FIR-фильтрации, прогнать по вектору скользящее окно (осуществить свертку с некоторым вектором коэффициентов фильтра).
Получается, что нужно пользоваться указателями на массивы и как-то их смещать каждый раз на 4 значения и подавать на вход SSE командам. Как это сделать практически? Вопросов куча: как рационально организовать такой цикл, как узнать, что массив кончился при сдвиге указателей?
И ещё вопрос:
Есть две различные инструкции пересылки данных: movups (невыровненное MOV) и movaps (выровненное MOV). movaps намного быстрее чем movups, но требует, чтобы аргументы были выровнены по 16-байтным границам при пересылке в/из памяти (иначе, иструкция вызывает исключение).
Как сделать 16-байтное выравнивание матриц и векторов в Delphi?
Оставить комментарий
Имя или ник:
Комментарий: