Skip to content

Commit c632fff

Browse files
committed
Avoid calling libsecp serialization fns when calculating length
When writing out libsecp256k1 objects during serialization in a TLV, we potentially calculate the TLV length twice before performing the actual serialization (once when calculating the total TLV-stream length and once when calculating the length of the secp256k1-object-containing TLV). Because the lengths of secp256k1 objects is a constant, we'd ideally like LLVM to entirely optimize out those calls and simply know the expected length. However, without cross-language LTO, there is no way for LLVM to verify that there are no side-effects of the calls to libsecp256k1, leaving LLVM with no way to optimize them out. This commit adds a new method to `Writeable` which returns the length of an object once serialized. It is implemented by default using `LengthCalculatingWriter` (which LLVM generally optimizes out for Rust objects) and overrides it for libsecp256k1 objects. As of this commit, on an Intel 2687W v3, the serialization benchmarks take: test routing::network_graph::benches::read_network_graph ... bench: 2,035,402,164 ns/iter (+/- 1,855,357) test routing::network_graph::benches::write_network_graph ... bench: 308,235,267 ns/iter (+/- 140,202)
1 parent 615419d commit c632fff

File tree

2 files changed

+35
-21
lines changed

2 files changed

+35
-21
lines changed

lightning/src/util/ser.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use core::cmp;
1919

2020
use bitcoin::secp256k1::Signature;
2121
use bitcoin::secp256k1::key::{PublicKey, SecretKey};
22-
use bitcoin::secp256k1::constants::{PUBLIC_KEY_SIZE, COMPACT_SIGNATURE_SIZE};
22+
use bitcoin::secp256k1::constants::{PUBLIC_KEY_SIZE, SECRET_KEY_SIZE, COMPACT_SIGNATURE_SIZE};
2323
use bitcoin::blockdata::script::Script;
2424
use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut};
2525
use bitcoin::consensus;
@@ -185,6 +185,16 @@ pub trait Writeable {
185185
msg.0[..2].copy_from_slice(&(len as u16 - 2).to_be_bytes());
186186
msg.0
187187
}
188+
189+
/// Gets the length of this object after it has been serialized. This can be overridden to
190+
/// optimize cases where we prepend an object with its length.
191+
// Note that LLVM optimizes this away in most cases! Check that it isn't before you override!
192+
#[inline]
193+
fn serialized_length(&self) -> usize {
194+
let mut len_calc = LengthCalculatingWriter(0);
195+
self.write(&mut len_calc).expect("No in-memory data may fail to serialize");
196+
len_calc.0
197+
}
188198
}
189199

190200
impl<'a, T: Writeable> Writeable for &'a T {
@@ -561,6 +571,10 @@ impl Writeable for PublicKey {
561571
fn write<W: Writer>(&self, w: &mut W) -> Result<(), ::std::io::Error> {
562572
self.serialize().write(w)
563573
}
574+
#[inline]
575+
fn serialized_length(&self) -> usize {
576+
PUBLIC_KEY_SIZE
577+
}
564578
}
565579

566580
impl Readable for PublicKey {
@@ -575,15 +589,19 @@ impl Readable for PublicKey {
575589

576590
impl Writeable for SecretKey {
577591
fn write<W: Writer>(&self, w: &mut W) -> Result<(), ::std::io::Error> {
578-
let mut ser = [0; 32];
592+
let mut ser = [0; SECRET_KEY_SIZE];
579593
ser.copy_from_slice(&self[..]);
580594
ser.write(w)
581595
}
596+
#[inline]
597+
fn serialized_length(&self) -> usize {
598+
SECRET_KEY_SIZE
599+
}
582600
}
583601

584602
impl Readable for SecretKey {
585603
fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
586-
let buf: [u8; 32] = Readable::read(r)?;
604+
let buf: [u8; SECRET_KEY_SIZE] = Readable::read(r)?;
587605
match SecretKey::from_slice(&buf) {
588606
Ok(key) => Ok(key),
589607
Err(_) => return Err(DecodeError::InvalidValue),
@@ -610,6 +628,10 @@ impl Writeable for Signature {
610628
fn write<W: Writer>(&self, w: &mut W) -> Result<(), ::std::io::Error> {
611629
self.serialize_compact().write(w)
612630
}
631+
#[inline]
632+
fn serialized_length(&self) -> usize {
633+
COMPACT_SIGNATURE_SIZE
634+
}
613635
}
614636

615637
impl Readable for Signature {
@@ -666,9 +688,7 @@ impl<T: Writeable> Writeable for Option<T> {
666688
match *self {
667689
None => 0u8.write(w)?,
668690
Some(ref data) => {
669-
let mut len_calc = LengthCalculatingWriter(0);
670-
data.write(&mut len_calc).expect("No in-memory data may fail to serialize");
671-
BigSize(len_calc.0 as u64 + 1).write(w)?;
691+
BigSize(data.serialized_length() as u64 + 1).write(w)?;
672692
data.write(w)?;
673693
}
674694
}

lightning/src/util/ser_macros.rs

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
macro_rules! encode_tlv {
1111
($stream: expr, {$(($type: expr, $field: expr)),*}, {$(($optional_type: expr, $optional_field: expr)),*}) => { {
1212
#[allow(unused_imports)]
13-
use util::ser::{BigSize, LengthCalculatingWriter};
13+
use util::ser::BigSize;
1414
// Fields must be serialized in order, so we have to potentially switch between optional
1515
// fields and normal fields while serializing. Thus, we end up having to loop over the type
1616
// counts.
@@ -30,19 +30,15 @@ macro_rules! encode_tlv {
3030
$(
3131
if i == $type {
3232
BigSize($type).write($stream)?;
33-
let mut len_calc = LengthCalculatingWriter(0);
34-
$field.write(&mut len_calc)?;
35-
BigSize(len_calc.0 as u64).write($stream)?;
33+
BigSize($field.serialized_length() as u64).write($stream)?;
3634
$field.write($stream)?;
3735
}
3836
)*
3937
$(
4038
if i == $optional_type {
4139
if let Some(ref field) = $optional_field {
4240
BigSize($optional_type).write($stream)?;
43-
let mut len_calc = LengthCalculatingWriter(0);
44-
field.write(&mut len_calc)?;
45-
BigSize(len_calc.0 as u64).write($stream)?;
41+
BigSize(field.serialized_length() as u64).write($stream)?;
4642
field.write($stream)?;
4743
}
4844
}
@@ -59,18 +55,16 @@ macro_rules! encode_varint_length_prefixed_tlv {
5955
{
6056
$(
6157
BigSize($type).write(&mut len)?;
62-
let mut field_len = LengthCalculatingWriter(0);
63-
$field.write(&mut field_len)?;
64-
BigSize(field_len.0 as u64).write(&mut len)?;
65-
len.0 += field_len.0;
58+
let field_len = $field.serialized_length();
59+
BigSize(field_len as u64).write(&mut len)?;
60+
len.0 += field_len;
6661
)*
6762
$(
6863
if let Some(ref field) = $optional_field {
6964
BigSize($optional_type).write(&mut len)?;
70-
let mut field_len = LengthCalculatingWriter(0);
71-
field.write(&mut field_len)?;
72-
BigSize(field_len.0 as u64).write(&mut len)?;
73-
len.0 += field_len.0;
65+
let field_len = field.serialized_length();
66+
BigSize(field_len as u64).write(&mut len)?;
67+
len.0 += field_len;
7468
}
7569
)*
7670
}

0 commit comments

Comments
 (0)