Skip to content

Commit 81cfb3c

Browse files
committed
revert: restore original SHA-512 x86 implementation
Both performance experiments (circular buffer + merged passes, and inline W+K fold) made throughput worse: -17% and -31% respectively vs baseline. The original three-pass structure (schedule → wk[] fold → compression) is well-tuned for the OoO pipeline: wk[] acts as a staging buffer that keeps the compression loop as pure sequential integer loads, letting the hardware prefetcher hide all memory latency behind the rounds' dependency chain. Mixing SIMD and integer work in the compression loop competes for execution ports and disrupts this balance. Restores baseline: ~537 MB/s (AVX-512 profile).
1 parent c3d8805 commit 81cfb3c

1 file changed

Lines changed: 53 additions & 68 deletions

File tree

include/dataforge/detail/hashes/sha512_intrinsics_x86.ipp

Lines changed: 53 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,9 @@
1919
// into a shift-or pair; selected on CPUs without AVX-512 (e.g.
2020
// Raptor Lake desktop with both P- and E-cores active).
2121
//
22-
// Structure (same for both backends):
23-
// Pass 1 — message schedule: w[40] linear array, sequential writes j=0..39.
24-
// Keeping the linear array (rather than a circular buffer) lets the
25-
// compiler hold w[j-1..j-8] in XMM registers across iterations.
26-
// Pass 2 — compression: 8 rounds per outer step, W[t]+K[t] folded inline
27-
// from w[t/2] + K[t/2] (one vector add per pair of rounds).
28-
// No intermediate wk[80] array — eliminates 640 bytes of stack
29-
// store-then-load traffic that was the main cost vs OpenSSL.
22+
// Both paths share the 8-way unrolled scalar compression rounds via the
23+
// DATAFORGE_SHA512_ROUND macro, which eliminates the 7 register-move
24+
// instructions that the naïve loop emits per round.
3025

3126
namespace dataforge::sha2_detail {
3227

@@ -94,51 +89,26 @@ __m128i sse41_sha512_sigma1(__m128i x)
9489
}
9590

9691
// ---------------------------------------------------------------------------
97-
// Compression-round macro.
92+
// 8-way unrolled compression-round macro.
9893
//
99-
// Variables a..h are passed by name so each call-site permutes the argument
94+
// Variables a..h are passed by name so each call site permutes the argument
10095
// list, rotating the logical state vector at compile time. No register moves
10196
// are emitted between rounds — the compiler assigns each name to a fixed
10297
// physical register for the whole 80-round sequence.
10398
//
104-
// wkval is the W[t]+K[t] scalar for this round, extracted from the XMM vector
105-
// inline (no intermediate array store).
99+
// wk[] is the pre-computed W[t]+K[t] table; t is the round index (0..79).
106100
// ---------------------------------------------------------------------------
107-
#define DATAFORGE_SHA512_ROUND(a,b,c,d,e,f,g,h,wkval) \
101+
#define DATAFORGE_SHA512_ROUND(a,b,c,d,e,f,g,h,wk,t) \
108102
do { \
109103
uint64_t _S1 = sha512_rotr(e,14) ^ sha512_rotr(e,18) ^ sha512_rotr(e,41); \
110-
uint64_t _T1 = (h) + _S1 + ((e & f) ^ (~(e) & g)) + (uint64_t)(wkval); \
104+
uint64_t _T1 = (h) + _S1 + ((e & f) ^ (~(e) & g)) + (wk)[t]; \
111105
uint64_t _S0 = sha512_rotr(a,28) ^ sha512_rotr(a,34) ^ sha512_rotr(a,39); \
112106
(d) += _T1; \
113107
(h) = _T1 + _S0 + ((a & b) ^ (a & c) ^ (b & c)); \
114108
} while(0)
115109

116-
// Two consecutive rounds from the low and high 64-bit lanes of one XMM vector.
117-
// The 16 argument names encode the compile-time state-vector rotation.
118-
// _mm_cvtsi128_si64 (SSE2) extracts low lane; _mm_extract_epi64 (SSE4.1) high.
119-
#define DATAFORGE_SHA512_ROUNDS2(a0,b0,c0,d0,e0,f0,g0,h0, \
120-
a1,b1,c1,d1,e1,f1,g1,h1, vec) \
121-
do { \
122-
DATAFORGE_SHA512_ROUND(a0,b0,c0,d0,e0,f0,g0,h0, \
123-
(uint64_t)_mm_cvtsi128_si64(vec)); \
124-
DATAFORGE_SHA512_ROUND(a1,b1,c1,d1,e1,f1,g1,h1, \
125-
(uint64_t)_mm_extract_epi64(vec, 1)); \
126-
} while(0)
127-
128-
// Eight rounds from four W+K vectors v0..v3. The round index entering this
129-
// macro is always a multiple of 8 (both initial blocks and the expansion loop
130-
// step by 4 vectors = 8 rounds), so the rotation always starts at (a,...,h)
131-
// and returns to (a,...,h) after the four ROUNDS2 calls.
132-
#define DATAFORGE_SHA512_8ROUNDS(v0, v1, v2, v3) \
133-
do { \
134-
DATAFORGE_SHA512_ROUNDS2(a,b,c,d,e,f,g,h, h,a,b,c,d,e,f,g, v0); \
135-
DATAFORGE_SHA512_ROUNDS2(g,h,a,b,c,d,e,f, f,g,h,a,b,c,d,e, v1); \
136-
DATAFORGE_SHA512_ROUNDS2(e,f,g,h,a,b,c,d, d,e,f,g,h,a,b,c, v2); \
137-
DATAFORGE_SHA512_ROUNDS2(c,d,e,f,g,h,a,b, b,c,d,e,f,g,h,a, v3); \
138-
} while(0)
139-
140110
// ---------------------------------------------------------------------------
141-
// AVX-512VL backend: SIMD message schedule + inline-folded compression.
111+
// AVX-512VL backend: SIMD message schedule + 8-unrolled scalar compression
142112
// ---------------------------------------------------------------------------
143113
#if defined(_MSC_VER)
144114
#pragma warning(push)
@@ -151,18 +121,17 @@ inline void process_blocks_sha512_x86_avx512(uint64_t(&state)[8], const void* ms
151121
static const __m128i SHUF_MASK = _mm_set_epi64x(0x08090A0B0C0D0E0FULL, 0x0001020304050607ULL);
152122

153123
const auto* data = reinterpret_cast<const uint8_t*>(msg);
154-
const auto* K = reinterpret_cast<const __m128i*>(sha2_def_base<512>::K);
124+
const auto* Kvec = reinterpret_cast<const __m128i*>(sha2_def_base<512>::K);
155125

156-
// Linear w[40]: two 64-bit words per XMM vector (W[2j], W[2j+1]).
157-
// Linear layout lets the compiler keep w[j-1..j-8] in XMM registers
158-
// across iterations of the schedule loop.
126+
// Message schedule: two 64-bit words per XMM vector (40 vectors = 80 words).
127+
// W[t] = sigma1(W[t-2]) + W[t-7] + sigma0(W[t-15]) + W[t-16]
128+
// W[t+1] = sigma1(W[t-1]) + W[t-6] + sigma0(W[t-14]) + W[t-15]
129+
// The sigma1 look-back is exactly the previous vector, so there is no
130+
// intra-vector dependency to break up.
159131
__m128i w[40];
160132
for (;;) {
161-
// --- Pass 1: message schedule ---
162133
for (int i = 0; i < 8; ++i)
163-
w[i] = _mm_shuffle_epi8(
164-
_mm_loadu_si128(reinterpret_cast<const __m128i*>(data + 16 * i)),
165-
SHUF_MASK);
134+
w[i] = _mm_shuffle_epi8(_mm_loadu_si128(reinterpret_cast<const __m128i*>(data + 16 * i)), SHUF_MASK);
166135

167136
for (int j = 8; j < 40; ++j) {
168137
const __m128i s0in = _mm_alignr_epi8(w[j - 7], w[j - 8], 8); // {W[t-15], W[t-14]}
@@ -173,16 +142,27 @@ inline void process_blocks_sha512_x86_avx512(uint64_t(&state)[8], const void* ms
173142
w[j] = v;
174143
}
175144

176-
// --- Pass 2: compression (W+K folded inline, no wk[] array) ---
145+
// Fold round constants in one pass (aligned loads from the 64-byte-aligned K
146+
// table) so the compression loop only needs a single load per round.
147+
alignas(64) uint64_t wk[80];
148+
for (int j = 0; j < 40; ++j)
149+
_mm_store_si128(reinterpret_cast<__m128i*>(wk + 2 * j),
150+
_mm_add_epi64(w[j], _mm_load_si128(Kvec + j)));
151+
177152
uint64_t a = state[0], b = state[1], c = state[2], d = state[3];
178153
uint64_t e = state[4], f = state[5], g = state[6], h = state[7];
179154

180-
for (int j = 0; j < 40; j += 4) {
181-
__m128i v0 = _mm_add_epi64(w[j ], K[j ]);
182-
__m128i v1 = _mm_add_epi64(w[j+1], K[j+1]);
183-
__m128i v2 = _mm_add_epi64(w[j+2], K[j+2]);
184-
__m128i v3 = _mm_add_epi64(w[j+3], K[j+3]);
185-
DATAFORGE_SHA512_8ROUNDS(v0, v1, v2, v3);
155+
// 8-way unroll: argument permutation rotates the logical state vector,
156+
// eliminating all inter-round register moves.
157+
for (int t = 0; t < 80; t += 8) {
158+
DATAFORGE_SHA512_ROUND(a,b,c,d,e,f,g,h, wk, t+0);
159+
DATAFORGE_SHA512_ROUND(h,a,b,c,d,e,f,g, wk, t+1);
160+
DATAFORGE_SHA512_ROUND(g,h,a,b,c,d,e,f, wk, t+2);
161+
DATAFORGE_SHA512_ROUND(f,g,h,a,b,c,d,e, wk, t+3);
162+
DATAFORGE_SHA512_ROUND(e,f,g,h,a,b,c,d, wk, t+4);
163+
DATAFORGE_SHA512_ROUND(d,e,f,g,h,a,b,c, wk, t+5);
164+
DATAFORGE_SHA512_ROUND(c,d,e,f,g,h,a,b, wk, t+6);
165+
DATAFORGE_SHA512_ROUND(b,c,d,e,f,g,h,a, wk, t+7);
186166
}
187167

188168
state[0] += a; state[1] += b; state[2] += c; state[3] += d;
@@ -198,23 +178,22 @@ inline void process_blocks_sha512_x86_avx512(uint64_t(&state)[8], const void* ms
198178
#endif
199179

200180
// ---------------------------------------------------------------------------
201-
// SSE4.1 backend: identical structure, sigma0/sigma1 use shift-or pairs
202-
// instead of vprorq — runs on any x86-64 CPU since ~2008.
181+
// SSE4.1 backend: identical structure, but sigma0/sigma1 use shift-or pairs
182+
// instead of vprorq, so it runs on any x86-64 CPU (Raptor Lake desktop,
183+
// Alder Lake, Skylake, etc.) without requiring AVX-512.
203184
// ---------------------------------------------------------------------------
204185
DATAFORGE_SSE41_TARGET
205186
inline void process_blocks_sha512_x86_sse41(uint64_t(&state)[8], const void* msg, size_t block_count)
206187
{
207188
static const __m128i SHUF_MASK = _mm_set_epi64x(0x08090A0B0C0D0E0FULL, 0x0001020304050607ULL);
208189

209190
const auto* data = reinterpret_cast<const uint8_t*>(msg);
210-
const auto* K = reinterpret_cast<const __m128i*>(sha2_def_base<512>::K);
191+
const auto* Kvec = reinterpret_cast<const __m128i*>(sha2_def_base<512>::K);
211192

212193
__m128i w[40];
213194
for (;;) {
214195
for (int i = 0; i < 8; ++i)
215-
w[i] = _mm_shuffle_epi8(
216-
_mm_loadu_si128(reinterpret_cast<const __m128i*>(data + 16 * i)),
217-
SHUF_MASK);
196+
w[i] = _mm_shuffle_epi8(_mm_loadu_si128(reinterpret_cast<const __m128i*>(data + 16 * i)), SHUF_MASK);
218197

219198
for (int j = 8; j < 40; ++j) {
220199
const __m128i s0in = _mm_alignr_epi8(w[j - 7], w[j - 8], 8);
@@ -225,15 +204,23 @@ inline void process_blocks_sha512_x86_sse41(uint64_t(&state)[8], const void* msg
225204
w[j] = v;
226205
}
227206

207+
alignas(64) uint64_t wk[80];
208+
for (int j = 0; j < 40; ++j)
209+
_mm_store_si128(reinterpret_cast<__m128i*>(wk + 2 * j),
210+
_mm_add_epi64(w[j], _mm_load_si128(Kvec + j)));
211+
228212
uint64_t a = state[0], b = state[1], c = state[2], d = state[3];
229213
uint64_t e = state[4], f = state[5], g = state[6], h = state[7];
230214

231-
for (int j = 0; j < 40; j += 4) {
232-
__m128i v0 = _mm_add_epi64(w[j ], K[j ]);
233-
__m128i v1 = _mm_add_epi64(w[j+1], K[j+1]);
234-
__m128i v2 = _mm_add_epi64(w[j+2], K[j+2]);
235-
__m128i v3 = _mm_add_epi64(w[j+3], K[j+3]);
236-
DATAFORGE_SHA512_8ROUNDS(v0, v1, v2, v3);
215+
for (int t = 0; t < 80; t += 8) {
216+
DATAFORGE_SHA512_ROUND(a,b,c,d,e,f,g,h, wk, t+0);
217+
DATAFORGE_SHA512_ROUND(h,a,b,c,d,e,f,g, wk, t+1);
218+
DATAFORGE_SHA512_ROUND(g,h,a,b,c,d,e,f, wk, t+2);
219+
DATAFORGE_SHA512_ROUND(f,g,h,a,b,c,d,e, wk, t+3);
220+
DATAFORGE_SHA512_ROUND(e,f,g,h,a,b,c,d, wk, t+4);
221+
DATAFORGE_SHA512_ROUND(d,e,f,g,h,a,b,c, wk, t+5);
222+
DATAFORGE_SHA512_ROUND(c,d,e,f,g,h,a,b, wk, t+6);
223+
DATAFORGE_SHA512_ROUND(b,c,d,e,f,g,h,a, wk, t+7);
237224
}
238225

239226
state[0] += a; state[1] += b; state[2] += c; state[3] += d;
@@ -244,8 +231,6 @@ inline void process_blocks_sha512_x86_sse41(uint64_t(&state)[8], const void* msg
244231
}
245232
}
246233

247-
#undef DATAFORGE_SHA512_8ROUNDS
248-
#undef DATAFORGE_SHA512_ROUNDS2
249234
#undef DATAFORGE_SHA512_ROUND
250235

251236
}

0 commit comments

Comments
 (0)