state_manager: vectorise find_same without changing its granularity

find_same() walked the changed run one 32-bit word at a time. Compare
four per iteration with SSE2 instead.

The granularity has to stay 32-bit. The comment above the loop notes it
is "random whether two consecutive identical words are caught" and that
the compression rate depends on which case hits, so widening the
comparison itself would change the compressed output. Using a per-lane
mask keeps that intact: _mm_cmpeq_epi32 plus movemask gives one bit per
word, and the tzcnt picks the same word the scalar loop would have
stopped at. Same stopping index, same output, fewer iterations.

The scalar loop stays for non-SSE2 targets, and the NO_UNALIGNED_MEM
prologue is untouched.

Overread grows from 4 to 16 bytes past the stopping word, which is
inside STATE_MANAGER_SCAN_PAD (64).

Bit-exact: exhaustive harness over every start alignment (0..30) x every
first-matching-word index (0..191) x 4 randomised fills, with all
preceding words forced to differ -- 12288 cases, zero disagreements
with the scalar form.

The gain depends on how long the changed runs are, and it is not a win
everywhere. Measured per call: 1-2 word runs are a wash or a hair worse
(both forms are ~2ns, at the timing floor), 4 words ~1.3x, 8 words
~2.0x, 16 words ~2.6x, 64 words and up ~2.7-3.0x. Savestates with
contiguous modified regions land in the profitable range; states whose
changes are scattered single words gain nothing.
This commit is contained in:
libretroadmin authored and libretroadmin committed 2026-07-26 06:00:00 +02:00
1 parent 0f1d861080
commit cd16da75f7
1 file changed
+25
+25
View File
@@ -204,11 +204,36 @@ static size_t find_same(const uint16_t *a, const uint16_t *b)
const uint32_t *a_big = (const uint32_t*)a;
const uint32_t *b_big = (const uint32_t*)b;
#if __SSE2__
/* Four words per iteration. The granularity stays 32-bit -- the lane
* mask is per word, so this stops at exactly the word the scalar loop
* would, which the compressed output depends on. */
for (;;)
{
__m128i v0 = _mm_loadu_si128((const __m128i*)a_big);
__m128i v1 = _mm_loadu_si128((const __m128i*)b_big);
int mask = _mm_movemask_ps(_mm_castsi128_ps(
_mm_cmpeq_epi32(v0, v1)));
if (mask)
{
unsigned lane = (unsigned)compat_ctz((unsigned)mask);
a_big += lane;
b_big += lane;
break;
}
a_big += 4;
b_big += 4;
}
#else
while (*a_big != *b_big)
{
a_big++;
b_big++;
}
#endif
a = (const uint16_t*)a_big;
b = (const uint16_t*)b_big;