Thanks for the feedback everyone. I wasn't sure about internal functions because FillWord, for example, is surrounded by "{$ifndef FPC_SYSTEM_HAS_FILLWORD}", which isn't defined under Win64, whereas FPC_SYSTEM_HAS_FILLCHAR is defined and the implementation of FillChar is nowhere to be found when you try to search for it in Lazarus.
For the speed-optimised assembler routines, I have the following (which does borrow ideas from FillChar):
procedure SpeedOptimisedFillWord(var x; count: SizeInt; Value: Word); assembler; nostackframe;
asm
{ RCX = Pointer to x
RDX = Count
R8W = Value }
PUSH RDI
MOVZX RAX, R8W
MOV R9, $0001000100010001
MOV RDI, RCX
IMUL RAX, R9{ Do some memory alignment first (it should be at least aligned to a 16-bit boundary already) } AND CL, $6
JZ @Aligned8
TEST CL, $2
JZ @Aligned4
MOV [RDI], R8W
DEC RDX
ADD RDI, $2
TEST CL, $4
JNZ @Aligned8 { Note that it's NOT zero here, because if TEST CL, $4 sets ZF here, then the memory block was originally 2 bytes away from the boundary } @Aligned4:
MOV [RDI], EAX
SUB RDX, $2
ADD RDI, $4
@Aligned8:
MOV R10B,DL
SHR RDX, 2
AND R10B,$3
MOV RCX, RDX
CMP RDX, $80000
JB @NoBlocks { To small for the non-temporal hint to be worthwhile, so just use STOSQ } SHR RDX, 2
AND RCX, $3
{ Write 32 bytes at a time using a non-temporal hint } @BlockLoop:
ADD RDI, $20
MOVNTI [RDI-$20], RAX
MOVNTI [RDI-$18], RAX
DEC RDX
MOVNTI [RDI-$10], RAX
MOVNTI [RDI-$8], RAX
JNZ @BlockLoop
MFENCE
@NoBlocks:
SHR R10B, 1
REP STOSQ
JNC @NoLooseWord
MOV [RDI], R8W
LEA RDI, [RDI+2]
@NoLooseWord:
JZ @NoLooseDWord
MOV [RDI], EAX
@NoLooseDWord:
POP RDI
end;
procedure SpeedOptimisedFillDWord(var x; count: SizeInt; Value: DWord); assembler; nostackframe;
asm
{ RCX = Pointer to x
RDX = Count
R8W = Value }
PUSH RDI
MOV RAX, R8
MOV RDI, RCX
SHL RAX, 32
OR RAX, R8
{ Do some memory alignment first (it should be at least aligned to a 32-bit boundary already) }
AND CL, $4
JZ @Aligned8
MOV [RDI], R8D
DEC RDX
ADD RDI, $4
@Aligned8:
SHR RDX, 1
SETC R10B
MOV RCX, RDX
CMP RDX, $80000
JB @NoBlocks { To small for the non-temporal hint to be worthwhile, so just use STOSQ }
SHR RDX, 2
AND RCX, $3
{ Write 32 bytes at a time using a non-temporal hint }
@BlockLoop:
ADD RDI, $20
MOVNTI [RDI-$20], RAX
MOVNTI [RDI-$18], RAX
DEC RDX
MOVNTI [RDI-$10], RAX
MOVNTI [RDI-$8], RAX
JNZ @BlockLoop
MFENCE
@NoBlocks:
TEST R10B, R10B
REP STOSQ
JZ @NoLooseDWord
MOV [RDI], EAX
@NoLooseDWord:
POP RDI
end;
procedure SpeedOptimisedFillQWord(var x; count: SizeInt; Value: QWord); assembler; nostackframe;
asm
{ RCX = Pointer to x
RDX = Count
R8 = Value }
PUSH RDI
CMP RDX, $80000
MOV RDI, RCX
MOV RCX, RDX
JB @NoBlocks { To small for the non-temporal hint to be worthwhile, so just use STOSQ }
AND RCX, $3
SHR RDX, 2
JZ @NoBlocks
{ Write 32 bytes at a time using a non-temporal hint }
@BlockLoop:
ADD RDI, $20
MOVNTI [RDI-$20], R8
MOVNTI [RDI-$18], R8
DEC RDX
MOVNTI [RDI-$10], R8
MOVNTI [RDI-$8], R8
JNZ @BlockLoop
MFENCE
@NoBlocks:
MOV RAX, R8
REP STOSQ
POP RDI
end;Regarding the CFI annotations, these functions are actually even better under Linux x64 because RDI is volatile and doesn't need to be pushed and popped, and those operations were the only things that modified the stack pointer... and since the above routines don't call any other procedures, we can use "nostackframe" safely.
I am tempted to experiment a little further, because one thing that's guaranteed to be present under x64 is SSE2, so it may be possible to increase the speed even more, although at the same time there may be a performance penalty if the rest of the application uses AVX or floating-point SSE.
J. Gareth "Kit" Moreton
On Wed 01/11/17 11:03 , Sergei Gorelkin via fpc-devel sent:
