Re: [PATCH v2 0/2] Optimize RISC-V memset() port for speed
Kito Cheng <[email protected]>
| Newsgroups | gmane.comp.lib.newlib |
|---|---|
| Message-ID | <CA+yXCZBHgMt_t-0EpqZ_ZOpBePghVF-Tp2ZQ-Ve2GMVO-V5RXg@mail.gmail.com> |
Pushed to trunk, thanks :) On Mon, May 26, 2025 at 2:18 PM Kito Cheng <[email protected]> wrote: > > LGTM, thanks :) > > On Sat, May 24, 2025 at 6:05 AM Eric Salem <[email protected]> wrote: >> >> The existing memset() implementation does not take advantage of various >> RISC-V extensions like Zba, Zbkb, and Zilsd/Zclsd, so adding these >> instructions when available improves the performance independent of any >> other changes. >> >> At the same time, the current loop unrolling can be expanded further for >> greater speed gains. Following the same technique for byte stores, >> another table of instructions is used for single or double word stores. >> >> To ensure that the benchmarking is objective as possible, a range of >> addresses with various alignments were tested so neither algorithm had >> an advantage. Addresses divided by eight with a remainder of 0-7 were >> used for the benchmark. For example: >> >> 0x20002a80 >> 0x20003289 >> 0x20003a92 >> 0x2000429b >> 0x20004aa4 >> 0x200052b5 >> 0x20005ac6 >> 0x200062d7 >> >> The existing implementation aligns the starting address to be 16-byte >> aligned, whereas the proposed implementation aligns it to be either 4- >> or 8-byte aligned, depending on the store instruction used. >> >> The implementations were benchmarked using actual RISC-V hardware, >> specifically a Raspberry Pi Pico 2. This was chosen since Newlib is a C >> standard library targeted for use on embedded systems. It's also the >> only RISC-V hardware I had access to. >> >> I tested a range of counts from 0 to 2,048 for each alignment, for a >> total of 16,384 tests, for both zero (null) being passed in as the byte >> to set the buffer to, and also a non-zero value. The null case is the >> more important of the two, since that's the value most often passed to >> memset(). >> >> Overwhelmingly, the proposed implementation is faster than the existing. >> The four scenarios tested are null with extensions, non-null with >> extensions, null without extensions, and non-null without extensions >> (all extension benchmarking excludes Zilsd): >> >> BENCHMARK RESULTS >> +------+-----------+----------+------------+----------+ >> | |Null |Non-null |Null |Non-null | >> | |with |with |without |without | >> | |extensions |extensions|extensions |extensions| >> +------+-----------+----------+------------+----------+ >> |Slower|32, 1-8 cy.|2, 1-2 cy.|56, 1-10 cy.|7, 2-6 cy.| >> +------+-----------+----------+------------+----------+ >> |Tied |6 |1 |139 |12 | >> +------+-----------+----------+------------+----------+ >> |Faster|16,346 |16,381 |16,189 |16,365 | >> +------+-----------+----------+------------+----------+ >> >> (Number of cases, with slower cases followed by cycle ranges) >> >> The cases where the existing implementation was faster were >> circumstances that favored it, where the buffer is already 16-byte >> aligned (14 cases were exceptions to this for the >> null-without-extensions scenario, and it was only 2 cycles slower), and >> either the number of bytes to copy was a multiple of 16 (the unrolled >> loop size), or it was a small number to be copied (16 <= n <= 96, not a >> continuous range but 56 values). For copying relatively large amounts of >> bytes (more than 96), the proposed implementation is the clear winner, >> and the improvement increases as the number of bytes grows. The proposed >> implementation with extensions is also faster for counts from 0 to 15, >> so it's at least an improvement for very small copies. >> >> It's impossible to be faster for both small and large copies, without >> creating an inordinate number of unrolled loops for every combination of >> alignment and number of bytes to copy below a certain threshold. The >> proposed implementation is a reasonable trade-off between the two. >> >> The following architectures were tested for correctness: >> >> rv32ec-ext-zilsd >> rv32ec-ext-no-zilsd >> rv32ec-no-ext-zilsd >> rv32ec-no-ext-no-zilsd >> rv32ic-ext-zilsd >> rv32ic-ext-no-zilsd >> rv32ic-no-ext-zilsd >> rv32ic-no-ext-no-zilsd >> rv64ec-ext >> rv64ec-no-ext >> rv64ic-ext >> rv64ic-no-ext >> >> If extensions are enabled (excluding Zilsd), "ext" is present. >> Otherwise, "no-ext" is present. Zilsd is tested separately with either >> "zilsd" or "no-zilsd" (the latter not testing Zilsd). QEMU and spike >> with pk were the simulators used. pk does not support the E base ISA, so >> QEMU was used to test those binaries. >> >> Changes in v2: >> - Rename macros used for register size and store instruction >> - Move whitespace changes for size optimized version to separate patch >> - Link to v1: https://sourceware.org/pipermail/newlib/2025/021706.html >> >> Eric Salem (2): >> newlib: riscv: Optimize memset() for speed >> newlib: riscv: Align whitespace of size optimized memset() >> >> newlib/libc/machine/riscv/memset.S | 341 ++++++++++++++++++++++------- >> 1 file changed, 266 insertions(+), 75 deletions(-) >> >> -- >> 2.49.0 >>