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

Reply via email to