Re: [PATCH] libcpp: Optimize AArch64 search_line_fast
Wilco Dijkstra <[email protected]>
| Newsgroups | gmane.comp.gcc.patches |
|---|---|
| Message-ID | <PAWPR08MB898244A774001A1F2CF4A92683DA2@PAWPR08MB8982.eurprd08.prod.outlook.com> |
Hi Alexander, > On Wed, 12 Aug 2026, Wilco Dijkstra wrote: > >> Performance is significantly better as a result: 48% faster on Neoverse N1, >> 67% on Neoverse V1 and 69% on Neoverse V2. > > It is a bit strange to see exact numbers without a mention of the workload. > It is not very surprising that the new loop runs 2x-3x faster on modern CPUs, > but if you're showing exact percentages, can you say how you measured them, > please? It's just measuring the throughput of the search_line_fast function for different sizes. Then calculate the average from the speedup of each size times the frequency of the distribution. As it happens, the old code was doing a lot of unnecessary initialization, checks etc, so the speedup goes from ~85% for 1-16 chars to ~50% at 80 chars. > Yeah. When working on x86 SSSE3 implementation I noticed that at 16 characters > per iteration, the branch that exits the loop tends to be pretty unpredictable > on "typical" C++ sources, so moving to 32 characters per iteration helped with > that a bit, even with 16-byte vectors (so two vector loads per iteration, etc) > > I wonder if you looked at something like that? Doing two 16-character matches > per one iteration, then combining results into one GPR for the final CBZ? > Not sure if there's a way to make the epilogue cheap enough, though. It's certainly possible to do so, but it would be hard to measure whether it ends up faster. It probably means saving a trace and then replaying it. How did you measure the effect of branch prediction on overall compile time? > I don't see why you want to switch to an aligned load at the end. x86 relies > on sufficient padding to make unaligned loads all the way. One more issue with > that: I didn't know there is guaranteed padding at the end, so trying to avoid overreading is not really needed... >> + const uchar *limit = (const uchar*) ((uintptr_t)end & ~15); > > This is not safe in absence of padding: when 's' itself is not aligned, > and 'end' is close to it, this may move 'limit' to before 's', and then > the load from *limit will pick up bytes prior to 's'. Yes that's why I read from limit to get a full vector and then just shift out any chars before 's' - this magic removes 0-15 nibbles from the result mask based on the alignment of 's': + mask >>= (uintptr_t)s * 4; > Now, libcpp provides sufficient tail padding (at least 16 bytes, with '\n' > at the beginning of padding area) so in context this is safe, but then > the final aligned load should never be reached, and computing 'limit' is > pointless. If we can safely read 16 bytes beyond 'end' then yes, the loop condition could be: while (s < end) ... loop // no tail code, just return end since it is known that *end == '\n'. return end; Cheers, Wilco