Skip to content

Commit 399017b

Browse files
committed
perf(dash-wasm): batch element attributes
1 parent bc2ab3d commit 399017b

4 files changed

Lines changed: 94 additions & 23 deletions

File tree

‎src/parsers/manifest/dash/wasm-parser/rs/lib.rs‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ extern "C" {
4949
/// * `len` - Length of the data - starting at `ptr` - in bytes.
5050
fn onAttribute(attr_name: AttributeName, ptr: *const u8, len: usize);
5151

52+
/// JS callback called once with all attributes parsed for one XML element.
53+
fn onAttributeBatch(ptr: *const u8, len: usize);
54+
5255
/// JS callback for other specific operations, for example logging and warnings.
5356
///
5457
/// # Arguments

‎src/parsers/manifest/dash/wasm-parser/rs/processor/attributes.rs‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
use crate::errors::ParsingError;
22
use crate::events::AttributeName::*;
3+
use crate::reportable::AttributeBatchGuard;
34

45
pub fn report_mpd_attrs(e: &quick_xml::events::BytesStart) {
6+
let _batch = AttributeBatchGuard::new();
57
for res_attr in e.attributes().with_checks(false) {
68
match res_attr {
79
Ok(attr) => match attr.key.as_ref() {
@@ -40,6 +42,7 @@ pub fn report_mpd_attrs(e: &quick_xml::events::BytesStart) {
4042
}
4143

4244
pub fn report_period_attrs(tag_bs: &quick_xml::events::BytesStart) {
45+
let _batch = AttributeBatchGuard::new();
4346
for res_attr in tag_bs.attributes().with_checks(false) {
4447
match res_attr {
4548
Ok(attr) => match attr.key.as_ref() {
@@ -65,6 +68,7 @@ pub fn report_period_attrs(tag_bs: &quick_xml::events::BytesStart) {
6568
}
6669

6770
pub fn report_adaptation_set_attrs(e: &quick_xml::events::BytesStart) {
71+
let _batch = AttributeBatchGuard::new();
6872
for res_attr in e.attributes().with_checks(false) {
6973
match res_attr {
7074
Ok(attr) => match attr.key.as_ref() {
@@ -110,6 +114,7 @@ pub fn report_adaptation_set_attrs(e: &quick_xml::events::BytesStart) {
110114
}
111115

112116
pub fn report_representation_attrs(tag_bs: &quick_xml::events::BytesStart) {
117+
let _batch = AttributeBatchGuard::new();
113118
for res_attr in tag_bs.attributes().with_checks(false) {
114119
match res_attr {
115120
Ok(attr) => match attr.key.as_ref() {
@@ -141,6 +146,7 @@ pub fn report_representation_attrs(tag_bs: &quick_xml::events::BytesStart) {
141146
}
142147

143148
pub fn report_base_url_attrs(tag_bs: &quick_xml::events::BytesStart) {
149+
let _batch = AttributeBatchGuard::new();
144150
for res_attr in tag_bs.attributes().with_checks(false) {
145151
match res_attr {
146152
Ok(attr) => {
@@ -154,6 +160,7 @@ pub fn report_base_url_attrs(tag_bs: &quick_xml::events::BytesStart) {
154160
}
155161

156162
pub fn report_segment_template_attrs(tag_bs: &quick_xml::events::BytesStart) {
163+
let _batch = AttributeBatchGuard::new();
157164
for res_attr in tag_bs.attributes().with_checks(false) {
158165
match res_attr {
159166
Ok(attr) => match attr.key.as_ref() {
@@ -181,6 +188,7 @@ pub fn report_segment_template_attrs(tag_bs: &quick_xml::events::BytesStart) {
181188
}
182189

183190
pub fn report_segment_base_attrs(tag_bs: &quick_xml::events::BytesStart) {
191+
let _batch = AttributeBatchGuard::new();
184192
for res_attr in tag_bs.attributes().with_checks(false) {
185193
match res_attr {
186194
Ok(attr) => match attr.key.as_ref() {
@@ -204,6 +212,7 @@ pub fn report_segment_base_attrs(tag_bs: &quick_xml::events::BytesStart) {
204212
}
205213

206214
pub fn report_content_component_attrs(tag_bs: &quick_xml::events::BytesStart) {
215+
let _batch = AttributeBatchGuard::new();
207216
for res_attr in tag_bs.attributes().with_checks(false) {
208217
match res_attr {
209218
Ok(attr) => match attr.key.as_ref() {
@@ -219,6 +228,7 @@ pub fn report_content_component_attrs(tag_bs: &quick_xml::events::BytesStart) {
219228
}
220229

221230
pub fn report_content_protection_attrs(tag_bs: &quick_xml::events::BytesStart) {
231+
let _batch = AttributeBatchGuard::new();
222232
for res_attr in tag_bs.attributes().with_checks(false) {
223233
match res_attr {
224234
Ok(attr) => match attr.key.as_ref() {
@@ -238,6 +248,7 @@ pub fn report_content_protection_attrs(tag_bs: &quick_xml::events::BytesStart) {
238248

239249
/// Report attributes encountered in an `<Initialization>` element.
240250
pub fn report_initialization_attrs(tag_bs: &quick_xml::events::BytesStart) {
251+
let _batch = AttributeBatchGuard::new();
241252
for res_attr in tag_bs.attributes().with_checks(false) {
242253
match res_attr {
243254
Ok(attr) => match attr.key.as_ref() {
@@ -257,6 +268,7 @@ pub fn report_initialization_attrs(tag_bs: &quick_xml::events::BytesStart) {
257268
/// - "schemeIdUri"
258269
/// - "value"
259270
pub fn report_scheme_attrs(tag_bs: &quick_xml::events::BytesStart) {
271+
let _batch = AttributeBatchGuard::new();
260272
for res_attr in tag_bs.attributes().with_checks(false) {
261273
match res_attr {
262274
Ok(attr) => match attr.key.as_ref() {
@@ -270,6 +282,7 @@ pub fn report_scheme_attrs(tag_bs: &quick_xml::events::BytesStart) {
270282
}
271283

272284
pub fn report_segment_url_attrs(tag_bs: &quick_xml::events::BytesStart) {
285+
let _batch = AttributeBatchGuard::new();
273286
for res_attr in tag_bs.attributes().with_checks(false) {
274287
match res_attr {
275288
Ok(attr) => match attr.key.as_ref() {
@@ -285,6 +298,7 @@ pub fn report_segment_url_attrs(tag_bs: &quick_xml::events::BytesStart) {
285298
}
286299

287300
pub fn report_event_stream_attrs(tag_bs: &quick_xml::events::BytesStart) {
301+
let _batch = AttributeBatchGuard::new();
288302
for res_attr in tag_bs.attributes().with_checks(false) {
289303
match res_attr {
290304
Ok(attr) => match attr.key.as_ref() {
@@ -303,6 +317,7 @@ pub fn report_event_stream_attrs(tag_bs: &quick_xml::events::BytesStart) {
303317
}
304318

305319
pub fn report_event_stream_event_attrs(tag_bs: &quick_xml::events::BytesStart) {
320+
let _batch = AttributeBatchGuard::new();
306321
for res_attr in tag_bs.attributes().with_checks(false) {
307322
match res_attr {
308323
Ok(attr) => match attr.key.as_ref() {

‎src/parsers/manifest/dash/wasm-parser/rs/reportable.rs‎

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,55 @@
11
use crate::events::AttributeName;
2-
use crate::onAttribute;
32
use crate::processor::SegmentObject;
3+
use crate::{onAttribute, onAttributeBatch};
44
use core::mem;
55
use std::borrow::Cow;
6+
use std::cell::{Cell, RefCell};
7+
8+
thread_local! {
9+
static ATTRIBUTE_BATCH: RefCell<Vec<u8>> = const { RefCell::new(Vec::new()) };
10+
static ATTRIBUTE_BATCH_ACTIVE: Cell<bool> = const { Cell::new(false) };
11+
}
12+
13+
/// Keeps a single temporary allocation whose size is bounded by the largest
14+
/// attribute list encountered on one XML element.
15+
pub struct AttributeBatchGuard;
16+
17+
impl AttributeBatchGuard {
18+
pub fn new() -> Self {
19+
ATTRIBUTE_BATCH.with(|batch| {
20+
batch.borrow_mut().clear();
21+
});
22+
ATTRIBUTE_BATCH_ACTIVE.with(|active| active.set(true));
23+
Self
24+
}
25+
}
26+
27+
impl Drop for AttributeBatchGuard {
28+
fn drop(&mut self) {
29+
ATTRIBUTE_BATCH_ACTIVE.with(|active| active.set(false));
30+
ATTRIBUTE_BATCH.with(|batch| {
31+
let buffer = batch.borrow();
32+
if !buffer.is_empty() {
33+
unsafe { onAttributeBatch(buffer.as_ptr(), buffer.len()) };
34+
}
35+
});
36+
}
37+
}
38+
39+
#[inline(always)]
40+
fn report_bytes(attr_name: AttributeName, bytes: &[u8]) {
41+
let was_batched = ATTRIBUTE_BATCH_ACTIVE.with(|active| active.get());
42+
if was_batched {
43+
ATTRIBUTE_BATCH.with(|batch| {
44+
let mut batch = batch.borrow_mut();
45+
batch.push(attr_name as u8);
46+
batch.extend((bytes.len() as u32).to_le_bytes());
47+
batch.extend(bytes);
48+
});
49+
} else {
50+
unsafe { onAttribute(attr_name, bytes.as_ptr(), bytes.len()) };
51+
}
52+
}
653

754
/// Trait implemented for values that can be "reported" as an attribute to the
855
/// JS-side.
@@ -40,9 +87,7 @@ impl ReportableAttribute for bool {
4087
let val: u8 = if *self { 1 } else { 0 };
4188
// UNSAFE: We're using FFI, so we don't know how the pointer is used.
4289
// Hopefully, the JavaScript-side should clone that value synchronously.
43-
unsafe {
44-
onAttribute(attr_name, &val, 1);
45-
};
90+
report_bytes(attr_name, std::slice::from_ref(&val));
4691
}
4792
}
4893

@@ -59,9 +104,7 @@ impl ReportableAttribute for f64 {
59104
// callback expects.
60105
// This should not matter: Rust types are not communicated to
61106
// JavaScript anyway.
62-
unsafe {
63-
onAttribute(attr_name, self as *const f64 as *const u8, 8);
64-
};
107+
report_bytes(attr_name, &self.to_le_bytes());
65108
}
66109
}
67110

@@ -78,9 +121,10 @@ impl ReportableAttribute for (f64, f64) {
78121
// callback expects.
79122
// This should not matter: Rust types are not communicated to
80123
// JavaScript anyway.
81-
unsafe {
82-
onAttribute(attr_name, self as *const (f64, f64) as *const u8, 16);
83-
};
124+
let mut bytes = [0; 16];
125+
bytes[..8].copy_from_slice(&self.0.to_le_bytes());
126+
bytes[8..].copy_from_slice(&self.1.to_le_bytes());
127+
report_bytes(attr_name, &bytes);
84128
}
85129
}
86130

@@ -91,10 +135,9 @@ impl ReportableAttribute for &[SegmentObject] {
91135

92136
// UNSAFE: We're using FFI, so we don't know how the pointer is used.
93137
// Hopefully, the JavaScript-side should clone that value synchronously.
94-
unsafe {
95-
let len = self.len() * mem::size_of::<SegmentObject>();
96-
onAttribute(attr_name, self.as_ptr() as *const u8, len);
97-
}
138+
let len = self.len() * mem::size_of::<SegmentObject>();
139+
let bytes = unsafe { std::slice::from_raw_parts(self.as_ptr() as *const u8, len) };
140+
report_bytes(attr_name, bytes);
98141
}
99142
}
100143

@@ -114,9 +157,7 @@ impl<'a> ReportableAttribute for (&'a [u8], Cow<'a, str>) {
114157

115158
// UNSAFE: We're using FFI, so we don't know how the pointer is used.
116159
// Hopefully, the JavaScript-side should clone that value synchronously.
117-
unsafe {
118-
onAttribute(attr_name, msg.as_ptr(), msg.len());
119-
};
160+
report_bytes(attr_name, &msg);
120161
}
121162
}
122163

@@ -127,9 +168,7 @@ impl<'a> ReportableAttribute for Cow<'a, [u8]> {
127168

128169
// UNSAFE: We're using FFI, so we don't know how the pointer is used.
129170
// Hopefully, the JavaScript-side should clone that value synchronously.
130-
unsafe {
131-
onAttribute(attr_name, self.as_ptr(), self.len());
132-
};
171+
report_bytes(attr_name, self);
133172
}
134173
}
135174

@@ -140,8 +179,6 @@ impl<'a> ReportableAttribute for Cow<'a, str> {
140179

141180
// UNSAFE: We're using FFI, so we don't know how the pointer is used.
142181
// Hopefully, the JavaScript-side should clone that value synchronously.
143-
unsafe {
144-
onAttribute(attr_name, self.as_ptr(), self.len());
145-
};
182+
report_bytes(attr_name, self.as_bytes());
146183
}
147184
}

‎src/parsers/manifest/dash/wasm-parser/ts/dash-wasm-parser.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ export default class DashWasmParser {
154154
onTagOpen,
155155
onCustomEvent,
156156
onAttribute,
157+
onAttributeBatch,
157158
readNext,
158159
onTagClose,
159160
},
@@ -251,6 +252,21 @@ export default class DashWasmParser {
251252
return parsersStack.attributeParser(attr, ptr, len);
252253
}
253254

255+
/** Dispatch all attributes from one XML element in a single WASM callback. */
256+
function onAttributeBatch(ptr: number, len: number): void {
257+
const linearMemory = self._linearMemory as WebAssembly.Memory;
258+
const dataView = new DataView(linearMemory.buffer);
259+
const end = ptr + len;
260+
let offset = ptr;
261+
while (offset < end) {
262+
const attr = dataView.getUint8(offset);
263+
const valueLen = dataView.getUint32(offset + 1, true);
264+
offset += 5;
265+
parsersStack.attributeParser(attr, offset, valueLen);
266+
offset += valueLen;
267+
}
268+
}
269+
254270
/**
255271
* Callback called on the various "custom events" triggered by the WASM.
256272
*

0 commit comments

Comments
 (0)