Skip to content

Commit c2316bd

Browse files
committed
Add buffering during encode
Currently we write char at a time to both writers (`fmt::Write` and `std::io::Write`). We can improve performance by first collecting the ASCII bytes of the encoded bech32 string into a buffer on the stack then writing the buffer as a single call. This is purely an optimization.
1 parent 487655c commit c2316bd

File tree

2 files changed

+114
-36
lines changed

2 files changed

+114
-36
lines changed

src/lib.rs

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ pub use {
160160
crate::primitives::{Bech32, Bech32m, NoChecksum},
161161
};
162162

163+
/// A bech32 string is at most 90 characters long.
164+
pub const BECH32_MAX_LENGTH: usize = 90;
165+
163166
/// Decodes a bech32 encoded string.
164167
///
165168
/// If this function succeeds the input string was found to be well formed (hrp, separator, bech32
@@ -265,11 +268,20 @@ pub fn encode_lower_to_fmt<Ck: Checksum, W: fmt::Write>(
265268
hrp: Hrp,
266269
data: &[u8],
267270
) -> Result<(), fmt::Error> {
271+
let mut buf = [0u8; BECH32_MAX_LENGTH];
272+
let mut pos = 0;
273+
268274
let iter = data.iter().copied().bytes_to_fes();
269275
let chars = iter.with_checksum::<Ck>(&hrp).chars();
270-
for c in chars {
271-
fmt.write_char(c)?;
272-
}
276+
277+
buf.iter_mut().zip(chars).for_each(|(b, c)| {
278+
*b = c as u8;
279+
pos += 1;
280+
});
281+
282+
let s = core::str::from_utf8(&buf[..pos]).expect("we only write ASCII");
283+
fmt.write_str(s)?;
284+
273285
Ok(())
274286
}
275287

@@ -283,11 +295,20 @@ pub fn encode_upper_to_fmt<Ck: Checksum, W: fmt::Write>(
283295
hrp: Hrp,
284296
data: &[u8],
285297
) -> Result<(), fmt::Error> {
298+
let mut buf = [0u8; BECH32_MAX_LENGTH];
299+
let mut pos = 0;
300+
286301
let iter = data.iter().copied().bytes_to_fes();
287302
let chars = iter.with_checksum::<Ck>(&hrp).chars();
288-
for c in chars {
289-
fmt.write_char(c.to_ascii_uppercase())?;
290-
}
303+
304+
buf.iter_mut().zip(chars).for_each(|(b, c)| {
305+
*b = c.to_ascii_uppercase() as u8;
306+
pos += 1;
307+
});
308+
309+
let s = core::str::from_utf8(&buf[..pos]).expect("we only write ASCII");
310+
fmt.write_str(s)?;
311+
291312
Ok(())
292313
}
293314

@@ -316,11 +337,19 @@ pub fn encode_lower_to_writer<Ck: Checksum, W: std::io::Write>(
316337
hrp: Hrp,
317338
data: &[u8],
318339
) -> Result<(), std::io::Error> {
340+
let mut buf = [0u8; BECH32_MAX_LENGTH];
341+
let mut pos = 0;
342+
319343
let iter = data.iter().copied().bytes_to_fes();
320344
let chars = iter.with_checksum::<Ck>(&hrp).chars();
321-
for c in chars {
322-
w.write_all(&[c as u8])?;
323-
}
345+
346+
buf.iter_mut().zip(chars).for_each(|(b, c)| {
347+
*b = c as u8;
348+
pos += 1;
349+
});
350+
351+
w.write_all(&buf[..pos])?;
352+
324353
Ok(())
325354
}
326355

@@ -335,11 +364,19 @@ pub fn encode_upper_to_writer<Ck: Checksum, W: std::io::Write>(
335364
hrp: Hrp,
336365
data: &[u8],
337366
) -> Result<(), std::io::Error> {
367+
let mut buf = [0u8; BECH32_MAX_LENGTH];
368+
let mut pos = 0;
369+
338370
let iter = data.iter().copied().bytes_to_fes();
339371
let chars = iter.with_checksum::<Ck>(&hrp).chars();
340-
for c in chars {
341-
w.write_all(&[c.to_ascii_uppercase() as u8])?;
342-
}
372+
373+
buf.iter_mut().zip(chars).for_each(|(b, c)| {
374+
*b = c.to_ascii_uppercase() as u8;
375+
pos += 1;
376+
});
377+
378+
w.write_all(&buf[..pos])?;
379+
343380
Ok(())
344381
}
345382

src/segwit.rs

Lines changed: 65 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ use crate::primitives::iter::{ByteIterExt, Fe32IterExt};
5454
use crate::primitives::segwit;
5555
use crate::primitives::segwit::{InvalidWitnessVersionError, WitnessLengthError};
5656
use crate::primitives::{Bech32, Bech32m};
57+
use crate::BECH32_MAX_LENGTH;
5758

5859
#[rustfmt::skip] // Keep public re-exports separate.
5960
#[doc(inline)]
@@ -144,19 +145,30 @@ pub fn encode_lower_to_fmt_unchecked<W: fmt::Write>(
144145
witness_version: Fe32,
145146
witness_program: &[u8],
146147
) -> fmt::Result {
148+
let mut buf = [0u8; BECH32_MAX_LENGTH];
149+
let mut pos = 0;
150+
147151
let iter = witness_program.iter().copied().bytes_to_fes();
148152
match witness_version {
149153
VERSION_0 => {
150-
for c in iter.with_checksum::<Bech32>(hrp).with_witness_version(VERSION_0).chars() {
151-
fmt.write_char(c)?;
152-
}
154+
let chars = iter.with_checksum::<Bech32>(hrp).with_witness_version(VERSION_0).chars();
155+
buf.iter_mut().zip(chars).for_each(|(b, c)| {
156+
*b = c as u8;
157+
pos += 1;
158+
});
153159
}
154160
version => {
155-
for c in iter.with_checksum::<Bech32m>(hrp).with_witness_version(version).chars() {
156-
fmt.write_char(c)?;
157-
}
161+
let chars = iter.with_checksum::<Bech32m>(hrp).with_witness_version(version).chars();
162+
buf.iter_mut().zip(chars).for_each(|(b, c)| {
163+
*b = c as u8;
164+
pos += 1;
165+
});
158166
}
159167
}
168+
169+
let s = core::str::from_utf8(&buf[..pos]).expect("we only write ASCII");
170+
fmt.write_str(s)?;
171+
160172
Ok(())
161173
}
162174

@@ -173,20 +185,30 @@ pub fn encode_upper_to_fmt_unchecked<W: fmt::Write>(
173185
witness_version: Fe32,
174186
witness_program: &[u8],
175187
) -> fmt::Result {
188+
let mut buf = [0u8; BECH32_MAX_LENGTH];
189+
let mut pos = 0;
190+
176191
let iter = witness_program.iter().copied().bytes_to_fes();
177192
match witness_version {
178193
VERSION_0 => {
179-
for c in iter.with_checksum::<Bech32>(hrp).with_witness_version(VERSION_0).chars() {
180-
fmt.write_char(c.to_ascii_uppercase())?;
181-
}
194+
let chars = iter.with_checksum::<Bech32>(hrp).with_witness_version(VERSION_0).chars();
195+
buf.iter_mut().zip(chars).for_each(|(b, c)| {
196+
*b = c.to_ascii_uppercase() as u8;
197+
pos += 1;
198+
});
182199
}
183200
version => {
184-
for c in iter.with_checksum::<Bech32m>(hrp).with_witness_version(version).chars() {
185-
fmt.write_char(c.to_ascii_uppercase())?;
186-
}
201+
let chars = iter.with_checksum::<Bech32m>(hrp).with_witness_version(version).chars();
202+
buf.iter_mut().zip(chars).for_each(|(b, c)| {
203+
*b = c.to_ascii_uppercase() as u8;
204+
pos += 1;
205+
});
187206
}
188207
}
189208

209+
let s = core::str::from_utf8(&buf[..pos]).expect("we only write ASCII");
210+
fmt.write_str(s)?;
211+
190212
Ok(())
191213
}
192214

@@ -217,19 +239,29 @@ pub fn encode_lower_to_writer_unchecked<W: std::io::Write>(
217239
witness_version: Fe32,
218240
witness_program: &[u8],
219241
) -> std::io::Result<()> {
242+
let mut buf = [0u8; BECH32_MAX_LENGTH];
243+
let mut pos = 0;
244+
220245
let iter = witness_program.iter().copied().bytes_to_fes();
221246
match witness_version {
222247
VERSION_0 => {
223-
for c in iter.with_checksum::<Bech32>(hrp).with_witness_version(VERSION_0).chars() {
224-
w.write_all(&[c as u8])?;
225-
}
248+
let chars = iter.with_checksum::<Bech32>(hrp).with_witness_version(VERSION_0).chars();
249+
buf.iter_mut().zip(chars).for_each(|(b, c)| {
250+
*b = c as u8;
251+
pos += 1;
252+
});
226253
}
227254
version => {
228-
for c in iter.with_checksum::<Bech32m>(hrp).with_witness_version(version).chars() {
229-
w.write_all(&[c as u8])?;
230-
}
255+
let chars = iter.with_checksum::<Bech32m>(hrp).with_witness_version(version).chars();
256+
buf.iter_mut().zip(chars).for_each(|(b, c)| {
257+
*b = c as u8;
258+
pos += 1;
259+
});
231260
}
232261
}
262+
263+
w.write_all(&buf[..pos])?;
264+
233265
Ok(())
234266
}
235267

@@ -247,20 +279,29 @@ pub fn encode_upper_to_writer_unchecked<W: std::io::Write>(
247279
witness_version: Fe32,
248280
witness_program: &[u8],
249281
) -> std::io::Result<()> {
282+
let mut buf = [0u8; BECH32_MAX_LENGTH];
283+
let mut pos = 0;
284+
250285
let iter = witness_program.iter().copied().bytes_to_fes();
251286
match witness_version {
252287
VERSION_0 => {
253-
for c in iter.with_checksum::<Bech32>(hrp).with_witness_version(VERSION_0).chars() {
254-
w.write_all(&[c.to_ascii_uppercase() as u8])?;
255-
}
288+
let chars = iter.with_checksum::<Bech32>(hrp).with_witness_version(VERSION_0).chars();
289+
buf.iter_mut().zip(chars).for_each(|(b, c)| {
290+
*b = c.to_ascii_uppercase() as u8;
291+
pos += 1;
292+
});
256293
}
257294
version => {
258-
for c in iter.with_checksum::<Bech32m>(hrp).with_witness_version(version).chars() {
259-
w.write_all(&[c.to_ascii_uppercase() as u8])?;
260-
}
295+
let chars = iter.with_checksum::<Bech32m>(hrp).with_witness_version(version).chars();
296+
buf.iter_mut().zip(chars).for_each(|(b, c)| {
297+
*b = c.to_ascii_uppercase() as u8;
298+
pos += 1;
299+
});
261300
}
262301
}
263302

303+
w.write_all(&buf[..pos])?;
304+
264305
Ok(())
265306
}
266307

0 commit comments

Comments
 (0)