Some cleanup

This commit is contained in:
Michael Pfaff 2024-03-14 17:19:43 -04:00
parent 9daecc56ea
commit 0144056d99
8 changed files with 102 additions and 71 deletions

View File

@ -7,22 +7,10 @@ edition = "2021"
default = ["std"]
alloc = []
std = ["alloc"]
# provides the test data and helpers used by the benches crate
test = []
[dependencies]
brisk = { git = "https://git.pfaff.dev/michael/brisk.git" }
cache-padded = "1.2"
[dev-dependencies]
criterion = { version = "0.4", features = [ "real_blackbox" ] }
rand = "0.8.5"
[[bench]]
name = "dec"
harness = false
required-features = ["test"]
[[bench]]
name = "enc"
harness = false
required-features = ["test"]

View File

@ -0,0 +1,19 @@
[package]
name = "fast-hex-benches"
version = "0.1.0"
edition = "2021"
[dependencies]
fast-hex = { path = "../", features = ["test"] }
[dev-dependencies]
criterion = { version = "0.4", features = [ "real_blackbox" ] }
rand = "0.8.5"
[[bench]]
name = "dec"
harness = false
[[bench]]
name = "enc"
harness = false

View File

@ -9,9 +9,11 @@ use fast_hex::dec::*;
use fast_hex::defs::{DIGIT_BATCH_SIZE, WIDE_BATCH_SIZE};
use fast_hex::test::name;
// TODO: change this example text to something less silly
const ASCII_BYTES: &[u8; 16] = b"Donald J. Trump!";
const HEX_BYTES: &[u8; ASCII_BYTES.len() * 2] = b"446F6E616C64204A2E205472756D7021";
// TODO: change this example text to something less silly
const ASCII_BYTES_LONG: &[u8; 256] = b"Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!";
const HEX_BYTES_LONG: &[u8; ASCII_BYTES_LONG.len() * 2] = b"446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021";

View File

@ -8,9 +8,11 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion};
use fast_hex::enc::{Encode as _, Encoder};
use fast_hex::test::name;
// TODO: change this example text to something less silly
const ASCII_BYTES: &[u8; 16] = b"Donald J. Trump!";
const HEX_BYTES: &[u8; ASCII_BYTES.len() * 2] = b"446F6E616C64204A2E205472756D7021";
// TODO: change this example text to something less silly
const ASCII_BYTES_LONG: &[u8; 256] = b"Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!Donald J. Trump!";
const HEX_BYTES_LONG: &[u8; ASCII_BYTES_LONG.len() * 2] = b"446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021446F6E616C64204A2E205472756D7021";

View File

@ -2,8 +2,9 @@
use core::mem::MaybeUninit;
use core::simd::*;
use core::simd::num::SimdUint;
#[cfg(feature = "alloc")]
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::{boxed::Box, vec::Vec};
use crate::prelude::*;
@ -179,7 +180,7 @@ macro_rules! merge_hex_digits_into_bytes_inline {
let lsb = simd::extract_hi_bytes($hex_digits);
let msb1: simd::arch::__m128i;
unsafe { std::arch::asm!("vpsllq {dst}, {src}, 4", src = in(xmm_reg) msb, dst = lateout(xmm_reg) msb1, options(pure, nomem, preserves_flags, nostack)) };
unsafe { core::arch::asm!("vpsllq {dst}, {src}, 4", src = in(xmm_reg) msb, dst = lateout(xmm_reg) msb1, options(pure, nomem, preserves_flags, nostack)) };
if_trace_simd! {
let msb1: Simd<u8, WIDE_BATCH_SIZE> = msb1.into();
println!("msb1: {msb1:x?}");
@ -350,9 +351,7 @@ pub const fn hex_bytes_sized_const<const N: usize>(ascii: &[u8; N * 2]) -> Optio
if i >> 1 >= bytes.len() {
unsafe { core::hint::unreachable_unchecked() };
}
match hex_byte(unsafe { *ascii.get_unchecked(i) }, unsafe {
*ascii.get_unchecked(i + 1)
}) {
match hex_byte(ascii[i], ascii[i + 1]) {
Some(b) => bytes[i >> 1] = MaybeUninit::new(b),
None => return None,
}

View File

@ -1,7 +1,10 @@
//! SIMD-accelerated hex encoding.
use std::mem::MaybeUninit;
use std::simd::*;
use core::fmt;
use core::mem::MaybeUninit;
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::{boxed::Box, string::String, vec::Vec};
use crate::prelude::*;
@ -147,7 +150,7 @@ macro_rules! const_impl1 {
#[inline(always)]
fn u64_to_ne_u16(v: u64) -> [u16; 4] {
unsafe { std::mem::transmute(v.to_ne_bytes()) }
unsafe { core::mem::transmute(v.to_ne_bytes()) }
}
macro_rules! const_impl {
@ -277,7 +280,7 @@ macro_rules! common_impl {
//let chunk: simd::arch::__m128i = Simd::from_array(chunk).into();
let chunk: simd::arch::__m128i;
// We've aligned the src ptr
std::arch::asm!("vmovdqu {dst}, [{src}]", src = in(reg) src, dst = lateout(xmm_reg) chunk, options(pure, readonly, preserves_flags, nostack));
core::arch::asm!("vmovdqu {dst}, [{src}]", src = in(reg) src, dst = lateout(xmm_reg) chunk, options(pure, readonly, preserves_flags, nostack));
let hi = chunk.and(hi_mask);
// 64 vs 16 seems to make no difference
@ -337,7 +340,7 @@ macro_rules! common_impl {
if_trace_simd! {
let slice: &[_] = $dst.as_ref();
match std::str::from_utf8(unsafe { &*(slice as *const [_] as *const [u8]) }) {
match core::str::from_utf8(unsafe { &*(slice as *const [_] as *const [u8]) }) {
Ok(s) => {
println!("encoded: {s:?}");
}
@ -355,9 +358,10 @@ macro_rules! define_encode {
write => $write:ident
$(,)?
} $(where $( $where:tt )+)?) => {
#[cfg(feature = "alloc")]
fn $str$(<const $N: usize>)?(src: $in) -> String $( where $( $where )+ )?;
fn $write$(<const $N: usize>)?(w: impl std::fmt::Write, src: $in) -> std::fmt::Result $( where $( $where )+ )?;
fn $write$(<const $N: usize>)?(w: impl fmt::Write, src: $in) -> fmt::Result $( where $( $where )+ )?;
};
}
@ -374,35 +378,42 @@ macro_rules! impl_encode_str {
macro_rules! impl_encode_write {
($name:ident$(<$N:ident>)?($in:ty) => $impl:ident (|$bytes:ident| $into_slice:expr) $(where $( $where:tt )+)?) => {
#[inline]
fn $name$(<const $N: usize>)?(mut w: impl std::fmt::Write, src: $in) -> std::fmt::Result $( where $( $where )+ )? {
fn $name$(<const $N: usize>)?(mut w: impl fmt::Write, src: $in) -> fmt::Result $( where $( $where )+ )? {
let $bytes = Self::$impl(src);
let s = unsafe { std::str::from_utf8_unchecked($into_slice) };
let s = unsafe { core::str::from_utf8_unchecked($into_slice) };
w.write_str(s)
}
};
}
pub struct DisplaySized<'a, E: Encode + ?Sized, const N: usize>(&'a [u8; N], std::marker::PhantomData<E>);
pub struct DisplaySizedHeap<'a, E: Encode + ?Sized, const N: usize>(&'a [u8; N], std::marker::PhantomData<E>);
pub struct DisplaySlice<'a, E: Encode + ?Sized>(&'a [u8], std::marker::PhantomData<E>);
// TODO: keep only DisplaySized and DisplaySlice and for DisplaySized implement a heuristic to
// decide when to use a fixed-size buffer and encode+write in chunks
pub struct DisplaySized<'a, E: Encode + ?Sized, const N: usize>(&'a [u8; N], core::marker::PhantomData<E>);
#[cfg(feature = "alloc")]
#[deprecated(note = "Please don't start using this. It will be merged in to [`DisplaySized`] soon.")]
pub struct DisplaySizedHeap<'a, E: Encode + ?Sized, const N: usize>(&'a [u8; N], core::marker::PhantomData<E>);
#[cfg(feature = "alloc")]
pub struct DisplaySlice<'a, E: Encode + ?Sized>(&'a [u8], core::marker::PhantomData<E>);
impl<'a, E: Encode, const N: usize> std::fmt::Display for DisplaySized<'a, E, N> where [u8; N * 2]: {
impl<'a, E: Encode, const N: usize> fmt::Display for DisplaySized<'a, E, N> where [u8; N * 2]: {
#[inline(always)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
E::write_sized(f, self.0)
}
}
impl<'a, E: Encode, const N: usize> std::fmt::Display for DisplaySizedHeap<'a, E, N> where [u8; N * 2]: {
#[cfg(feature = "alloc")]
impl<'a, E: Encode, const N: usize> fmt::Display for DisplaySizedHeap<'a, E, N> where [u8; N * 2]: {
#[inline(always)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
E::write_sized_heap(f, self.0)
}
}
impl<'a, E: Encode> std::fmt::Display for DisplaySlice<'a, E> {
#[cfg(feature = "alloc")]
impl<'a, E: Encode> fmt::Display for DisplaySlice<'a, E> {
#[inline(always)]
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
E::write_slice(f, self.0)
}
}
@ -414,38 +425,55 @@ pub trait Encode {
[u8; N * 2]:;
/// Encodes the sized input on the heap.
#[cfg(feature = "alloc")]
fn enc_sized_heap<const N: usize>(src: &[u8; N]) -> Box<[u8; N * 2]>
where
[u8; N * 2]:;
/// Encodes the unsized input on the heap.
#[cfg(feature = "alloc")]
fn enc_slice(src: &[u8]) -> Box<[u8]>;
define_encode!(enc_sized<N>(&[u8; N]) {
str => enc_str_sized,
write => write_sized,
} where [u8; N * 2]:);
#[cfg(feature = "alloc")]
define_encode!(enc_sized_heap<N>(&[u8; N]) {
str => enc_str_sized_heap,
write => write_sized_heap,
} where [u8; N * 2]:);
#[cfg(feature = "alloc")]
define_encode!(enc_slice(&[u8]) {
str => enc_str_slice,
write => write_slice,
});
/// Returns an `impl Display` of the sized input on the stack.
#[inline(always)]
fn display_sized<const N: usize>(src: &[u8; N]) -> DisplaySized<'_, Self, N>
where
[u8; N * 2]:;
[u8; N * 2]:
{
DisplaySized(src, core::marker::PhantomData)
}
/// Returns an `impl Display` of the sized input on the heap.
#[cfg(feature = "alloc")]
#[inline(always)]
fn display_sized_heap<const N: usize>(src: &[u8; N]) -> DisplaySizedHeap<'_, Self, N>
where
[u8; N * 2]:;
[u8; N * 2]:
{
DisplaySizedHeap(src, core::marker::PhantomData)
}
/// Returns an `impl Display` of the unsized input on the heap.
fn display_slice(src: &[u8]) -> DisplaySlice<'_, Self>;
#[cfg(feature = "alloc")]
#[inline(always)]
fn display_slice(src: &[u8]) -> DisplaySlice<'_, Self> {
DisplaySlice(src, core::marker::PhantomData)
}
}
pub struct Encoder<const UPPER: bool = false>;
@ -461,7 +489,7 @@ impl<const N: usize> Buffer<N> where [u8; N * 2]: {
#[inline]
pub fn format_exact<const UPPER: bool>(&mut self, bytes: &[u8; N]) -> &str {
self.0 = MaybeUninit::new(Encoder::<UPPER>::enc_sized(bytes));
unsafe { std::str::from_utf8_unchecked(self.0.assume_init_ref()) }
unsafe { core::str::from_utf8_unchecked(self.0.assume_init_ref()) }
}
// TODO: support using only part of the buffer.
@ -469,7 +497,7 @@ impl<const N: usize> Buffer<N> where [u8; N * 2]: {
/*pub fn format<const UPPER: bool>(&mut self, bytes: &[u8]) -> &str {
assert!(bytes.len() <= N);
self.0 = MaybeUninit::new(Encoder::<UPPER>::enc_slice(bytes));
unsafe { std::str::from_utf8_unchecked(self.0.assume_init_ref()) }
unsafe { core::str::from_utf8_unchecked(self.0.assume_init_ref()) }
}*/
}
@ -490,6 +518,7 @@ impl<const UPPER: bool> Encode for Encoder<UPPER> {
unsafe { MaybeUninit::array_assume_init(buf.0) }
}
#[cfg(feature = "alloc")]
#[inline]
fn enc_sized_heap<const N: usize>(src: &[u8; N]) -> Box<[u8; N * 2]>
where
@ -501,6 +530,7 @@ impl<const UPPER: bool> Encode for Encoder<UPPER> {
unsafe { Box::from_raw(Box::into_raw(buf).cast()) }
}
#[cfg(feature = "alloc")]
#[inline]
fn enc_slice(src: &[u8]) -> Box<[u8]> {
let mut buf: Box<[MaybeUninit<u8>]> =
@ -509,36 +539,21 @@ impl<const UPPER: bool> Encode for Encoder<UPPER> {
unsafe { Box::<[_]>::assume_init(buf) }
}
// TODO: use an ArrayStr-like struct instead of allocating a String
#[cfg(feature = "alloc")]
impl_encode_str!(enc_str_sized<N>(&[u8; N]) => enc_sized (|bytes| bytes.into()) where [u8; N * 2]:);
#[cfg(feature = "alloc")]
impl_encode_str!(enc_str_sized_heap<N>(&[u8; N]) => enc_sized_heap (|bytes| {
Vec::from_raw_parts(Box::into_raw(bytes) as *mut u8, N * 2, N * 2)
}) where [u8; N * 2]:);
#[cfg(feature = "alloc")]
impl_encode_str!(enc_str_slice(&[u8]) => enc_slice (|bytes| Vec::from(bytes)));
impl_encode_write!(write_sized<N>(&[u8; N]) => enc_sized (|bytes| &bytes) where [u8; N * 2]:);
#[cfg(feature = "alloc")]
impl_encode_write!(write_sized_heap<N>(&[u8; N]) => enc_sized_heap (|bytes| bytes.as_ref()) where [u8; N * 2]:);
#[cfg(feature = "alloc")]
impl_encode_write!(write_slice(&[u8]) => enc_slice (|bytes| bytes.as_ref()));
#[inline(always)]
fn display_sized<const N: usize>(src: &[u8; N]) -> DisplaySized<'_, Self, N>
where
[u8; N * 2]:,
{
DisplaySized(src, std::marker::PhantomData)
}
#[inline(always)]
fn display_sized_heap<const N: usize>(src: &[u8; N]) -> DisplaySizedHeap<'_, Self, N>
where
[u8; N * 2]:,
{
DisplaySizedHeap(src, std::marker::PhantomData)
}
#[inline(always)]
fn display_slice(src: &[u8]) -> DisplaySlice<'_, Self> {
DisplaySlice(src, std::marker::PhantomData)
}
}
impl<const UPPER: bool> Encoder<UPPER> {
@ -556,6 +571,9 @@ impl<const UPPER: bool> Encoder<UPPER> {
#[cfg(test)]
mod test {
#[cfg(all(feature = "alloc", not(feature = "std")))]
use alloc::borrow::ToOwned;
use super::*;
use crate::test::*;
@ -619,20 +637,25 @@ mod test {
for_each_sample!(enc_const, |_, _, b, hb| assert_eq!(Enc::enc_const(b), *hb));
for_each_sample!(enc_sized, |_, _, b, hb| assert_eq!(Enc::enc_sized(b), *hb));
#[cfg(feature = "alloc")]
for_each_sample!(enc_sized_heap, |_, _, b, hb| assert_eq!(
Enc::enc_sized_heap(b),
Box::new(*hb)
));
#[cfg(feature = "alloc")]
for_each_sample!(enc_slice, |_, _, b, hb| assert_eq!(
Enc::enc_slice(b),
(*hb).into_iter().collect::<Vec<_>>().into_boxed_slice()
));
#[cfg(feature = "alloc")]
for_each_sample!(enc_str_sized, |_, hs, b, _| assert_eq!(Enc::enc_str_sized(b), hs.to_owned()));
#[cfg(feature = "alloc")]
for_each_sample!(enc_str_sized_heap, |_, hs, b, _| assert_eq!(
Enc::enc_str_sized_heap(b),
hs.to_owned()
));
#[cfg(feature = "alloc")]
for_each_sample!(enc_str_slice, |_, hs, b, _| assert_eq!(
Enc::enc_str_slice(b),
hs.to_owned()

View File

@ -1,10 +1,10 @@
// ignores warning about `generic_const_exprs`
#![allow(incomplete_features)]
#![cfg_attr(not(feature = "std"), no_std)]
#![feature(array_chunks)]
#![feature(core_intrinsics)]
#![feature(const_eval_select)]
#![feature(const_slice_index)]
#![feature(const_trait_impl)]
#![feature(extend_one)]
#![feature(effects)]
#![feature(generic_const_exprs)]
#![feature(maybe_uninit_slice)]
#![feature(maybe_uninit_uninit_array)]
@ -12,10 +12,7 @@
#![feature(const_maybe_uninit_array_assume_init)]
#![feature(const_maybe_uninit_uninit_array)]
#![cfg_attr(feature = "alloc", feature(new_uninit))]
#![feature(stdsimd)]
#![feature(portable_simd)]
// ignores warning about `generic_const_exprs`
#![allow(incomplete_features)]
#[cfg(feature = "alloc")]
extern crate alloc;

View File

@ -1,9 +1,10 @@
macro_rules! from_utf8 {
($bytes:ident) => {
unsafe { std::str::from_utf8_unchecked($bytes) }
unsafe { core::str::from_utf8_unchecked($bytes) }
};
}
// TODO: change this example text to something less silly
pub const BYTES: &[u8; 16] = b"Donald J. Trump!";
pub const HEX_BYTES: &[u8; 32] = b"446F6E616C64204A2E205472756D7021";
pub const STR: &str = from_utf8!(BYTES);
@ -44,13 +45,13 @@ macro_rules! __test__name {
concat!("[", $group, "] - ", $f)
};
($group:expr, $f:literal) => {
std::boxed::Box::leak(format!(name!("{}", $f), $group).into_boxed_str())
::alloc::boxed::Box::leak(format!(name!("{}", $f), $group).into_boxed_str())
};
($group:expr, $f:expr) => {
std::boxed::Box::leak(format!(name!("{}", "{}"), $group, $f).into_boxed_str())
::alloc::boxed::Box::leak(format!(name!("{}", "{}"), $group, $f).into_boxed_str())
};
($group:literal, $f:expr) => {
std::boxed::Box::leak(format!(name!($group, "{}"), $f).into_boxed_str())
::alloc::boxed::Box::leak(format!(name!($group, "{}"), $f).into_boxed_str())
};
}