Неэффективная процедура встроенной сборки в Delphi с использованием SSE2
У меня есть простая операция с плавающей точкой, которая всегда выполняется дважды. Поэтому я попытался перевести его на SSE, но он просто не удался. Язык высокого уровня - Delphi, так как он не поддерживает функции Intrinsics, я должен написать все целиком. В основном у меня просто есть параметр загрузки / выгрузки и некоторые умножения и дополнения.
Procedure TLP1Poly2.Process(Const _a1, _b1, _OldIn1, _OldIn2, _OldOut1, _OldOut2: Double; Var Sample1, Sample2: Double);
Asm
MOVLPD XMM4, _a1
MOVHPD XMM4, _a1
MOVLPD XMM3, _b1
MOVHPD XMM3, _b1
//
MOVLPD XMM0, [Sample1]
MOVHPD XMM0, [Sample2]
MULPD XMM0, XMM4
//
MOVLPD XMM1, _OldIn1
MOVHPD XMM1, _OldIn2
MULPD XMM1, XMM4
//
MOVLPD XMM2, _OldOut1
MOVHPD XMM2, _OldOut2
MULPD XMM2, XMM3
//
ADDPD XMM0, XMM1
ADDPD XMM0, XMM2
//
MOVLPD [Sample1], XMM0
MOVHPD [Sample2], XMM0
//
// which stands for twice this:
// Sample:= Sample*a1 + oldinp*a1 + oldout*b1;
//
End;
но эта процедура не работает, если я 'nop' все между загрузкой / сохранением Sample1/Sample2, это нормально, но в противном случае мой фильтр молчит. Что является основным, что я не получаю с SSE в этом?
Addenum:
старый класс класс:
constructor TLP1.create;
begin
oldfreq := -1 ;
end;
procedure TLp1.process(inp,Frq,SR :single);
begin
if Frq<>oldfreq then
begin
a := 2* SR;
t := Frq * _ppi;
n := 1/ (a+t) ;
b1:= (a - t) * n;
a1:= t * n;
oldfreq := frq;
end;
outlp := (inp+_kd)*a1 + oldinp*a1 + oldout*b1;
oldout := outlp ;
oldinp := inp;
end;
Новый класс:
Procedure TLP2Poly2.SetSamplerate(Const Value: Single);
Begin
If Value = FSamplerate Then Exit;
FSamplerate := Value;
UpdateCoefficients;
End;
Procedure TLP2Poly2.SetFrequency(Const Value: Single);
Begin
If Value = FFrequency Then Exit;
FFrequency := Value;
UpdateCoefficients;
End;
Procedure TLP2Poly2.UpdateCoefficients;
Var
a,t,n: Single;
Begin
a := 2 * FSamplerate ;
t := FFrequency * 2 * pi;
n := 1/ (a+t) ;
b1:= (a - t) * n;
a1:= t * n;
End;
Procedure TLP2Poly2.Process(Var Sample1, Sample2: Double);
Var
o1, o2: Double;
Begin
o1 := Sample1;
o2 := Sample2;
IntProcess( a1, b1, OldIn1, OldIn2, OldOut1, OldOut2, Sample1, Sample2);
OldOut1 := Sample1;
OldOut2 := Sample2;
OldIn1 := o1;
OldIn2 := o2;
End;
Procedure TLP2Poly2.IntProcess(Const _a1, _b1, _OldIn1, _OldIn2, _OldOut1, _OldOut2: Double; Var Sample1, Sample2: Double);
Asm
MOVLPD XMM4, _a1
MOVHPD XMM4, _a1
MOVLPD XMM3, _b1
MOVHPD XMM3, _b1
//
MOVLPD XMM0, [Sample1]
MOVHPD XMM0, [Sample2]
MULPD XMM0, XMM4
//
MOVLPD XMM1, _OldIn1
MOVHPD XMM1, _OldIn2
MULPD XMM1, XMM4
//
MOVLPD XMM2, _OldOut1
MOVHPD XMM2, _OldOut2
MULPD XMM2, XMM3
//
ADDPD XMM0, XMM1
ADDPD XMM0, XMM2
//
MOVLPD [Sample1], XMM0
MOVHPD [Sample2], XMM0
End;
2 ответа
При написании ассемблера для Delphi, особенно в 64-битном режиме, вы всегда должны знать, как передаются параметры. Я никогда не использую имена первых 4 параметров, так как они в любом случае есть в регистрах. Я использую эти регистры напрямую.
Обратите внимание, что _a1
, _b1
, _oldIn1
а также _oldIn2
передаются в XMM0 - XMM3 соответственно, поэтому первая часть вашего кода перезаписывает некоторые из этих регистров. Например, загрузка XMM3 с _b1
переписал бы _oldIn2
, То же самое происходит с XMM2, который содержит _oldIn1
,
Было бы целесообразно изменить порядок использования регистра, чтобы вам не приходилось использовать память в качестве промежуточного элемента.
IOW, попробуйте что-то вроде (не проверено):
asm
MOVDDUP XMM0,XMM0
MOVDDUP XMM1,XMM1
MOVLPD XMM4,[Sample1]
MOVHPD XMM4,[Sample2]
MULPD XMM4,XMM0
// etc...
В Delphi есть панель отладчика ("FPU"), которая показывает регистры SSE. Так что, если вы подадите свой фильтр на ненулевые значения, вы сможете найти источник бесшумного вывода.