Skip to content

Commit 530ec39

Browse files
committed
Optimize swizzle_dyn AVX2 implementation
llvm-mca shows no change on Haswell and Broadwell, but Zen 1, Zen 2 and Tiger Lake show a 23% improvement, and a 7-20% improvement on Zen 3.
1 parent 3e83711 commit 530ec39

1 file changed

Lines changed: 12 additions & 20 deletions

File tree

crates/core_simd/src/swizzle_dyn.rs

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -259,35 +259,27 @@ unsafe fn aarch64_swizzle<const N: usize>(bytes: Simd<u8, N>, idxs: Simd<u8, N>)
259259
#[inline]
260260
#[allow(clippy::let_and_return)]
261261
unsafe fn avx2_pshufb(bytes: Simd<u8, 32>, idxs: Simd<u8, 32>) -> Simd<u8, 32> {
262-
use crate::simd::{Select, cmp::SimdPartialOrd};
263262
#[cfg(target_arch = "x86")]
264263
use core::arch::x86;
265264
#[cfg(target_arch = "x86_64")]
266265
use core::arch::x86_64 as x86;
267266
use x86::_mm256_permute2x128_si256 as avx2_cross_shuffle;
268267
use x86::_mm256_shuffle_epi8 as avx2_half_pshufb;
269-
let mid = Simd::splat(16u8);
270-
let high = mid + mid;
271268
// SAFETY: Caller promised AVX2
272269
unsafe {
273-
// This is ordering sensitive, and LLVM will order these how you put them.
274-
// Most AVX2 impls use ~5 "ports", and only 1 or 2 are capable of permutes.
275-
// But the "compose" step will lower to ops that can also use at least 1 other port.
276-
// So this tries to break up permutes so composition flows through "open" ports.
277-
// Comparative benches should be done on multiple AVX2 CPUs before reordering this
278-
279-
let hihi = avx2_cross_shuffle::<0x11>(bytes.into(), bytes.into());
280-
let hi_shuf = Simd::from(avx2_half_pshufb(
281-
hihi, // duplicate the vector's top half
282-
idxs.into(), // so that using only 4 bits of an index still picks bytes 16-31
283-
));
284-
// A zero-fill during the compose step gives the "all-Neon-like" OOB-is-0 semantics
285-
let compose = idxs.simd_lt(high).select(hi_shuf, Simd::splat(0));
286270
let lolo = avx2_cross_shuffle::<0x00>(bytes.into(), bytes.into());
287-
let lo_shuf = Simd::from(avx2_half_pshufb(lolo, idxs.into()));
288-
// Repeat, then pick indices < 16, overwriting indices 0-15 from previous compose step
289-
let compose = idxs.simd_lt(mid).select(lo_shuf, compose);
290-
compose
271+
let hihi = avx2_cross_shuffle::<0x11>(bytes.into(), bytes.into());
272+
273+
// Adding 0x60 preserves the low nibble and bit 4 for valid
274+
// indices 0..=31. Larger indices get their high bit set, so
275+
// VPSHUFB supplies the required out-of-bounds zeroing.
276+
let control = x86::_mm256_adds_epu8(idxs.into(), x86::_mm256_set1_epi8(0x60));
277+
278+
// Move index bit 4 into each byte's sign bit for VPBLENDVB.
279+
let select_high = x86::_mm256_slli_epi16::<3>(control);
280+
let from_low = avx2_half_pshufb(lolo, control);
281+
let from_high = avx2_half_pshufb(hihi, control);
282+
x86::_mm256_blendv_epi8(from_low, from_high, select_high).into()
291283
}
292284
}
293285

0 commit comments

Comments
 (0)