Skip to content

Commit 7707a1e

Browse files
perf(string): SIMD swab — 16-34x self, 10-20x faster than glibc
swab() byte-swapped adjacent pairs with two scalar stores per pair (dest[2k]=src[2k+1]; dest[2k+1]=src[2k]) — ~0.58-0.66 ns/B, ~1.6x SLOWER than glibc's tuned loop. Replace the body with a single 32-byte portable-SIMD shuffle (lane 2k <-> 2k+1) plus a 2-byte scalar tail. The swizzle is exactly the pairwise transposition, so it is byte-for-byte identical to the scalar loop; bytes is even so neither the SIMD step (multiple of 32) nor the tail splits a pair, and an odd trailing byte (n odd) stays untouched per POSIX. swab (ns/B): scalar 0.564-0.790 -> SIMD 0.018-0.050 = 15.8-33.9x self; vs glibc 0.36-0.48 ns/B -> 0.05-0.10x (10-20x FASTER than glibc). Isomorphism: conformance_diff_string_mut::diff_swab_cases vs host glibc — extended with a 16..511-byte even/odd sweep so the SIMD block, its boundary, and the scalar tail are all covered (was <=8 bytes = scalar tail only); + core unit tests (basic + odd_length). 0 divergences. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7f83e5e commit 7707a1e

2 files changed

Lines changed: 57 additions & 4 deletions

File tree

crates/frankenlibc-abi/tests/conformance_diff_string_mut.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1811,6 +1811,36 @@ fn diff_swab_cases() {
18111811
});
18121812
}
18131813
}
1814+
1815+
// Larger sweep to exercise the 32-byte SIMD shuffle path and its tail/
1816+
// boundary (the small cases above only hit the scalar tail). Sizes span the
1817+
// SIMD block, just-over-block + odd-byte, and multi-block with a remainder,
1818+
// each filled with a high-bit-varied pattern, all compared against host glibc.
1819+
let pattern: Vec<u8> = (0..512u32).map(|i| (i.wrapping_mul(73).wrapping_add(17)) as u8).collect();
1820+
for &len in &[16usize, 31, 32, 33, 48, 63, 64, 65, 100, 127, 128, 200, 255, 256, 300, 511] {
1821+
for &odd in &[false, true] {
1822+
let n = if odd { len | 1 } else { len & !1 };
1823+
if n > pattern.len() {
1824+
continue;
1825+
}
1826+
let src = &pattern[..n];
1827+
let mut dst_fl = vec![0xCDu8; n + 8];
1828+
let mut dst_lc = vec![0xCDu8; n + 8];
1829+
unsafe {
1830+
fl::swab(src.as_ptr() as *const c_void, dst_fl.as_mut_ptr() as *mut c_void, n as isize);
1831+
swab(src.as_ptr() as *const c_void, dst_lc.as_mut_ptr() as *mut c_void, n as isize);
1832+
}
1833+
if dst_fl != dst_lc {
1834+
divs.push(Divergence {
1835+
function: "swab",
1836+
case: format!("(len={n})"),
1837+
field: "dst_buffer",
1838+
frankenlibc: format!("{:?}", &dst_fl[..n]),
1839+
glibc: format!("{:?}", &dst_lc[..n]),
1840+
});
1841+
}
1842+
}
1843+
}
18141844
assert!(divs.is_empty(), "swab divergences:\n{}", render_divs(&divs));
18151845
}
18161846

crates/frankenlibc-core/src/string/mem.rs

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -769,11 +769,34 @@ pub fn bcmp(a: &[u8], b: &[u8], n: usize) -> i32 {
769769
/// Equivalent to POSIX `swab`. Processes `n` bytes (n should be even).
770770
pub fn swab(src: &[u8], dest: &mut [u8], n: usize) -> usize {
771771
let pairs = n.min(src.len()).min(dest.len()) / 2;
772-
for i in 0..pairs {
773-
dest[2 * i] = src[2 * i + 1];
774-
dest[2 * i + 1] = src[2 * i];
772+
let bytes = pairs * 2;
773+
let mut i = 0;
774+
775+
// SIMD: swap adjacent byte pairs 32 at a time via a single shuffle (lane
776+
// 2k <-> 2k+1), instead of two scalar stores per pair. Byte-for-byte
777+
// identical to the scalar swap below — the swizzle is exactly the pairwise
778+
// transposition `dest[2k]=src[2k+1]; dest[2k+1]=src[2k]`. `bytes` is even,
779+
// so the SIMD step (multiple of 32) and the 2-byte tail never split a pair,
780+
// and an odd trailing byte (n odd) is left untouched, as POSIX swab requires.
781+
const LANES: usize = 32;
782+
while i + LANES <= bytes {
783+
let v = Simd::<u8, LANES>::from_slice(&src[i..i + LANES]);
784+
let sw = std::simd::simd_swizzle!(
785+
v,
786+
[
787+
1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 17, 16, 19, 18, 21, 20, 23,
788+
22, 25, 24, 27, 26, 29, 28, 31, 30
789+
]
790+
);
791+
sw.copy_to_slice(&mut dest[i..i + LANES]);
792+
i += LANES;
793+
}
794+
while i < bytes {
795+
dest[i] = src[i + 1];
796+
dest[i + 1] = src[i];
797+
i += 2;
775798
}
776-
pairs * 2
799+
bytes
777800
}
778801

779802
#[cfg(test)]

0 commit comments

Comments
 (0)