I've implemented a Rust clone of bgpdump that displays MRT messages in the same format as bgpdump. To print the message, I implemented the Display trait and printed it to stdout. However, I'm concerned that the Display trait is enormously slow compared to Serde Serialize trait when printing the same message in JSON format.
For instance:
- Serde_json serialize take => 48s to dump 12G of data
- Display trait take => more than 5mins to dump 5G of data
My benchmark is quite strait forward I dump the same file in the two different format (txt vs json) and I time it as follows :
> time mrtdump --print filename > dump.txt # print use the display trait
> time mrtdump --json filename > dump.json # use the serde_json
I don't quite understand why there is such gap in performance between the two approaches.
Here is my implementation of the Display trait
impl Display for RibIpV4Unicast {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for entry in &self.rib_entries {
writeln!(f, "TIME: {}", self.time.format("%Y-%m-%d %H:%M:%S"))?;
writeln!(f, "TYPE: TABLE_DUMP_V2/IPV4_UNICAST")?;
writeln!(f, "PREFIX: {}/{}", self.prefix, self.prefix_len)?;
writeln!(f, "SEQUENCE: {}", self.sequence_number)?;
writeln!(f, "FROM: {} AS {}", entry.peer_ip, entry.peer_asn)?;
writeln!(
f,
"ORIGINATED: {}",
entry.originated_time.format("%Y-%m-%d %H:%M:%S")
)?;
if let Some(origin) = &entry.bgp_origin {
writeln!(f, "ORIGIN: {}", origin)?;
}
if let Some(as_path) = &entry.bgp_as_path {
writeln!(
f,
"ASPATH: {}",
as_path
.segments
.iter()
.map(|seg| seg.to_string())
.collect::<Vec<_>>()
.join(" ")
)?;
}
if let Some(next_hop) = &entry.bgp_next_hop {
writeln!(f, "NEXT_HOP: {}", next_hop.0)?;
}
if let Some(multi_exit_disc) = &entry.bgp_multi_exit_disc {
writeln!(f, "MULTI_EXIT_DISC: {}", multi_exit_disc.0)?;
}
if let Some(communities) = &entry.bgp_community {
writeln!(
f,
"COMMUNITIES: {}",
communities
.0
.iter()
.map(|(asn, local)| format!("{}:{}", asn, local))
.collect::<Vec<_>>()
.join(" ")
)?;
}
if let Some(communities) = &entry.bgp_large_community {
writeln!(
f,
"LARGE_COMMUNITY: {}",
communities
.0
.iter()
.map(|(asn, local, global)| format!("{}:{}:{}", asn, local, global))
.collect::<Vec<_>>()
.join(" ")
)?;
}
if let Some(aggregator) = &entry.bgp_aggregator {
writeln!(f, "AGGREGATOR: {} {}", aggregator.asn, aggregator.ip)?;
}
writeln!(f)?;
}
Ok(())
}
}
Which provide the following output
> mrtdump rib.20250701.0000
TIME: 2025-07-01 00:00:00
TYPE: TABLE_DUMP_V2/IPV4_UNICAST
PREFIX: 0.0.0.0/0
SEQUENCE: 0
FROM: 87.121.64.4 AS57463
ORIGINATED: 2025-06-26 21:10:33
ORIGIN: IGP
ASPATH: 57463 3356
NEXT_HOP: 87.121.64.4
COMMUNITIES: 1:1085 64700:3356 65400:1 65400:65500
LARGE_COMMUNITY: 57463:64700:3356
...
Displayimpl by letting it print to your terminal, your terminal can very easily be the bottleneck and slow things down. So please provide the benchmarking code.Displaywas not designed for speed, especially throughput, whileSerializewas very much designed for speed. So it's not a surprise.Displayis not designed for speed, what other serialization approach or trait shoud I use to provide the same result ?