There's no real optimisation — LDIR
(or indeed LDDR
, which goes downward instead of upwards) is the complete inner loop. It will always load from DEHL, store to HLDE, increment both and decrement BC. Then if BC is non-zero it will repeat.
Annoyingly it will repeat almost exactly by just decrementing the PC by 2. So it'll read the full instruction again. Which means that 50% of memory accesses are it reading the opcode for the entire loop. No doubt that's to keep refresh going but oneThe advantage is that interrupts can imaginebe accommodated while a better solutiontransfer is in progress.
Cost is 21 cycles for each time around the loop that does lead to a repetition, then 16 cycles on the final go. Four cycles opcode fetch, four cycles opcode fetch, three cycles reading from the address pointed to by DEHL, then a long five cycle write to the address pointed to by HLDE. Then five cycles without a bus access if repeating.
Unrolled LDIs will be faster if you have the space as they always cost the flat 16 cycles. E.g. put 16 of them in a row with a conditional jump at the end and if the number of bytes you want to copy is n then jump into the loop n mod 16 steps from the repetition test for the first iteration.
Alternatively, if you're writing for a computer with slower RAM access that ROM access (e.g. because RAM is shared with video) then a top tip is to look for the three-byte sequence LDIR RET
anywhere in ROM and call it.