2014-12-23 4 views
2

저는 6 비트에서 8 비트의 32 비트 정수를 32 비트 실수로 변환해야하는 경우가 종종 있습니다. 필자는 델파이 코드를 사용자 정의 어셈블러 코드로 대체했으며 FPU 변환은 항상 빠르며 일부 컴퓨터에서는 SSE 변환보다 빠릅니다. 변환하는 동안 스케일링 (그래서 곱셈)이있을 필요가SSE : 대량 정수 변환 + FPE보다 SSE가 더 느리게 곱합니까?

program Project1; 

{$R *.res} 

uses 
windows,dialogs,sysutils; 

type 
piiii=^tiiii; 
tiiii=record i1,i2,i3,i4:longint; end; 
pssss=^tssss; 
tssss=record s1,s2,s3,s4:single; end; 

var 
convert_value:single=13579.02468; 

function convert_x87(adata:longint):single; 
asm 
mov [esp-4],eax 
fild longint([esp-4]) 
fmul [convert_value] 
end; 

procedure convert_sse(afrom,ato,aconv:pointer); 
asm 
CVTDQ2PS xmm0,[eax] 
mulps xmm0,[ecx] 
movaps [edx],xmm0 
end; 

procedure get_mem(var p1,p2:pointer); 
begin 
getmem(p1,31); 
p2:=pointer((longint(p1)+15) and (not 15)); 
end; 

var 
a,b,c,d:cardinal; 
z:single; 
i:piiii; 
s1,s2:pssss; 
w1,w2,w3:pointer; 
begin 
b:=gettickcount; 
a:=0; 
repeat 
    z:=convert_x87(a); 

    inc(a); 
until a=0; 
c:=gettickcount-b; 

get_mem(pointer(w1),pointer(i)); 
get_mem(pointer(w2),pointer(s1)); 
get_mem(pointer(w3),pointer(s2)); 

s1.s1:=convert_value; 
s1.s2:=convert_value; 
s1.s3:=convert_value; 
s1.s4:=convert_value; 

b:=gettickcount; 
i.i1:=0; 
i.i2:=1; 
i.i3:=2; 
i.i4:=3; 
repeat 
    convert_sse(i,s2,s1); 

    inc(i.i1,4); 
    inc(i.i2,4); 
    inc(i.i3,4); 
    inc(i.i4,4); 
until i.i1=0; 
d:=gettickcount-b; 

freemem(w1); 
freemem(w2); 
freemem(w3); 

showmessage('FPU:'+inttostr(c)+'/SSE:'+inttostr(d)); 
end. 

이 하나가 이유입니다 : 여기에 보여 일부 코드입니다. 사용 된 값은 내가 선택한 임의의 값이지만 사용 된 값에 관계없이 결과는 동일합니다. 또한 FPU와 SSE 사이의 라운딩에는 매우 작은 차이점이 있지만이 경우 중요하지 않습니다.

그러나이 코드를 실행하면 FPU 경로가 SSE 경로보다 느리지 않으며 의미가없는 것을 볼 수 있습니다. 누구나 무슨 일이 일어나고 있는지 알 겠어?


편집 : 여기 어셈블러에서 루프와 다른 소스 코드입니다. 결과는 정말 흥미 롭습니다. 증가 지침이 주석하는 경우, SSE 버전은 눈에 띄는만큼 FPU 버전보다 빠르지 만 증가 지침은 다음 포함 된 경우 그들은 거의 같은 속도입니다

program Project1; 

{$R *.res} 

uses 
windows,dialogs,sysutils; 

type 
piiii=^tiiii; 
tiiii=record i1,i2,i3,i4:longint; end; 
pssss=^tssss; 
tssss=record s1,s2,s3,s4:single; end; 

var 
convert_value:single=13579.02468; 

procedure test_convert_x87; 
asm 
// init test data 
push ebx 
xor ebx,ebx 

mov [esp-4],$98765432 

// convert and multiply 1 int32 to 1 single 
@next_loop: 
// inc [esp-4] 
fild longint([esp-4]) 
fmul [convert_value] 
fstp single([esp-8]) 

// loop 
dec ebx 
jnz @next_loop 

pop ebx 
end; 

procedure test_convert_sse(afrom,ato,aconv:pointer); 
asm 
// init test data 
push ebx 
xor ebx,ebx 

mov [eax+0],$98765432 
mov [eax+4],$98765432 
mov [eax+8],$98765432 
mov [eax+12],$98765432 

// convert and multiply 4 int32 to 4 single 
@next_loop: 
// inc [eax+0] 
// inc [eax+4] 
// inc [eax+8] 
// inc [eax+12] 
cvtdq2ps xmm0,[eax] 
mulps xmm0,[ecx] 
movaps [edx],xmm0 

// loop 
sub ebx,4 
jnz @next_loop 

pop ebx 
end; 

procedure get_mem(var p1,p2:pointer); 
begin 
getmem(p1,31); 
p2:=pointer((longint(p1)+15) and (not 15)); 
end; 

var 
b,c,d:cardinal; 
i:piiii; 
s1,s2:pssss; 
w1,w2,w3:pointer; 
begin 
b:=gettickcount; 
test_convert_x87; 
c:=gettickcount-b; 

get_mem(pointer(w1),pointer(i)); 
get_mem(pointer(w2),pointer(s1)); 
get_mem(pointer(w3),pointer(s2)); 

s1.s1:=convert_value; 
s1.s2:=convert_value; 
s1.s3:=convert_value; 
s1.s4:=convert_value; 

b:=gettickcount; 
test_convert_sse(i,s2,s1); 
d:=gettickcount-b; 

freemem(w1); 
freemem(w2); 
freemem(w3); 

showmessage('FPU:'+inttostr(c)+'/SSE:'+inttostr(d)); 
end. 
+0

빠르고 느린 버전으로 생성 된 어셈블리 코드를 게시하십시오. 이렇게하면 소수의 사람들이 파스칼을 사용하고 있고 당신의 szenario를 쉽게 다시 만들 수 있기 때문에 범람자를 찾기가 훨씬 쉬워집니다. –

+0

안녕하세요. 관심을 가져 주셔서 감사합니다. 게시 된 소스에서 convert_x87 및 convert_sse라는 함수가 어셈블러에 있는지 확인하면 붙여 넣기 할 수 있습니다. 타이밍 부분 만 델파이에 있습니다. – Marladu

+0

convert_sse라는 함수는 한 번에 4 convert + multiply를 수행하며 4 회 호출 한 번에 1을 수행하는 convert_fpu 함수와 속도가 같거나 느립니다.convert_sse 함수에서 사용 된 명령어는 CVTDQ2PS mulps movaps입니다.이 태스크에 대한 올바른 SIMD 명령어가 아닙니까? – Marladu

답변

1

중요한 것은 천천히 보인다 귀하의 asm에 관한 정보가 레지스터에 저장되어 있지 않습니다. 4 inc 연속적인 메모리 위치 4 개가 미친다. Esp. 다음 번에 다시 메모리에서 다시 읽으려는 경우입니다. 루프 카운터 벡터를 루프 밖에서 설정 한 다음 { 1, 1, 1, 1 } 벡터를 추가하여 증가시킵니다.

당신은 또한 32bit-windows 호출 규칙 (어떤 arg가 어떤 레지스터에 들어 갔는지)에 대한 어떤 알림도 갖고 있지 않으므로 함수 arg 변수 이름을 보는 것에서 그것을 써. 이 후 mulps의 규모 벡터를 다릅니다 설정, 루프하지 않을 함수의 경우

; *untested* 
    movdqa xmm1, [ vector_of_ones ] ; or pcmpgt same,same -> all 1s, packed right shift by 32bits 
    xor ebx, ebx ; loop counter 
; also broadcast the scale value to xmm4, maybe with shufps 
    movdqa xmm2, [eax] ; values to be incremented and converted 
loop: 
    cvtdq2ps xmm0, xmm2 
    mulps xmm0, xmm4 ; scale 
    movaps [edx], xmm0 
    paddd xmm2, xmm1 ; increment counters 
    sub  ebx, 4 
    jne  loop ; loop 2^32 times 

    ; movdqa [eax], xmm2 ; store the incremented loop counter? 
    ; Not sure if this was desired, or a side effect of using mem instead of regs. 
    ; If you want this to work on an array, put this store in the loop 
    ; and use an indexed addressing mode for eax and edx (or increment pointers) 

:

그래서 내부 루프는 식으로 뭔가를 할 수 있습니다. 이상적으로는 scale arg는 벡터 레지스터의 하위 요소에 전달되어야하며 여기에서 shufps 또는 무엇인가를 사용하여 브로드 캐스트합니다. 델파이가 GP 레지스터에 의해 지시 된 메모리와 같이 오도록 강제한다면, movss을 먼저 생각해보십시오. 컴파일 타임 상수라면 16B 벡터 상수를 mulps에 대한 메모리 피연산자로 사용하는 것이 좋습니다. Core2 이상에서는 128b로드에 대해 단일 사이클 만 사용합니다. (그래도 오래된 CPU의 비 AVX 벡터에 대해서는 정렬해야합니다.)

어쨌든 벤치 마크에서 느린 주된 기능은 특히 메모리 액세스였습니다. 한주기 당 하나의 저장소 만 가능합니다. 델파이가 부동 소수점 인수를 레지스터에 전달할 수 없다면 그건 싫증이납니다.

+0

안녕하세요 피터, 시간과 노력에 감사드립니다. 나는이 질문을 올린 지 오랜 시간이 지났으며, 나는 내가 배워야 할 시도를 벤치마킹하고 있다고 생각하며, 매우 저조한 벤치 마크를 작성했다. 나는 이번 주말에이 답을 올바르게 읽을 것이고, 나는 여전히 주제에 관해 배울 것이고, 그래서 대답 할 시간을 갖기 위해 다시 한번 thakns한다. – Marladu

+0

이 문제가 다시 발생하면 asm에서 전체 내부 루프를 작성하십시오. 벤치마킹뿐만 아니라 실제 사용에서는 인라인되지 않은 함수 호출 또는 호출 규칙에 대한 데이터 이동이 실제 함수 본문에 소요되는 시간을 지배 할 수 있습니다. –

관련 문제