libs/libc/string: Optimize string routines for misaligned pointers. - #19857
Open
Fishwaldo wants to merge 1 commit into
Open
libs/libc/string: Optimize string routines for misaligned pointers.#19857Fishwaldo wants to merge 1 commit into
Fishwaldo wants to merge 1 commit into
Conversation
The word paths in these routines are taken only when both pointers are
already on a "long" boundary:
#define UNALIGNED(x, y) \
(((long)(uintptr_t)(x) & (sizeof(long) - 1)) | \
((long)(uintptr_t)(y) & (sizeof(long) - 1)))
That asks more than the loops need. They read and write at the same
boundary in both operands, so what matters is that the two agree about
where a boundary falls, not that either is already on one. A pair
offset by the same amount is walked up to the boundary a byte at a time
and handled a word at a time from there, because aligning one aligns the
other.
The union also holds far less often than the difference. For arbitrary
pointers on a 64 bit target it is true about one time in 64 against one
in eight, and the case it rejects, two strings carved out of the same
buffer or a structure copied field by field, is a common one.
No unaligned access is introduced. Every word read and write is still
on a boundary, so this is safe on targets where a misaligned access
faults or is emulated.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Nine of the BSD string routines take their word path only when both pointers
are already on a
longboundary:That asks more than the loops need. They read and write at the same boundary
in both operands, so what matters is whether the two agree about where a
boundary falls, not whether either is already on one. A pair offset by the
same amount is walked up to the boundary a byte at a time and handled a word
at a time from there, because aligning one aligns the other.
The union also holds far less often than the difference. For arbitrary
pointers on a 64 bit target it is true about one time in 64 against one in
eight, and the case it rejects, two strings carved out of the same buffer or a
structure copied field by field, is a common one.
memccpy,memcmp,memcpy,stpcpy,stpncpy,strcmp,strcpy,strncmp,strncpy. The five single pointer routines cannot benefit and areuntouched.
No unaligned access is introduced. Every word read and write is still on a
boundary, so this is safe where a misaligned access faults or is emulated.
Where this comes from
@xiaoxiang781216 asked for exactly this in #19735: keep the general
optimisation out of the arch specific code and put it in the BSD
implementation. #19856 corrects the RISC-V assembly that has the same problem;
this is the portable half, and it is the one that reaches every architecture.
Measured
EIC7700X, rv64 at 1.4 GHz,
CONFIG_ALLOW_BSD_COMPONENTS=ywithCONFIG_LIBC_NEWLIB_OPTSPEED=y, 4 KB operands, taken with the benchmark inapache/nuttx-apps#3706.
Both pointers offset by one, the case the union rejects:
Every one of those
beforefigures is the byte loop. After the change eachlands within a few percent of the same function's aligned rate, which is what
says the prologue is doing what it should and nothing more.
Both pointers aligned, where the new test costs an extra branch and nothing
else:
strncpy,stpncpyandmemccpylose 7 to 12 per cent there. Those threereturn from inside the prologue to handle padding or the stop character, which
adds a branch to the aligned path as well. The rest are unchanged.
Pointers that genuinely disagree are unaffected, as they must be: no single
boundary serves both.
This needs measuring on other hardware
I can only speak for one part. The gain depends on how expensive a byte loop
is relative to a word loop on a given core, and the loss on those three
functions depends on branch prediction, so both numbers will move.
Please do not take QEMU numbers for this. QEMU executes misaligned
accesses natively at full speed and does not model the alignment behaviour of
the part it emulates, so it flatters the code being replaced and hides what
this is worth. It is fine for correctness and useless for this measurement.
Every figure above is from silicon.
If you have a board to hand, apache/nuttx-apps#3706 sweeps sizes against every
source and destination alignment pair and reports MB/s and cycles per byte.
Testing
testing/libc/arch_libcunder qemu rv64, all nineteenfunctions pass.
memccpyandstpncpyhad no coverage anywhere, so testsfor both went into testing/libc/arch_libc: Add a throughput benchmark. nuttx-apps#3706 first; the padding, the stop
character and the return values are what the prologue is easiest to get
wrong on.
-icountand on x86_64 under KVM, both of which agree in direction, thoughfor the reason above I would not put weight on the QEMU figures.