Invert the PRSG xorshift stages by repeated squaring (~25% faster)

This commit is contained in:
Sam Demeulemeester
2026-07-11 11:22:01 +02:00
committed by Sam Demeulemeester
parent 72d08688ba
commit 80717a1c5b
3 changed files with 45 additions and 36 deletions
+10 -16
View File
@@ -52,30 +52,24 @@ static const testword_t lane_salt[VEC_LANES] = {
// Private Functions
//------------------------------------------------------------------------------
// Inverts one y = x ^ (x << shift) step of the xorshift generator.
// Inverts one y = x ^ (x << shift) step of the xorshift generator by repeated
// squaring, doubling the shift until it exceeds the word width.
static inline testword_t prsg_undo_lshift(testword_t y, int shift)
{
testword_t x = y;
testword_t t = y;
for (int k = shift; k < TESTWORD_WIDTH; k += shift) {
t <<= shift;
x ^= t;
for (int k = shift; k < TESTWORD_WIDTH; k *= 2) {
y ^= y << k;
}
return x;
return y;
}
// Inverts one y = x ^ (x >> shift) step of the xorshift generator.
// Inverts one y = x ^ (x >> shift) step of the xorshift generator by repeated
// squaring, doubling the shift until it exceeds the word width.
static inline testword_t prsg_undo_rshift(testword_t y, int shift)
{
testword_t x = y;
testword_t t = y;
for (int k = shift; k < TESTWORD_WIDTH; k += shift) {
t >>= shift;
x ^= t;
for (int k = shift; k < TESTWORD_WIDTH; k *= 2) {
y ^= y >> k;
}
return x;
return y;
}
// Returns the previous word in the pseudo-random sequence generated by prsg().
+21 -11
View File
@@ -10,8 +10,9 @@
// vzeroupper itself before returning to legacy SSE code.
//
// Forward xorshift: x ^= x << 13; x ^= x >> 7; x ^= x << 17;
// The backward step inverts each stage in reverse order, using the identity
// that y = x ^ (x << s) is undone by x = y ^ (y << s) ^ (y << 2s) ^ ...
// The backward step inverts each stage in reverse order by repeated squaring:
// y = x ^ (x << s) is undone by x = y; x ^= x << s; x ^= x << 2s; x ^= x << 4s;
// ... doubling the shift until it exceeds the word width.
#if defined(__x86_64__)
@@ -20,6 +21,13 @@
#include "vec_prsg.h"
// The kernels end with vzeroupper, which zeroes the upper half of ALL ymm
// registers, not just those in the clobber lists. This is only safe because
// the compiler cannot hold values there when AVX code generation is disabled.
#ifdef __AVX__
#error "vec_prsg_avx2.c must be compiled without -mavx*"
#endif
#define AVX2_STEP_FWD(s, t) \
"vpsllq $13, " s ", " t " \n\t" \
"vpxor " t ", " s ", " s " \n\t" \
@@ -28,18 +36,20 @@
"vpsllq $17, " s ", " t " \n\t" \
"vpxor " t ", " s ", " s " \n\t"
#define AVX2_UNDO_SHIFT(s, t, op, shift, terms) \
#define AVX2_UNDO_STEP(s, t, op, shift) \
op " $" #shift ", " s ", " t " \n\t" \
"vpxor " t ", " s ", " s " \n\t" \
".rept " #terms " \n\t" \
op " $" #shift ", " t ", " t " \n\t" \
"vpxor " t ", " s ", " s " \n\t" \
".endr \n\t"
"vpxor " t ", " s ", " s " \n\t"
#define AVX2_STEP_BACK(s, t) \
AVX2_UNDO_SHIFT(s, t, "vpsllq", 17, 2) \
AVX2_UNDO_SHIFT(s, t, "vpsrlq", 7, 8) \
AVX2_UNDO_SHIFT(s, t, "vpsllq", 13, 3)
AVX2_UNDO_STEP(s, t, "vpsllq", 17) \
AVX2_UNDO_STEP(s, t, "vpsllq", 34) \
AVX2_UNDO_STEP(s, t, "vpsrlq", 7) \
AVX2_UNDO_STEP(s, t, "vpsrlq", 14) \
AVX2_UNDO_STEP(s, t, "vpsrlq", 28) \
AVX2_UNDO_STEP(s, t, "vpsrlq", 56) \
AVX2_UNDO_STEP(s, t, "vpsllq", 13) \
AVX2_UNDO_STEP(s, t, "vpsllq", 26) \
AVX2_UNDO_STEP(s, t, "vpsllq", 52)
void vec_fill_avx2(vec_state_t *st, testword_t *p, size_t nblocks, bool splat)
{
+14 -9
View File
@@ -9,8 +9,9 @@
// SIMD support and no intrinsics headers are required.
//
// Forward xorshift: x ^= x << 13; x ^= x >> 7; x ^= x << 17;
// The backward step inverts each stage in reverse order, using the identity
// that y = x ^ (x << s) is undone by x = y ^ (y << s) ^ (y << 2s) ^ ...
// The backward step inverts each stage in reverse order by repeated squaring:
// y = x ^ (x << s) is undone by x = y; x ^= x << s; x ^= x << 2s; x ^= x << 4s;
// ... doubling the shift until it exceeds the word width.
#if defined(__x86_64__)
@@ -30,17 +31,21 @@
"psllq $17, " t " \n\t" \
"pxor " t ", " s " \n\t"
#define SSE2_UNDO_SHIFT(s, t, op, shift, terms) \
#define SSE2_UNDO_STEP(s, t, op, shift) \
"movdqa " s ", " t " \n\t" \
".rept " #terms " \n\t" \
op " $" #shift ", " t " \n\t" \
"pxor " t ", " s " \n\t" \
".endr \n\t"
"pxor " t ", " s " \n\t"
#define SSE2_STEP_BACK(s, t) \
SSE2_UNDO_SHIFT(s, t, "psllq", 17, 3) \
SSE2_UNDO_SHIFT(s, t, "psrlq", 7, 9) \
SSE2_UNDO_SHIFT(s, t, "psllq", 13, 4)
SSE2_UNDO_STEP(s, t, "psllq", 17) \
SSE2_UNDO_STEP(s, t, "psllq", 34) \
SSE2_UNDO_STEP(s, t, "psrlq", 7) \
SSE2_UNDO_STEP(s, t, "psrlq", 14) \
SSE2_UNDO_STEP(s, t, "psrlq", 28) \
SSE2_UNDO_STEP(s, t, "psrlq", 56) \
SSE2_UNDO_STEP(s, t, "psllq", 13) \
SSE2_UNDO_STEP(s, t, "psllq", 26) \
SSE2_UNDO_STEP(s, t, "psllq", 52)
void vec_fill_sse2(vec_state_t *st, testword_t *p, size_t nblocks, bool splat)
{