2022-10-23 11:28:06 -04:00
#![ feature(generic_const_exprs) ]
2022-10-23 11:28:22 -04:00
#![ feature(new_uninit) ]
2022-10-24 15:14:10 -04:00
#![ feature(portable_simd) ]
2022-10-23 11:28:22 -04:00
use std ::mem ::MaybeUninit ;
2022-10-23 11:28:06 -04:00
use criterion ::{ black_box , criterion_group , criterion_main , Criterion } ;
use fast_hex ::* ;
const ASCII_BYTES : & [ u8 ; 16 ] = b " Donald J. Trump! " ;
const HEX_BYTES : & [ u8 ; ASCII_BYTES . len ( ) * 2 ] = b " 446F6E616C64204A2E205472756D7021 " ;
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 " ;
2022-10-23 11:28:22 -04:00
macro_rules ! name {
2022-10-23 14:33:33 -04:00
( $group :literal , $f :literal ) = > {
2022-10-24 15:14:10 -04:00
concat! ( " [ " , $group , " ] - " , $f )
2022-10-23 11:28:22 -04:00
} ;
2022-10-30 15:35:17 -04:00
( $group :expr , $f :literal ) = > {
std ::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 ( ) )
} ;
( $group :literal , $f :expr ) = > {
std ::boxed ::Box ::leak ( format! ( name! ( $group , " {} " ) , $f ) . into_boxed_str ( ) )
} ;
2022-10-23 11:28:22 -04:00
}
2022-10-30 15:35:17 -04:00
#[ track_caller ]
2022-10-23 11:28:22 -04:00
fn test_sized < const N : usize , const HEAP_ONLY : bool > ( hex_bytes : & [ u8 ; N * 2 ] , bytes : & [ u8 ; N ] )
2022-10-23 11:28:06 -04:00
where
2022-10-23 11:28:22 -04:00
[ ( ) ; N * 2 ] :,
2022-10-23 11:28:06 -04:00
{
2022-10-23 11:28:22 -04:00
test ( hex_bytes , bytes ) ;
if ! HEAP_ONLY {
assert_eq! (
Some ( bytes ) ,
hex_bytes_sized_const ::< N > ( hex_bytes ) . as_ref ( ) ,
stringify! ( hex_bytes_sized_const )
) ;
assert_eq! (
Some ( bytes ) ,
hex_bytes_sized ::< N > ( hex_bytes ) . as_ref ( ) ,
stringify! ( hex_bytes_sized )
) ;
}
assert_eq! (
Some ( bytes ) ,
hex_bytes_sized_heap ::< N > ( hex_bytes )
. as_ref ( )
. map ( Box ::as_ref ) ,
stringify! ( hex_bytes_sized_heap )
) ;
}
2022-10-30 15:35:17 -04:00
#[ track_caller ]
2022-10-23 11:28:22 -04:00
#[ inline ]
fn test ( hex_bytes : & [ u8 ] , bytes : & [ u8 ] ) {
assert_eq! ( hex_bytes . len ( ) , bytes . len ( ) * 2 ) ;
assert_eq! (
Some ( bytes ) ,
hex_bytes_dyn ( hex_bytes ) . as_ref ( ) . map ( Box ::as_ref ) ,
stringify! ( hex_bytes_dyn )
) ;
assert_eq! (
Some ( bytes ) ,
hex_bytes_dyn_niched ( hex_bytes ) . as_ref ( ) . map ( Box ::as_ref ) ,
stringify! ( hex_bytes_dyn_niched )
) ;
assert_eq! (
Some ( bytes ) ,
hex_bytes_dyn_unsafe_iter ( hex_bytes )
. as_ref ( )
. map ( Box ::as_ref ) ,
stringify! ( hex_bytes_dyn_unsafe_iter )
) ;
2022-10-23 14:33:33 -04:00
assert_eq! (
Some ( bytes ) ,
hex_bytes_dyn_unsafe_iter_niched ( hex_bytes )
. as_ref ( )
. map ( Box ::as_ref ) ,
stringify! ( hex_bytes_dyn_unsafe_iter_niched )
) ;
2022-10-23 11:28:22 -04:00
assert_eq! (
Some ( bytes ) ,
hex_bytes_dyn_unsafe ( hex_bytes ) . as_ref ( ) . map ( Box ::as_ref ) ,
stringify! ( hex_bytes_dyn_unsafe )
) ;
}
fn benchmark_sized < const N : usize , const HEAP_ONLY : bool > (
name : & str ,
bytes : & [ u8 ; N * 2 ] ,
c : & mut Criterion ,
) where
[ ( ) ; N * 2 ] :,
{
if ! HEAP_ONLY {
c . bench_function ( name! ( name , " sized " ) , | b | {
b . iter ( | | hex_bytes_sized ::< N > ( black_box ( bytes ) ) )
} ) ;
c . bench_function ( name! ( name , " sized const " ) , | b | {
b . iter ( | | hex_bytes_sized_const ::< N > ( black_box ( bytes ) ) )
} ) ;
}
c . bench_function ( name! ( name , " sized heap " ) , | b | {
b . iter ( | | hex_bytes_sized_heap ::< N > ( black_box ( bytes ) ) )
} ) ;
benchmark ( name , bytes , c ) ;
}
const BENCH_UNSAFE : bool = true ;
const BENCH_UNSAFE_ITER : bool = true ;
2022-10-23 14:33:33 -04:00
const BENCH_UNSAFE_ITER_NICHED : bool = true ;
const BENCH_NON_NICHED : bool = true ;
const BENCH_NICHED : bool = true ;
2022-10-23 11:28:22 -04:00
fn benchmark ( name : & str , bytes : & [ u8 ] , c : & mut Criterion ) {
if BENCH_UNSAFE {
c . bench_function ( name! ( name , " dyn unsafe " ) , | b | {
b . iter ( | | hex_bytes_dyn_unsafe ( black_box ( bytes ) ) )
} ) ;
}
//c.bench_function(format!("{name} - dyn unsafe for"), |b| b.iter(|| hex_bytes_dyn_unsafe_for(black_box(bytes))));
if BENCH_UNSAFE_ITER {
c . bench_function ( name! ( name , " dyn unsafe iter " ) , | b | {
b . iter ( | | hex_bytes_dyn_unsafe_iter ( black_box ( bytes ) ) )
} ) ;
}
2022-10-23 14:33:33 -04:00
if BENCH_UNSAFE_ITER_NICHED {
c . bench_function ( name! ( name , " dyn unsafe iter niched " ) , | b | {
b . iter ( | | hex_bytes_dyn_unsafe_iter_niched ( black_box ( bytes ) ) )
} ) ;
}
2022-10-23 11:28:22 -04:00
if BENCH_NON_NICHED {
c . bench_function ( name! ( name , " dyn non-niched " ) , | b | {
b . iter ( | | hex_bytes_dyn ( black_box ( bytes ) ) )
} ) ;
}
if BENCH_NICHED {
c . bench_function ( name! ( name , " dyn niched " ) , | b | {
b . iter ( | | hex_bytes_dyn_niched ( black_box ( bytes ) ) )
} ) ;
2022-10-23 11:28:06 -04:00
}
}
pub fn bench_16 ( c : & mut Criterion ) {
2022-10-23 11:28:22 -04:00
test_sized ::< { ASCII_BYTES . len ( ) } , false > ( HEX_BYTES , ASCII_BYTES ) ;
2022-10-24 15:14:10 -04:00
benchmark_sized ::< { ASCII_BYTES . len ( ) } , false > ( " 16 " , HEX_BYTES , c ) ;
2022-10-23 11:28:06 -04:00
}
pub fn bench_256 ( c : & mut Criterion ) {
2022-10-23 11:28:22 -04:00
test_sized ::< { ASCII_BYTES_LONG . len ( ) } , false > ( HEX_BYTES_LONG , ASCII_BYTES_LONG ) ;
2022-10-24 15:14:10 -04:00
benchmark_sized ::< { ASCII_BYTES_LONG . len ( ) } , false > ( " 256 " , HEX_BYTES_LONG , c ) ;
2022-10-23 11:28:22 -04:00
}
const fn __make_hex_chars ( ) -> [ u8 ; 16 ] {
let mut chars = [ 0 u8 ; 16 ] ;
let mut i = 0 u8 ;
while ( i as usize ) < chars . len ( ) {
chars [ i as usize ] = if i < 10 {
'0' as u8 + i
} else {
'a' as u8 + i - 10
} ;
i + = 1 ;
}
chars
}
const HEX_CHARS : [ u8 ; 16 ] = __make_hex_chars ( ) ;
trait SliceRandom {
type Item ;
fn choose < R > ( & self , rng : & mut R ) -> Option < & Self ::Item >
where
R : rand ::Rng + ? Sized ;
}
#[ inline ]
fn gen_index < R : rand ::Rng + ? Sized , const UBOUND : usize > ( rng : & mut R , ubound : usize ) -> usize {
if UBOUND < = ( core ::u32 ::MAX as usize ) {
rng . gen_range ( 0 .. ubound as u32 ) as usize
} else {
rng . gen_range ( 0 .. ubound )
}
}
impl < T > SliceRandom for [ T ] {
type Item = T ;
#[ inline ]
fn choose < R > ( & self , rng : & mut R ) -> Option < & Self ::Item >
where
R : rand ::Rng + ? Sized ,
{
if self . is_empty ( ) {
None
} else {
Some ( & self [ gen_index ::< _ , { usize ::MAX } > ( rng , self . len ( ) ) ] )
}
}
}
impl < const N : usize , T > SliceRandom for [ T ; N ] {
type Item = T ;
#[ inline ]
fn choose < R > ( & self , rng : & mut R ) -> Option < & Self ::Item >
where
R : rand ::Rng + ? Sized ,
{
if self . is_empty ( ) {
None
} else {
Some ( & self [ gen_index ::< _ , N > ( rng , N ) ] )
}
}
}
struct DisplayAsHexDigits < ' a > ( & ' a [ u8 ] ) ;
impl < ' a > std ::fmt ::Display for DisplayAsHexDigits < ' a > {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
use std ::fmt ::Write ;
if self . 0. is_empty ( ) {
return f . write_str ( " [] " ) ;
}
f . write_str ( " [ " ) ? ;
for b in self . 0 {
match hex_digit ( * b ) {
d @ 0 ..= 9 = > f . write_char ( ( '0' as u8 + d ) as char ) ,
d @ 10 ..= 15 = > f . write_char ( ( 'a' as u8 + d - 10 ) as char ) ,
2022-10-24 15:14:10 -04:00
_ = > write! ( f , " 0x{:02x} " , b ) ,
2022-10-23 11:28:22 -04:00
} ? ;
f . write_char ( ' ' ) ? ;
}
f . write_char ( ']' ) ? ;
Ok ( ( ) )
}
}
pub fn bench_1_6m ( c : & mut Criterion ) {
const LEN : usize = 1_600_000 ;
const LEN2 : usize = LEN * 2 ;
let mut hex_bytes : Box < [ MaybeUninit < u8 > ; LEN2 ] > =
unsafe { std ::mem ::transmute ( Box ::< [ u8 ; LEN2 ] > ::new_uninit ( ) ) } ;
let mut rng = rand ::thread_rng ( ) ;
for b in hex_bytes . iter_mut ( ) {
* b = MaybeUninit ::new ( * HEX_CHARS . choose ( & mut rng ) . unwrap ( ) ) ;
}
let hex_bytes : Box < [ u8 ; LEN2 ] > = unsafe { std ::mem ::transmute ( hex_bytes ) } ;
let bytes = match hex_bytes_dyn ( hex_bytes . as_ref ( ) ) {
Some ( b ) = > b ,
None = > {
panic! (
" Generated hex bytes were invalid: {} " ,
DisplayAsHexDigits ( hex_bytes . as_ref ( ) )
) ;
}
} ;
test_sized ::< LEN , true > ( & hex_bytes , bytes . as_ref ( ) . try_into ( ) . unwrap ( ) ) ;
2022-10-24 15:14:10 -04:00
benchmark_sized ::< LEN , true > ( " 1.6m " , & hex_bytes , c ) ;
2022-10-23 11:28:06 -04:00
}
2022-10-30 15:35:17 -04:00
pub fn bench_micro_hex_digit ( c : & mut Criterion ) {
2022-10-24 15:14:10 -04:00
use std ::simd ::Simd ;
2022-10-30 15:35:17 -04:00
const HEX_DIGITS_VALID : [ u8 ; DIGIT_BATCH_SIZE ] = [
0xf , 0xf , 0x0 , 0x0 , 0x1 , 0x1 , 0xe , 0xf , 0xf , 0xe , 0x0 , 0xf , 0xf , 0x0 , 0x3 , 0x4 , 0xf , 0xf ,
0x0 , 0x0 , 0x1 , 0x1 , 0xe , 0xf , 0xf , 0xe , 0x0 , 0xf , 0xf , 0x0 , 0x3 ,
0x4 ,
// 0xf, 0xf, 0x0, 0x0, 0x1, 0x1, 0xe, 0xf, 0xf, 0xe, 0x0, 0xf, 0xf, 0x0, 0x3, 0x4,
2022-10-24 15:14:10 -04:00
2022-10-30 15:35:17 -04:00
// 0xf, 0xf, 0x0, 0x0, 0x1, 0x1, 0xe, 0xf, 0xf, 0xe, 0x0, 0xf, 0xf, 0x0, 0x3, 0x4,
2022-10-24 15:14:10 -04:00
] ;
2022-10-30 15:35:17 -04:00
let hex_digits = Simd ::from_array ( black_box ( HEX_DIGITS_VALID ) ) ;
2022-10-24 15:14:10 -04:00
2022-10-30 15:35:17 -04:00
c . bench_function ( name! ( " micro " , " hex_digit " ) , | b | {
2022-10-24 15:14:10 -04:00
b . iter ( | | {
2022-10-30 15:35:17 -04:00
for b in black_box ( HEX_DIGITS_VALID ) {
black_box ( hex_digit ( b ) ) ;
2022-10-24 15:14:10 -04:00
}
} )
2022-10-23 14:33:33 -04:00
} ) ;
2022-10-24 15:14:10 -04:00
2022-10-30 15:35:17 -04:00
c . bench_function ( name! ( " micro " , " hex_digit_simd " ) , | b | {
b . iter ( | | hex_digit_simd ::< DIGIT_BATCH_SIZE > ( hex_digits ) )
} ) ;
}
pub fn bench_micro_hex_byte ( c : & mut Criterion ) {
const HEX_BYTES_VALID : [ [ u8 ; 2 ] ; WIDE_BATCH_SIZE ] = [
* b " ff " , * b " 00 " , * b " 11 " , * b " ef " , * b " fe " , * b " 0f " , * b " f0 " , * b " 34 " , * b " ff " , * b " 00 " , * b " 11 " ,
* b " ef " , * b " fe " , * b " 0f " , * b " f0 " ,
* b " 34 " ,
// *b"ff", *b"00", *b"11", *b"ef", *b"fe", *b"0f", *b"f0", *b"34",
// *b"ff", *b"00", *b"11", *b"ef", *b"fe", *b"0f", *b"f0", *b"34",
] ;
fn bench_decoder < T : HexByteDecoder + HexByteSimdDecoder > ( c : & mut Criterion , name : & str ) {
let hex_bytes = conv ::u8x2_to_u8 ( HEX_BYTES_VALID ) ;
c . bench_function ( name! ( " micro " , format! ( " {name} ::decode_packed " ) ) , | b | {
b . iter ( | | {
for b in black_box ( HEX_BYTES_VALID ) {
black_box ( T ::decode_packed ( & b ) ) ;
}
} )
} ) ;
c . bench_function ( name! ( " micro " , format! ( " {name} ::decode_unpacked " ) ) , | b | {
b . iter ( | | {
for [ hi , lo ] in black_box ( HEX_BYTES_VALID ) {
black_box ( T ::decode_unpacked ( hi , lo ) ) ;
}
} )
} ) ;
c . bench_function ( name! ( " micro " , format! ( " {name} ::decode_simd " ) ) , | b | {
b . iter ( | | T ::decode_simd ( black_box ( hex_bytes ) ) )
} ) ;
}
c . bench_function ( name! ( " micro " , " hex_byte " ) , | b | {
2022-10-24 15:14:10 -04:00
b . iter ( | | {
for b in black_box ( HEX_BYTES_VALID ) {
2022-10-30 15:35:17 -04:00
black_box ( hex_byte ( b [ 0 ] , b [ 1 ] ) ) ;
2022-10-24 15:14:10 -04:00
}
} )
} ) ;
2022-10-30 15:35:17 -04:00
bench_decoder ::< HexByteDecoderA > ( c , stringify! ( HexByteDecoderA ) ) ;
bench_decoder ::< HexByteDecoderB > ( c , stringify! ( HexByteDecoderB ) ) ;
}
pub fn bench_nano_hex_digit ( c : & mut Criterion ) {
let digit = black_box ( '5' as u8 ) ;
c . bench_function ( name! ( " nano " , " hex_digit " ) , | b | b . iter ( | | hex_digit ( digit ) ) ) ;
c . bench_function ( name! ( " nano " , " hex_digit +bb " ) , | b | {
b . iter ( | | hex_digit ( black_box ( digit ) ) )
2022-10-24 15:14:10 -04:00
} ) ;
}
pub fn bench_nano_hex_byte ( c : & mut Criterion ) {
2022-10-30 15:35:17 -04:00
const DIGITS : [ u8 ; 2 ] = [ '5' as u8 , 'b' as u8 ] ;
let digit = black_box ( DIGITS ) ;
2022-10-24 15:14:10 -04:00
c . bench_function ( name! ( " nano " , " hex_byte " ) , | b | {
b . iter ( | | hex_byte ( digit [ 0 ] , digit [ 1 ] ) )
} ) ;
2022-10-30 15:35:17 -04:00
fn bench_decoder < T : HexByteDecoder + HexByteSimdDecoder > ( c : & mut Criterion , name : & str ) {
let digit = black_box ( DIGITS ) ;
2022-10-24 15:14:10 -04:00
2022-10-30 15:35:17 -04:00
c . bench_function ( name! ( " nano " , format! ( " {name} ::decode_packed " ) ) , | b | {
b . iter ( | | {
black_box ( T ::decode_packed ( & digit ) ) ;
} )
} ) ;
2022-10-24 15:14:10 -04:00
2022-10-30 15:35:17 -04:00
c . bench_function ( name! ( " nano " , format! ( " {name} ::decode_unpacked " ) ) , | b | {
b . iter ( | | {
black_box ( T ::decode_unpacked (
black_box ( DIGITS [ 0 ] ) ,
black_box ( DIGITS [ 1 ] ) ,
) ) ;
} )
} ) ;
}
bench_decoder ::< HexByteDecoderA > ( c , stringify! ( HexByteDecoderA ) ) ;
bench_decoder ::< HexByteDecoderB > ( c , stringify! ( HexByteDecoderB ) ) ;
2022-10-23 14:33:33 -04:00
}
criterion_group! ( decode_benches , bench_16 , bench_256 , bench_1_6m ) ;
2022-10-30 15:35:17 -04:00
criterion_group! ( micro_benches , bench_micro_hex_digit , bench_micro_hex_byte ) ;
criterion_group! ( nano_benches , bench_nano_hex_digit , bench_nano_hex_byte ) ;
2022-10-24 15:14:10 -04:00
criterion_main! ( decode_benches , micro_benches , nano_benches ) ;