mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-07 08:36:02 -10:00
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:
1 parent
0f1d861080
commit
cd16da75f7
1 file changed
+25
@@ -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;
|
||||
|
||||
|
||||
Reference in new issue
Block a user