I just finished writing a function that computes the distance between two n-dimensional points.
The original one was written in C and it's basically a translation of this formula:
\$\text{dist}(x,y)=\sqrt{\sum_{i^1}^d{(x_i-y_i)^2}}\$
Here's the C code for completeness sake:
void distanceC(float* source, float* destination, int dimensions, float* result){
// sqrt(sum((source-destination)^2, dimensions)))
int i;
for (i = 0; i < dimensions; i++){
*result += powf(source[i] - destination[i], 2);
}
*result = sqrtf(*result);
}
Finally, here's the NASM version that I wrote (I stripped some useless parts):
section .data
; masks used to set exceeding items to 0 when vectors are not multiple of 4
align 16
mask1: dd 0xffffffff, 0x00, 0x00, 0x00
align 16
mask2: dd 0xffffffff, 0xffffffff, 0x00, 0x00
align 16
mask3: dd 0xffffffff, 0xffffffff, 0xffffffff, 0x00
section .text
Source equ 8
Destination equ 12
Dimensions equ 16
Result equ 20
; ------------------------------------------------------------
; Distance between two points in n-dimensional space
; ------------------------------------------------------------
distance:
; ------------------------------------------------------------
; Entering the function
; ------------------------------------------------------------
push ebp
mov ebp, esp
sub esp, 4
push ebx
push esi
push edi
; ------------------------------------------------------------
; Reading parameters from stack
; ------------------------------------------------------------
mov esi, [ebp+Source]
mov edi, [ebp+Destination]
mov ecx, [ebp+Dimensions]
mov eax, [ebp+Result]
; let's compute how many iterations do I need by doind Dimensions/4
; the result will tell how many packed iterations are needed, while the remainder will tell
; if I need we have exceeding items that require a final masked operation
push eax
mov edx, 0
mov eax, ecx
mov ebx, 4
div ebx
mov ecx, eax ; ecx becomes the counter for the packed iterations
pop eax
; now ecx contains the packed counter while edx contains the number of the exceeding items
; xmm0 = result
; xmm1 = source's components
; xmm2 = destination's components
; ebx = both vector's offset
mov ebx, 0
xorps xmm0, xmm0 ; setting xmm0 to 0 (may not be needed)
; we may have a situation in which vector's size is less than 4, and this means
; that we don't have any packed iteration to do but we'll jump to the final
; masked execution
cmp ecx, 0
je .dU1
.loopP: movaps xmm1, [esi+ebx*4] ; let's get Source's next 4 items
movaps xmm2, [edi+ebx*4] ; and Destination's next 4 ones
subps xmm1, xmm2 ; (Source-Destination)
mulps xmm1, xmm1 ; (Source-Destination)^2
dec ecx ; let's decrease ecx since we just did one iteration
haddps xmm1, xmm1
haddps xmm1, xmm1 ; let's sum the partial results that we have in xmm1
addps xmm0, xmm1 ; let's add the new computed results to the total
jz .loopC ; if ecx is 0 we're done with the packed iterations
add ebx, 1 ; let's move the offset to next elements
jmp .loopP ; next iteration
; the packed loop is done, let's see if we have some exceeding items that are needing the masked iteration
; if not we jump to the square root
.loopC cmp edx, 0
je .endD ; edx = 0 means no masked iterations,
; if we didn't jump than we have to deal with the masked iteration
; we need to decide which mask to apply and we decide it based on edx's value
.dU1: cmp edx, 1
jne .dU2 ; if edx is not 1 than it may be 2
; if edx is 1 then we use mask1
movaps xmm7, [mask1]
jmp .dU ; now that I have the correct mask I can jump to the actual execution
.dU2: cmp edx, 2
jne .dU3
movaps xmm7, [mask2]
jmp .dU
.dU3: movaps xmm7, [mask3] ; if I'm here than I can only use the third mask since the previous checks failed
; let's apply the mask and complete the computation
.dU: movaps xmm1, [esi+ebx*4] ; let's get Source's next 4 items
movaps xmm2, [edi+ebx*4] ; and Destination's next 4 ones
andps xmm1, xmm7 ; masking
andps xmm2, xmm7 ; masking
; now I can use the same instructions of the packed loop but just once
subps xmm1, xmm2 ; (Source-Destination)
mulps xmm1, xmm1 ; (Source-Destination)^2
haddps xmm1, xmm1
haddps xmm1, xmm1 ; let's sum the partial results that we have in xmm1
addps xmm0, xmm1 ; let's add the new computed results to the total
; finally we need to apply the square root and copy the result where eax is pointing
.endD: sqrtps xmm0, xmm0
movss [eax], xmm0
; ------------------------------------------------------------
; Exiting the function
; ------------------------------------------------------------
pop edi
pop esi
pop ebx
mov esp, ebp
pop ebp
ret
(You can test it by calling it from a C program, just add global distance
)
The question is: is there any way to make it faster or just write it better?