Skip to content

Commit 62f2550

Browse files
authored
Reduce memory allocations for SDP marshal (#596)
* Performance: reduce string allocations * Performance: cosmetic improvement
1 parent c32345d commit 62f2550

File tree

6 files changed

+141
-91
lines changed

6 files changed

+141
-91
lines changed

sdp/src/description/common.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,14 @@ pub struct Address {
3333

3434
impl fmt::Display for Address {
3535
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36-
let mut parts = vec![self.address.to_owned()];
36+
write!(f, "{}", self.address)?;
3737
if let Some(t) = &self.ttl {
38-
parts.push(t.to_string());
38+
write!(f, "/{}", t)?;
3939
}
4040
if let Some(r) = &self.range {
41-
parts.push(r.to_string());
41+
write!(f, "/{}", r)?;
4242
}
43-
write!(f, "{}", parts.join("/"))
43+
Ok(())
4444
}
4545
}
4646

sdp/src/description/media.rs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,11 @@ impl MediaDescription {
142142
fmtp: String,
143143
) -> Self {
144144
self.media_name.formats.push(payload_type.to_string());
145-
let mut rtpmap = format!("{payload_type} {name}/{clockrate}");
146-
if channels > 0 {
147-
rtpmap += format!("/{channels}").as_str();
148-
}
145+
let rtpmap = if channels > 0 {
146+
format!("{payload_type} {name}/{clockrate}/{channels}")
147+
} else {
148+
format!("{payload_type} {name}/{clockrate}")
149+
};
149150

150151
if !fmtp.is_empty() {
151152
self.with_value_attribute("rtpmap".to_string(), rtpmap)
@@ -236,13 +237,23 @@ pub struct MediaName {
236237

237238
impl fmt::Display for MediaName {
238239
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239-
let s = [
240-
self.media.clone(),
241-
self.port.to_string(),
242-
self.protos.join("/"),
243-
self.formats.join(" "),
244-
];
245-
write!(f, "{}", s.join(" "))
240+
write!(f, "{} {}", self.media, self.port)?;
241+
242+
let mut first = true;
243+
for part in &self.protos {
244+
if first {
245+
first = false;
246+
write!(f, " {}", part)?;
247+
} else {
248+
write!(f, "/{}", part)?;
249+
}
250+
}
251+
252+
for part in &self.formats {
253+
write!(f, " {}", part)?;
254+
}
255+
256+
Ok(())
246257
}
247258
}
248259

sdp/src/description/session.rs

Lines changed: 58 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,12 @@ pub struct RepeatTime {
150150

151151
impl fmt::Display for RepeatTime {
152152
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153-
let mut fields = vec![format!("{}", self.interval), format!("{}", self.duration)];
153+
write!(f, "{} {}", self.interval, self.duration)?;
154+
154155
for value in &self.offsets {
155-
fields.push(format!("{value}"));
156+
write!(f, " {value}")?;
156157
}
157-
write!(f, "{}", fields.join(" "))
158+
Ok(())
158159
}
159160
}
160161

@@ -234,6 +235,59 @@ pub struct SessionDescription {
234235
pub media_descriptions: Vec<MediaDescription>,
235236
}
236237

238+
impl fmt::Display for SessionDescription {
239+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240+
write_key_value(f, "v=", Some(&self.version))?;
241+
write_key_value(f, "o=", Some(&self.origin))?;
242+
write_key_value(f, "s=", Some(&self.session_name))?;
243+
244+
write_key_value(f, "i=", self.session_information.as_ref())?;
245+
246+
if let Some(uri) = &self.uri {
247+
write_key_value(f, "u=", Some(uri))?;
248+
}
249+
write_key_value(f, "e=", self.email_address.as_ref())?;
250+
write_key_value(f, "p=", self.phone_number.as_ref())?;
251+
if let Some(connection_information) = &self.connection_information {
252+
write_key_value(f, "c=", Some(&connection_information))?;
253+
}
254+
255+
for bandwidth in &self.bandwidth {
256+
write_key_value(f, "b=", Some(&bandwidth))?;
257+
}
258+
for time_description in &self.time_descriptions {
259+
write_key_value(f, "t=", Some(&time_description.timing))?;
260+
for repeat_time in &time_description.repeat_times {
261+
write_key_value(f, "r=", Some(&repeat_time))?;
262+
}
263+
}
264+
265+
write_key_slice_of_values(f, "z=", &self.time_zones)?;
266+
267+
write_key_value(f, "k=", self.encryption_key.as_ref())?;
268+
for attribute in &self.attributes {
269+
write_key_value(f, "a=", Some(&attribute))?;
270+
}
271+
272+
for media_description in &self.media_descriptions {
273+
write_key_value(f, "m=", Some(&media_description.media_name))?;
274+
write_key_value(f, "i=", media_description.media_title.as_ref())?;
275+
if let Some(connection_information) = &media_description.connection_information {
276+
write_key_value(f, "c=", Some(&connection_information))?;
277+
}
278+
for bandwidth in &media_description.bandwidth {
279+
write_key_value(f, "b=", Some(&bandwidth))?;
280+
}
281+
write_key_value(f, "k=", media_description.encryption_key.as_ref())?;
282+
for attribute in &media_description.attributes {
283+
write_key_value(f, "a=", Some(&attribute))?;
284+
}
285+
}
286+
287+
Ok(())
288+
}
289+
}
290+
237291
/// Reset cleans the SessionDescription, and sets all fields back to their default values
238292
impl SessionDescription {
239293
/// API to match draft-ietf-rtcweb-jsep
@@ -399,61 +453,7 @@ impl SessionDescription {
399453
/// k=* (encryption key)
400454
/// a=* (zero or more media attribute lines)
401455
pub fn marshal(&self) -> String {
402-
let mut result = String::new();
403-
404-
result += key_value_build("v=", Some(&self.version.to_string())).as_str();
405-
result += key_value_build("o=", Some(&self.origin.to_string())).as_str();
406-
result += key_value_build("s=", Some(&self.session_name)).as_str();
407-
408-
result += key_value_build("i=", self.session_information.as_ref()).as_str();
409-
410-
if let Some(uri) = &self.uri {
411-
result += key_value_build("u=", Some(&format!("{uri}"))).as_str();
412-
}
413-
result += key_value_build("e=", self.email_address.as_ref()).as_str();
414-
result += key_value_build("p=", self.phone_number.as_ref()).as_str();
415-
if let Some(connection_information) = &self.connection_information {
416-
result += key_value_build("c=", Some(&connection_information.to_string())).as_str();
417-
}
418-
419-
for bandwidth in &self.bandwidth {
420-
result += key_value_build("b=", Some(&bandwidth.to_string())).as_str();
421-
}
422-
for time_description in &self.time_descriptions {
423-
result += key_value_build("t=", Some(&time_description.timing.to_string())).as_str();
424-
for repeat_time in &time_description.repeat_times {
425-
result += key_value_build("r=", Some(&repeat_time.to_string())).as_str();
426-
}
427-
}
428-
if !self.time_zones.is_empty() {
429-
let mut time_zones = vec![];
430-
for time_zone in &self.time_zones {
431-
time_zones.push(time_zone.to_string());
432-
}
433-
result += key_value_build("z=", Some(&time_zones.join(" "))).as_str();
434-
}
435-
result += key_value_build("k=", self.encryption_key.as_ref()).as_str();
436-
for attribute in &self.attributes {
437-
result += key_value_build("a=", Some(&attribute.to_string())).as_str();
438-
}
439-
440-
for media_description in &self.media_descriptions {
441-
result +=
442-
key_value_build("m=", Some(&media_description.media_name.to_string())).as_str();
443-
result += key_value_build("i=", media_description.media_title.as_ref()).as_str();
444-
if let Some(connection_information) = &media_description.connection_information {
445-
result += key_value_build("c=", Some(&connection_information.to_string())).as_str();
446-
}
447-
for bandwidth in &media_description.bandwidth {
448-
result += key_value_build("b=", Some(&bandwidth.to_string())).as_str();
449-
}
450-
result += key_value_build("k=", media_description.encryption_key.as_ref()).as_str();
451-
for attribute in &media_description.attributes {
452-
result += key_value_build("a=", Some(&attribute.to_string())).as_str();
453-
}
454-
}
455-
456-
result
456+
self.to_string()
457457
}
458458

459459
/// Unmarshal is the primary function that deserializes the session description

sdp/src/extmap/mod.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,21 @@ pub struct ExtMap {
3737

3838
impl fmt::Display for ExtMap {
3939
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40-
let mut output = format!("{}", self.value);
40+
write!(f, "{}", self.value)?;
41+
4142
if self.direction != Direction::Unspecified {
42-
output += format!("/{}", self.direction).as_str();
43+
write!(f, "/{}", self.direction)?;
4344
}
4445

4546
if let Some(uri) = &self.uri {
46-
output += format!(" {uri}").as_str();
47+
write!(f, " {uri}")?;
4748
}
4849

4950
if let Some(ext_attr) = &self.ext_attr {
50-
output += format!(" {ext_attr}").as_str();
51+
write!(f, " {ext_attr}")?;
5152
}
5253

53-
write!(f, "{output}")
54+
Ok(())
5455
}
5556
}
5657

sdp/src/lexer/mod.rs

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use core::fmt;
2+
use std::fmt::Display;
13
use std::io;
24
use std::io::SeekFrom;
35

@@ -57,10 +59,39 @@ pub fn index_of(element: &str, data: &[&str]) -> i32 {
5759
-1
5860
}
5961

60-
pub fn key_value_build(key: &str, value: Option<&String>) -> String {
61-
if let Some(val) = value {
62-
format!("{key}{val}{END_LINE}")
63-
} else {
64-
"".to_string()
62+
pub fn write_key_value<W: fmt::Write, V: Display>(
63+
writer: &mut W,
64+
key: &str,
65+
value: Option<V>,
66+
) -> fmt::Result {
67+
let Some(value) = value else {
68+
return Ok(());
69+
};
70+
71+
write!(writer, "{key}{value}{END_LINE}")
72+
}
73+
74+
pub fn write_key_slice_of_values<W: fmt::Write, V: Display>(
75+
writer: &mut W,
76+
key: &str,
77+
value: &[V],
78+
) -> fmt::Result {
79+
if value.is_empty() {
80+
return Ok(());
81+
}
82+
83+
let mut first = true;
84+
85+
write!(writer, "{key}")?;
86+
for val in value {
87+
if first {
88+
first = false;
89+
write!(writer, "{val}")?;
90+
} else {
91+
write!(writer, " {val}")?;
92+
}
6593
}
94+
write!(writer, "{END_LINE}")?;
95+
96+
Ok(())
6697
}

sdp/src/util/mod.rs

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,14 +93,21 @@ impl fmt::Display for Codec {
9393
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
9494
write!(
9595
f,
96-
"{} {}/{}/{} ({}) [{}]",
97-
self.payload_type,
98-
self.name,
99-
self.clock_rate,
100-
self.encoding_parameters,
101-
self.fmtp,
102-
self.rtcp_feedback.join(", "),
103-
)
96+
"{} {}/{}/{} ({}) [",
97+
self.payload_type, self.name, self.clock_rate, self.encoding_parameters, self.fmtp,
98+
)?;
99+
100+
let mut first = true;
101+
for part in &self.rtcp_feedback {
102+
if first {
103+
first = false;
104+
write!(f, "{part}")?;
105+
} else {
106+
write!(f, ", {part}")?;
107+
}
108+
}
109+
110+
write!(f, "]")
104111
}
105112
}
106113

0 commit comments

Comments
 (0)