From 6a32d570b8fa651f08a3260e34e8809475a796d5 Mon Sep 17 00:00:00 2001 From: Lawrence Wu Date: Wed, 11 May 2022 12:58:41 +0530 Subject: [PATCH 01/11] Decode with multiple bytes per chunk --- src/decode.rs | 117 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/decode.rs | 1 + 2 files changed, 118 insertions(+) diff --git a/src/decode.rs b/src/decode.rs index d15533f..1198aee 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -219,6 +219,33 @@ impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> { Ok(output) } + /// Decode into a new vector of bytes. + /// + /// This method decodes multiple bytes simultaneously and allocates more memory than strictly + /// necessary (by a constant number of bytes). Simultaneously, this method does not obey the + /// `Check::Enabled` flag. + #[cfg(feature = "alloc")] + #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] + pub fn into_vec_unsafe(self) -> Result> { + let mut output = Vec::::new(); + output.resize((self.input.as_ref().len() + 3) / 4 * 4, 0); + + // Prevent running `output`'s destructor so we are in complete control + // of the allocation. + let mut output = std::mem::ManuallyDrop::new(output); + + // Pull out the various important pieces of information about `output` + let p = output.as_mut_ptr(); + let len = output.len(); + let cap = output.capacity(); + + let mut output = unsafe { Vec::::from_raw_parts(p as *mut u8, len * 4, cap * 4) }; + + let len = decode_into_limbs(self.input.as_ref(), &mut output, self.alpha)?; + output.truncate(len); + Ok(output) + } + /// Decode into the given buffer. /// /// Returns the length written into the buffer. @@ -306,6 +333,96 @@ fn decode_into(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Result Result { + let input_bytes_per_limb = 5; // 58**5 < 2**32 + + let decode_input_byte = |(i, c): (usize, &u8)| -> Result { + if *c > 127 { + return Err(Error::NonAsciiCharacter { index: i }); + } + + let val = alpha.decode[*c as usize] as usize; + if val == 0xFF { + return Err(Error::InvalidCharacter { + character: *c as char, + index: i, + }); + } + Ok(val) + }; + + let mut index = 0; + let mut input_iter = input.iter().enumerate(); + let next_limb_multiplier = 58 * 58 * 58 * 58 * 58; + + let (prefix, output_as_limbs, _) = unsafe { output.align_to_mut::() }; + if prefix.len() != 0 { + // invariant + return Err(Error::BufferTooSmall); + } + + while input_iter.len() > input_bytes_per_limb { + let input_byte0 = decode_input_byte(input_iter.next().unwrap())?; + let input_byte1 = decode_input_byte(input_iter.next().unwrap())?; + let input_byte2 = decode_input_byte(input_iter.next().unwrap())?; + let input_byte3 = decode_input_byte(input_iter.next().unwrap())?; + let input_byte4 = decode_input_byte(input_iter.next().unwrap())?; + + let mut next_limb + = input_byte0 * 58 * 58 * 58 * 58 + + input_byte1 * 58 * 58 * 58 + + input_byte2 * 58 * 58 + + input_byte3 * 58 + + input_byte4 + ; + + for limb in &mut output_as_limbs[..index] { + next_limb += (*limb as usize) * next_limb_multiplier; + *limb = (next_limb & 0xFFFFFFFF) as u32; + next_limb >>= 32; + } + + while next_limb > 0 { + let limb = output_as_limbs.get_mut(index).ok_or(Error::BufferTooSmall)?; + *limb = (next_limb & 0xFFFFFFFF) as u32; + index += 1; + next_limb >>= 32; + } + } + + // rescale for the remainder + index = index * 4; + while index > 0 && output[index - 1] == 0 { + index -= 1; + } + for input_byte in input_iter { + let mut val = decode_input_byte(input_byte)?; + + for byte in &mut output[..index] { + val += (*byte as usize) * 58; + *byte = (val & 0xFF) as u8; + val >>= 8; + } + + while val > 0 { + let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?; + *byte = (val & 0xFF) as u8; + index += 1; + val >>= 8 + } + } + + let zero = alpha.encode[0]; + for _ in input.iter().take_while(|c| **c == zero) { + let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?; + *byte = 0; + index += 1; + } + + output[..index].reverse(); + Ok(index) +} + #[cfg(feature = "check")] fn decode_check_into( input: &[u8], diff --git a/tests/decode.rs b/tests/decode.rs index 74cc7e4..856fe80 100644 --- a/tests/decode.rs +++ b/tests/decode.rs @@ -7,6 +7,7 @@ use assert_matches::assert_matches; fn test_decode() { for &(val, s) in cases::TEST_CASES.iter() { assert_eq!(val.to_vec(), bs58::decode(s).into_vec().unwrap()); + assert_eq!(val.to_vec(), bs58::decode::DecodeBuilder::new(s, bs58::Alphabet::DEFAULT).into_vec_unsafe().unwrap()); } } From 90b79c0cbbfa5f4ecac3742e572185ffca49e18e Mon Sep 17 00:00:00 2001 From: Lawrence Wu Date: Wed, 11 May 2022 15:17:02 +0530 Subject: [PATCH 02/11] Fix main loop ending condition --- src/decode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decode.rs b/src/decode.rs index 1198aee..e2a2607 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -361,7 +361,7 @@ fn decode_into_limbs(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Resul return Err(Error::BufferTooSmall); } - while input_iter.len() > input_bytes_per_limb { + while input_iter.len() >= input_bytes_per_limb { let input_byte0 = decode_input_byte(input_iter.next().unwrap())?; let input_byte1 = decode_input_byte(input_iter.next().unwrap())?; let input_byte2 = decode_input_byte(input_iter.next().unwrap())?; From c8b5a90b3426178decf4d6f26205a19bff13d51b Mon Sep 17 00:00:00 2001 From: Lawrence Wu Date: Wed, 11 May 2022 15:17:28 +0530 Subject: [PATCH 03/11] Do remaining bytes in 1 multiply loop --- src/decode.rs | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/decode.rs b/src/decode.rs index e2a2607..73a8e96 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -390,28 +390,34 @@ fn decode_into_limbs(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Resul } } - // rescale for the remainder - index = index * 4; - while index > 0 && output[index - 1] == 0 { - index -= 1; - } - for input_byte in input_iter { - let mut val = decode_input_byte(input_byte)?; + if input_iter.len() > 0 { + let mut next_limb = 0; + let mut last_limb_multiplier = 1; + for input_byte in input_iter { + next_limb = next_limb * 58 + decode_input_byte(input_byte)?; + last_limb_multiplier = last_limb_multiplier * 58; + } - for byte in &mut output[..index] { - val += (*byte as usize) * 58; - *byte = (val & 0xFF) as u8; - val >>= 8; + for limb in &mut output_as_limbs[..index] { + next_limb += (*limb as usize) * last_limb_multiplier; + *limb = (next_limb & 0xFFFFFFFF) as u32; + next_limb >>= 32; } - while val > 0 { - let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?; - *byte = (val & 0xFF) as u8; + while next_limb > 0 { + let limb = output_as_limbs.get_mut(index).ok_or(Error::BufferTooSmall)?; + *limb = (next_limb & 0xFFFFFFFF) as u32; index += 1; - val >>= 8 + next_limb >>= 32; } } + // rescale for the remainder + index = index * 4; + while index > 0 && output[index - 1] == 0 { + index -= 1; + } + let zero = alpha.encode[0]; for _ in input.iter().take_while(|c| **c == zero) { let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?; From 19d7487a8611dd036ecb4f196a3368079e492510 Mon Sep 17 00:00:00 2001 From: Lawrence Wu Date: Wed, 11 May 2022 15:33:03 +0530 Subject: [PATCH 04/11] Add benchmarks for decode::into_vec_unsafe --- benches/decode.rs | 6 ++++++ tests/decode.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/benches/decode.rs b/benches/decode.rs index 3996fef..edd0901 100644 --- a/benches/decode.rs +++ b/benches/decode.rs @@ -24,6 +24,9 @@ macro_rules! group_decode { let mut output = [0; $decoded_length]; b.iter(|| bs58::decode($encoded).into(&mut output).unwrap()); }); + group.bench_function("decode_bs58_unsafe", |b| { + b.iter(|| bs58::decode($encoded).into_vec_unsafe().unwrap()) + }); group.finish(); }}; } @@ -44,6 +47,9 @@ macro_rules! group_decode_long { let mut output = [0; $decoded_length]; b.iter(|| bs58::decode($encoded).into(&mut output[..]).unwrap()); }); + group.bench_function("decode_bs58_unsafe", |b| { + b.iter(|| bs58::decode($encoded).into_vec_unsafe().unwrap()) + }); // bs58_noalloc_array is not possible because of limited array lengths in trait impls group.finish(); }}; diff --git a/tests/decode.rs b/tests/decode.rs index 856fe80..938ad4b 100644 --- a/tests/decode.rs +++ b/tests/decode.rs @@ -7,7 +7,7 @@ use assert_matches::assert_matches; fn test_decode() { for &(val, s) in cases::TEST_CASES.iter() { assert_eq!(val.to_vec(), bs58::decode(s).into_vec().unwrap()); - assert_eq!(val.to_vec(), bs58::decode::DecodeBuilder::new(s, bs58::Alphabet::DEFAULT).into_vec_unsafe().unwrap()); + assert_eq!(val.to_vec(), bs58::decode(s).into_vec_unsafe().unwrap()); } } From 58e8a6c207d5d3d285b03ad5e42642c15216c522 Mon Sep 17 00:00:00 2001 From: Lawrence Wu Date: Wed, 11 May 2022 16:56:59 +0530 Subject: [PATCH 05/11] Implement batched encode --- benches/encode.rs | 6 +++ src/encode.rs | 118 ++++++++++++++++++++++++++++++++++++++++++++++ tests/encode.rs | 1 + 3 files changed, 125 insertions(+) diff --git a/benches/encode.rs b/benches/encode.rs index 1e9ecd8..9e824c5 100644 --- a/benches/encode.rs +++ b/benches/encode.rs @@ -20,6 +20,12 @@ macro_rules! group_encode { let mut output = String::with_capacity($encoded.len()); b.iter(|| bs58::encode($decoded).into(&mut output)); }); + group.bench_function("encode_bs58_vec", |b| { + b.iter(|| bs58::encode($decoded).into_vec()) + }); + group.bench_function("encode_bs58_vec_unsafe", |b| { + b.iter(|| bs58::encode($decoded).into_vec_unsafe()) + }); group.finish(); }}; } diff --git a/src/encode.rs b/src/encode.rs index 12ce14e..54cda5e 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -248,6 +248,28 @@ impl<'a, I: AsRef<[u8]>> EncodeBuilder<'a, I> { output } + /// Encode into a new owned vector. + pub fn into_vec_unsafe(self) -> Vec { + let mut output = Vec::::new(); + let max_encoded_len = (self.input.as_ref().len() / 5 + 1) * 8; + output.resize((max_encoded_len + 3) / 4 * 4, 0); + + // Prevent running `output`'s destructor so we are in complete control + // of the allocation. + let mut output = std::mem::ManuallyDrop::new(output); + + // Pull out the various important pieces of information about `output` + let p = output.as_mut_ptr(); + let len = output.len(); + let cap = output.capacity(); + + let mut output = unsafe { Vec::::from_raw_parts(p as *mut u8, len * 4, cap * 4) }; + + let len = encode_into_limbs(self.input.as_ref(), &mut output, self.alpha).unwrap(); + output.truncate(len); + output + } + /// Encode into the given buffer. /// /// Returns the length written into the buffer. @@ -370,6 +392,102 @@ where Ok(index) } +fn encode_into_limbs<'a, I, II>(input: I, output: &mut [u8], alpha: &Alphabet) -> Result +where + I: Clone + IntoIterator, + II: ExactSizeIterator, +{ + let input_bytes_per_limb = 3; + let (prefix, output_as_limbs, _) = unsafe { output.align_to_mut::() }; + if prefix.len() != 0 { + // invariant + return Err(Error::BufferTooSmall); + } + + let mut index = 0; + let mut input_iter = input.clone().into_iter(); + let next_limb_divisor = 58 * 58 * 58 * 58; + while input_iter.len() >= input_bytes_per_limb { + let input_byte0 = *input_iter.next().unwrap() as usize; + let input_byte1 = *input_iter.next().unwrap() as usize; + let input_byte2 = *input_iter.next().unwrap() as usize; + + let mut carry + = (input_byte0 << 16) + + (input_byte1 << 8) + + input_byte2 + ; + + for limb in &mut output_as_limbs[..index] { + carry += (*limb as usize) << 24; + *limb = (carry % next_limb_divisor) as u32; + carry /= next_limb_divisor; + } + + while carry > 0 { + let limb = output_as_limbs.get_mut(index).ok_or(Error::BufferTooSmall)?; + *limb = (carry % next_limb_divisor) as u32; + index += 1; + carry /= next_limb_divisor; + } + } + + if input_iter.len() > 0 { + let mut carry = 0; + let mut shift_size = 0; + for input_byte in input_iter { + carry = carry * 256 + *input_byte as usize; + shift_size = shift_size + 8; + } + + for limb in &mut output_as_limbs[..index] { + carry += (*limb as usize) << shift_size; + *limb = (carry % next_limb_divisor) as u32; + carry /= next_limb_divisor; + } + + while carry > 0 { + let limb = output_as_limbs.get_mut(index).ok_or(Error::BufferTooSmall)?; + *limb = (carry % next_limb_divisor) as u32; + index += 1; + carry /= next_limb_divisor; + } + } + + for limb in &mut output_as_limbs[..index] { + let output_byte0 = *limb / (58 * 58 * 58); + let output_byte1 = (*limb / (58 * 58)) % 58; + let output_byte2 = (*limb / 58) % 58; + let output_byte3 = *limb % 58; + + // TODO: endianness + *limb = (output_byte0 << 24) + + (output_byte1 << 16) + + (output_byte2 << 8) + + output_byte3 + ; + } + + // rescale for the remainder + index = index * 4; + while index > 0 && output[index - 1] == 0 { + index -= 1; + } + + for _ in input.into_iter().take_while(|v| **v == 0) { + let byte = output.get_mut(index).ok_or(Error::BufferTooSmall)?; + *byte = 0; + index += 1; + } + + for val in &mut output[..index] { + *val = alpha.encode[*val as usize]; + } + + output[..index].reverse(); + Ok(index) +} + #[cfg(feature = "check")] fn encode_check_into( input: &[u8], diff --git a/tests/encode.rs b/tests/encode.rs index 0adec18..2a3db00 100644 --- a/tests/encode.rs +++ b/tests/encode.rs @@ -8,6 +8,7 @@ fn test_encode() { assert_eq!(s, bs58::encode(val).into_string()); assert_eq!(s.as_bytes(), &*bs58::encode(val).into_vec()); + assert_eq!(s.as_bytes(), &*bs58::encode(val).into_vec_unsafe()); { let mut bytes = FILLER; From 13823085bc83a50a16f20d9050311ac6788dfa22 Mon Sep 17 00:00:00 2001 From: Lawrence Wu Date: Wed, 11 May 2022 19:04:56 +0530 Subject: [PATCH 06/11] Handle unaligned output instead of using UB unsafe allocs - Vec::from_raw_parts is much stricter than anticipated and requires deallocation to happen with the same alignment --- src/decode.rs | 26 ++++++++++---------------- src/encode.rs | 26 ++++++++++---------------- 2 files changed, 20 insertions(+), 32 deletions(-) diff --git a/src/decode.rs b/src/decode.rs index 73a8e96..c43bfa7 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -227,20 +227,9 @@ impl<'a, I: AsRef<[u8]>> DecodeBuilder<'a, I> { #[cfg(feature = "alloc")] #[cfg_attr(docsrs, doc(cfg(any(feature = "alloc", feature = "std"))))] pub fn into_vec_unsafe(self) -> Result> { - let mut output = Vec::::new(); + let mut output = Vec::new(); output.resize((self.input.as_ref().len() + 3) / 4 * 4, 0); - // Prevent running `output`'s destructor so we are in complete control - // of the allocation. - let mut output = std::mem::ManuallyDrop::new(output); - - // Pull out the various important pieces of information about `output` - let p = output.as_mut_ptr(); - let len = output.len(); - let cap = output.capacity(); - - let mut output = unsafe { Vec::::from_raw_parts(p as *mut u8, len * 4, cap * 4) }; - let len = decode_into_limbs(self.input.as_ref(), &mut output, self.alpha)?; output.truncate(len); Ok(output) @@ -356,10 +345,7 @@ fn decode_into_limbs(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Resul let next_limb_multiplier = 58 * 58 * 58 * 58 * 58; let (prefix, output_as_limbs, _) = unsafe { output.align_to_mut::() }; - if prefix.len() != 0 { - // invariant - return Err(Error::BufferTooSmall); - } + let prefix_len = prefix.len(); while input_iter.len() >= input_bytes_per_limb { let input_byte0 = decode_input_byte(input_iter.next().unwrap())?; @@ -414,6 +400,8 @@ fn decode_into_limbs(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Resul // rescale for the remainder index = index * 4; + { + let output = &mut output[prefix_len..]; while index > 0 && output[index - 1] == 0 { index -= 1; } @@ -426,6 +414,12 @@ fn decode_into_limbs(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Resul } output[..index].reverse(); + } + + if prefix_len > 0 { + output.copy_within(prefix_len..prefix_len + index, 0); + } + Ok(index) } diff --git a/src/encode.rs b/src/encode.rs index 54cda5e..b69e12b 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -250,21 +250,10 @@ impl<'a, I: AsRef<[u8]>> EncodeBuilder<'a, I> { /// Encode into a new owned vector. pub fn into_vec_unsafe(self) -> Vec { - let mut output = Vec::::new(); + let mut output = Vec::new(); let max_encoded_len = (self.input.as_ref().len() / 5 + 1) * 8; output.resize((max_encoded_len + 3) / 4 * 4, 0); - // Prevent running `output`'s destructor so we are in complete control - // of the allocation. - let mut output = std::mem::ManuallyDrop::new(output); - - // Pull out the various important pieces of information about `output` - let p = output.as_mut_ptr(); - let len = output.len(); - let cap = output.capacity(); - - let mut output = unsafe { Vec::::from_raw_parts(p as *mut u8, len * 4, cap * 4) }; - let len = encode_into_limbs(self.input.as_ref(), &mut output, self.alpha).unwrap(); output.truncate(len); output @@ -399,10 +388,7 @@ where { let input_bytes_per_limb = 3; let (prefix, output_as_limbs, _) = unsafe { output.align_to_mut::() }; - if prefix.len() != 0 { - // invariant - return Err(Error::BufferTooSmall); - } + let prefix_len = prefix.len(); let mut index = 0; let mut input_iter = input.clone().into_iter(); @@ -470,6 +456,8 @@ where // rescale for the remainder index = index * 4; + { + let output = &mut output[prefix_len..]; while index > 0 && output[index - 1] == 0 { index -= 1; } @@ -485,6 +473,12 @@ where } output[..index].reverse(); + } + + if prefix_len > 0 { + output.copy_within(prefix_len..prefix_len + index, 0); + } + Ok(index) } From 6ae3c07cfdac9a1a77b75cb27ff49b9130a4f38c Mon Sep 17 00:00:00 2001 From: Lawrence Wu Date: Wed, 11 May 2022 19:32:52 +0530 Subject: [PATCH 07/11] Encode with 5 base58 bytes per limb - slightly more complex expansion from limb to output bytes but pretty solid perf gains --- src/encode.rs | 54 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/encode.rs b/src/encode.rs index b69e12b..02ee8e3 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -252,7 +252,7 @@ impl<'a, I: AsRef<[u8]>> EncodeBuilder<'a, I> { pub fn into_vec_unsafe(self) -> Vec { let mut output = Vec::new(); let max_encoded_len = (self.input.as_ref().len() / 5 + 1) * 8; - output.resize((max_encoded_len + 3) / 4 * 4, 0); + output.resize((max_encoded_len + 4) / 5 * 5, 0); let len = encode_into_limbs(self.input.as_ref(), &mut output, self.alpha).unwrap(); output.truncate(len); @@ -386,26 +386,28 @@ where I: Clone + IntoIterator, II: ExactSizeIterator, { - let input_bytes_per_limb = 3; + let input_bytes_per_limb = 4; let (prefix, output_as_limbs, _) = unsafe { output.align_to_mut::() }; let prefix_len = prefix.len(); let mut index = 0; let mut input_iter = input.clone().into_iter(); - let next_limb_divisor = 58 * 58 * 58 * 58; + let next_limb_divisor = 58 * 58 * 58 * 58 * 58; while input_iter.len() >= input_bytes_per_limb { let input_byte0 = *input_iter.next().unwrap() as usize; let input_byte1 = *input_iter.next().unwrap() as usize; let input_byte2 = *input_iter.next().unwrap() as usize; + let input_byte3 = *input_iter.next().unwrap() as usize; let mut carry - = (input_byte0 << 16) - + (input_byte1 << 8) - + input_byte2 + = (input_byte0 << 24) + + (input_byte1 << 16) + + (input_byte2 << 8) + + input_byte3 ; for limb in &mut output_as_limbs[..index] { - carry += (*limb as usize) << 24; + carry += (*limb as usize) << 32; *limb = (carry % next_limb_divisor) as u32; carry /= next_limb_divisor; } @@ -440,22 +442,34 @@ where } } - for limb in &mut output_as_limbs[..index] { - let output_byte0 = *limb / (58 * 58 * 58); - let output_byte1 = (*limb / (58 * 58)) % 58; - let output_byte2 = (*limb / 58) % 58; - let output_byte3 = *limb % 58; - - // TODO: endianness - *limb = (output_byte0 << 24) - + (output_byte1 << 16) - + (output_byte2 << 8) - + output_byte3 - ; + for index in (0..index).rev() { + let limb_offset = prefix_len + index * 4; + let mut limb_bytes = [0; 4]; + limb_bytes.copy_from_slice(&output[limb_offset..limb_offset+4]); + let limb = if cfg!(target_endian = "little") { + u32::from_le_bytes(limb_bytes) + } else { + u32::from_be_bytes(limb_bytes) + }; + + let output_byte4 = limb / (58 * 58 * 58 * 58); + let output_byte3 = (limb / (58 * 58 * 58)) % 58; + let output_byte2 = (limb / (58 * 58)) % 58; + let output_byte1 = (limb / 58) % 58; + let output_byte0 = limb % 58; + + let output_offset = prefix_len + index * 5; + let output_bytes = &mut output[output_offset..]; + // write in LE? + output_bytes[0] = output_byte0 as u8; + output_bytes[1] = output_byte1 as u8; + output_bytes[2] = output_byte2 as u8; + output_bytes[3] = output_byte3 as u8; + output_bytes[4] = output_byte4 as u8; } // rescale for the remainder - index = index * 4; + index = index * 5; { let output = &mut output[prefix_len..]; while index > 0 && output[index - 1] == 0 { From 38468adb26c7ec0a96700c11adb88abfa91e0538 Mon Sep 17 00:00:00 2001 From: Lawrence Wu Date: Wed, 11 May 2022 19:41:26 +0530 Subject: [PATCH 08/11] Use bytemuck instead of raw unsafe align --- Cargo.lock | 9 +++++++++ Cargo.toml | 1 + src/decode.rs | 2 +- src/encode.rs | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ecad910..2c0f613 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ansi_term" version = "0.11.0" @@ -65,6 +67,7 @@ version = "0.4.0" dependencies = [ "assert_matches", "base58", + "bytemuck", "criterion", "rust-base58", "sha2", @@ -97,6 +100,12 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" +[[package]] +name = "bytemuck" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" + [[package]] name = "byteorder" version = "1.3.4" diff --git a/Cargo.toml b/Cargo.toml index 1e21c52..363aec6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ alloc = [] check = ["sha2"] [dependencies] +bytemuck = "1.9.1" sha2 = { version = "0.9.0", optional = true, default-features = false } [dev_dependencies] diff --git a/src/decode.rs b/src/decode.rs index c43bfa7..f3ee19d 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -344,7 +344,7 @@ fn decode_into_limbs(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Resul let mut input_iter = input.iter().enumerate(); let next_limb_multiplier = 58 * 58 * 58 * 58 * 58; - let (prefix, output_as_limbs, _) = unsafe { output.align_to_mut::() }; + let (prefix, output_as_limbs, _) = bytemuck::pod_align_to_mut::(output); let prefix_len = prefix.len(); while input_iter.len() >= input_bytes_per_limb { diff --git a/src/encode.rs b/src/encode.rs index 02ee8e3..dcbaf5c 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -387,7 +387,7 @@ where II: ExactSizeIterator, { let input_bytes_per_limb = 4; - let (prefix, output_as_limbs, _) = unsafe { output.align_to_mut::() }; + let (prefix, output_as_limbs, _) = bytemuck::pod_align_to_mut::(output); let prefix_len = prefix.len(); let mut index = 0; From f6e175cdc7c0a3dbc395bb422723d2b21196ff62 Mon Sep 17 00:00:00 2001 From: Lawrence Wu Date: Tue, 17 May 2022 09:24:18 -0500 Subject: [PATCH 09/11] Small slice indexing optimization --- src/encode.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/encode.rs b/src/encode.rs index dcbaf5c..98ca309 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -459,13 +459,13 @@ where let output_byte0 = limb % 58; let output_offset = prefix_len + index * 5; - let output_bytes = &mut output[output_offset..]; - // write in LE? - output_bytes[0] = output_byte0 as u8; - output_bytes[1] = output_byte1 as u8; - output_bytes[2] = output_byte2 as u8; - output_bytes[3] = output_byte3 as u8; - output_bytes[4] = output_byte4 as u8; + output[output_offset..output_offset+5].copy_from_slice(&[ + output_byte0 as u8, + output_byte1 as u8, + output_byte2 as u8, + output_byte3 as u8, + output_byte4 as u8, + ]); } // rescale for the remainder From faaa09c9c884d494998c029c0f3ff7f19df342d7 Mon Sep 17 00:00:00 2001 From: Lawrence Wu Date: Tue, 17 May 2022 09:24:43 -0500 Subject: [PATCH 10/11] Add sanity check for encoded output buffer length --- src/encode.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/encode.rs b/src/encode.rs index 98ca309..8ca77a9 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -442,6 +442,11 @@ where } } + // shouldn't happen since we control the output buffer passed in... + if output.len() < prefix_len + index * 5 { + return Err(Error::BufferTooSmall); + } + for index in (0..index).rev() { let limb_offset = prefix_len + index * 4; let mut limb_bytes = [0; 4]; From 5926ef2e9eea308723e3e2c02b81a887e1cf45a0 Mon Sep 17 00:00:00 2001 From: Lawrence Wu Date: Tue, 17 May 2022 09:29:42 -0500 Subject: [PATCH 11/11] Simplify en/decoding chunks loops - unrolling apparently does not help with performance. this simplifies a lot of the indexing and is also seemingly faster --- src/decode.rs | 45 +++++++-------------------------------------- src/encode.rs | 39 ++++----------------------------------- 2 files changed, 11 insertions(+), 73 deletions(-) diff --git a/src/decode.rs b/src/decode.rs index f3ee19d..bb4c3ce 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -325,15 +325,15 @@ fn decode_into(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Result Result { let input_bytes_per_limb = 5; // 58**5 < 2**32 - let decode_input_byte = |(i, c): (usize, &u8)| -> Result { - if *c > 127 { + let decode_input_byte = |i: usize, c: u8| -> Result { + if c > 127 { return Err(Error::NonAsciiCharacter { index: i }); } - let val = alpha.decode[*c as usize] as usize; + let val = alpha.decode[c as usize] as usize; if val == 0xFF { return Err(Error::InvalidCharacter { - character: *c as char, + character: c as char, index: i, }); } @@ -341,46 +341,15 @@ fn decode_into_limbs(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Resul }; let mut index = 0; - let mut input_iter = input.iter().enumerate(); - let next_limb_multiplier = 58 * 58 * 58 * 58 * 58; let (prefix, output_as_limbs, _) = bytemuck::pod_align_to_mut::(output); let prefix_len = prefix.len(); - while input_iter.len() >= input_bytes_per_limb { - let input_byte0 = decode_input_byte(input_iter.next().unwrap())?; - let input_byte1 = decode_input_byte(input_iter.next().unwrap())?; - let input_byte2 = decode_input_byte(input_iter.next().unwrap())?; - let input_byte3 = decode_input_byte(input_iter.next().unwrap())?; - let input_byte4 = decode_input_byte(input_iter.next().unwrap())?; - - let mut next_limb - = input_byte0 * 58 * 58 * 58 * 58 - + input_byte1 * 58 * 58 * 58 - + input_byte2 * 58 * 58 - + input_byte3 * 58 - + input_byte4 - ; - - for limb in &mut output_as_limbs[..index] { - next_limb += (*limb as usize) * next_limb_multiplier; - *limb = (next_limb & 0xFFFFFFFF) as u32; - next_limb >>= 32; - } - - while next_limb > 0 { - let limb = output_as_limbs.get_mut(index).ok_or(Error::BufferTooSmall)?; - *limb = (next_limb & 0xFFFFFFFF) as u32; - index += 1; - next_limb >>= 32; - } - } - - if input_iter.len() > 0 { + for (chunk_idx, chunk) in input.chunks(input_bytes_per_limb).enumerate() { let mut next_limb = 0; let mut last_limb_multiplier = 1; - for input_byte in input_iter { - next_limb = next_limb * 58 + decode_input_byte(input_byte)?; + for (byte_idx, input_byte) in chunk.into_iter().enumerate() { + next_limb = next_limb * 58 + decode_input_byte(chunk_idx * 4 + byte_idx, *input_byte)?; last_limb_multiplier = last_limb_multiplier * 58; } diff --git a/src/encode.rs b/src/encode.rs index 8ca77a9..c06eb13 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -381,50 +381,19 @@ where Ok(index) } -fn encode_into_limbs<'a, I, II>(input: I, output: &mut [u8], alpha: &Alphabet) -> Result -where - I: Clone + IntoIterator, - II: ExactSizeIterator, +fn encode_into_limbs(input: &[u8], output: &mut [u8], alpha: &Alphabet) -> Result { let input_bytes_per_limb = 4; let (prefix, output_as_limbs, _) = bytemuck::pod_align_to_mut::(output); let prefix_len = prefix.len(); let mut index = 0; - let mut input_iter = input.clone().into_iter(); let next_limb_divisor = 58 * 58 * 58 * 58 * 58; - while input_iter.len() >= input_bytes_per_limb { - let input_byte0 = *input_iter.next().unwrap() as usize; - let input_byte1 = *input_iter.next().unwrap() as usize; - let input_byte2 = *input_iter.next().unwrap() as usize; - let input_byte3 = *input_iter.next().unwrap() as usize; - - let mut carry - = (input_byte0 << 24) - + (input_byte1 << 16) - + (input_byte2 << 8) - + input_byte3 - ; - - for limb in &mut output_as_limbs[..index] { - carry += (*limb as usize) << 32; - *limb = (carry % next_limb_divisor) as u32; - carry /= next_limb_divisor; - } - - while carry > 0 { - let limb = output_as_limbs.get_mut(index).ok_or(Error::BufferTooSmall)?; - *limb = (carry % next_limb_divisor) as u32; - index += 1; - carry /= next_limb_divisor; - } - } - - if input_iter.len() > 0 { + for chunk in input.chunks(input_bytes_per_limb) { let mut carry = 0; let mut shift_size = 0; - for input_byte in input_iter { - carry = carry * 256 + *input_byte as usize; + for input_byte in chunk { + carry = (carry << 8) + *input_byte as usize; shift_size = shift_size + 8; }