memcpy optimization#4
Conversation
|
your axes would benefit a lot from labels :D. |
|
as the change on the size optimized version is pretty simple, lets have separate PRs on the two. |
|
I believe we need to discuss the algorithm in a more general sense.
When in doubt, you should always prioritize the getting alignment of the src. The is because you actually depend on the result of load, whereas the store can complete in the background. You will want a main work loop, in which you unroll as much as the registers permit you to. This will use ld/sd for RV64 and RV32-Zilsd, and lw/sw for RV32. In order to generate alignment, you optimally use a sequence of lb/sb, lh/sh, and optionally lw/sw. After that, you run the main work loop. This should give you the optimal sequence of loads/store with the least overhead. |
|
Thank you for the great and detailed feedback! I will go through it hopefully today.
|
df1f471 to
2d21f01
Compare
|
@i3abghany in your statements on performance, I am currently missing details on how you did the build. E.g. for C, which flags did you enable. Did you use -mno-strict-align? |
2d21f01 to
2869591
Compare
| #else | ||
| mv a3, a0 | ||
| li a4, SZREG | ||
| addi sp, sp, -2 * SZREG |
There was a problem hiding this comment.
there should be an option to use Zcmp
There was a problem hiding this comment.
actually this is debatable, as this would include the ra register.
would an additional register be of help anywhere?
Otherwise, only opportunity is use of Zilsd for RV32
2869591 to
1d2eca8
Compare
6db0086 to
f1552ad
Compare
This new version is based on the previous one but has two main differences: In the following, BLOCK size is 16 * SZREG - Instead of copying the remainder, BLOCK-misaligned bytes one byte at a time, the new implementation copies at most one half BLOCK, one SZREG at a time before copying one byte at a time. - In the case where __riscv_misaligned_avoid is defined, the C implementation copies one byte at a time if the src and dest alignments differ. The new implementation aligns dest to SZREG, does SZREG byte-sized loads, packs them in one register, and uses one aligned SZREG-sized store. The new implementation is 14% smaller than the C version compiled with -O3 with GCC 14.2. In terms of performance, gem5 was used to emulate a small, in-order core to exercise all possible pairs of src & dest alignments, and different copy sizes. Percentages are the average reduction in execution (number of cycles). Higher is better. Average over all alignment pairs: - `-mtune=thead-c906 -mno-strict-align -O3`: 2% - `-mtune=thead-c906 -mstrict-align -O3`: 13% Average over different sizes (0, 1, 2, 3, 4, 8, 16, 32, 64, 128, 200, 256, 300, 400, 512, 700, 800, 1000, 1024, 2048, 4096, 8192 16384): - `-mtune=thead-c906 -mno-strict-align -O3`: 3% - `-mtune=thead-c906 -mstrict-align -O3`: 7%
f1552ad to
a3a4031
Compare
|
|
||
| #if __riscv_xlen == 32 | ||
| /* 32-bit + zbkb */ | ||
| #define PACK4(r1, r2, r3, r4) \ |
There was a problem hiding this comment.
as this is assembly, use a .marco/.endm definition rather than a C macro.
| lbu s1, 7(a1) | ||
|
|
||
| PACK4(t0, a5, s0, s1) | ||
| pack a4, a4, t0 |
There was a problem hiding this comment.
i would move this inside the macro.
Also, I believe splat is the more fitting name.
llvm libc has a similar library function:
https://github.com/llvm/llvm-project/blob/be6ccc98f38227db02164f17bfaf0ac86d800e4a/libc/src/string/memory_utils/op_generic.h#L129
| PACK4(a4, a5, s0, s1) | ||
| #if __riscv_xlen == 64 | ||
|
|
||
| lbu t0, 4(a1) |
There was a problem hiding this comment.
why do you need to use a different register for rv64 over rv32?
| REG_S t6, 10 * SZREG(a3) | ||
| #endif | ||
|
|
||
| #ifdef __riscv_e |
There was a problem hiding this comment.
what is the performance difference between rv32e and rv32i?
It should be significant to justify this
I would also like to see the numbers on spike, i.e. difference in instret (you can of course take this out of gem5 also). |
This new version is based on the previous one but has two main
differences:
new implementation copies at most one 32-byte block before copying one
byte at a time.
implementation copies one byte at a time if the src and dest
alignments differ. The new implementation aligns dest to SZREG, does
SZREG byte-sized loads, packs them in one register, and uses one
aligned SZREG-sized store.
The new implementation is 18% smaller than the C version compiled with
-O3 with GCC 14.2.
In terms of performance, gem5 was used to emulate a small, in-order core
to exercise all possible pairs of src & dest alignments, and different
copy sizes. Percentages are the average reduction in execution (number
of cycles). Higher is better.
Average over all alignment pairs:
-mtune=thead-c906 -mno-strict-align -O3: 2%-mtune=thead-c906 -mstrict-align -O3: 13%Average over different sizes (0, 1, 2, 3, 4, 8, 16, 32, 64, 128, 200,
256, 300, 400, 512, 700, 800, 1000, 1024, 2048, 4096, 8192 16384):
-mtune=thead-c906 -mno-strict-align -O3: 3%-mtune=thead-c906 -mstrict-align -O3: 7%