Skip to content

Commit 7a70605

Browse files
committed
perf: FullMappingsEncoder
1 parent 4eae42c commit 7a70605

2 files changed

Lines changed: 58 additions & 25 deletions

File tree

src/encoder.rs

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,75 @@ use crate::Mapping;
33
const B64_CHARS: &[u8] =
44
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
55

6-
pub fn encode_vlq(out: &mut Vec<u8>, a: u32, b: u32) {
6+
/// Encode a VLQ diff (a relative to b) into `out`.
7+
///
8+
/// Uses a stack buffer to batch the write, avoiding per-byte `Vec::push`
9+
/// bounds checks.
10+
#[inline(always)]
11+
fn encode_vlq(out: &mut Vec<u8>, a: u32, b: u32) {
712
let mut num = if a >= b {
813
(a - b) << 1
914
} else {
1015
((b - a) << 1) + 1
1116
};
1217

18+
// A VLQ for u32 needs at most ceil(32/5)+1 = 7 base64 characters.
19+
let mut buf = [0u8; 7];
20+
let mut len = 0;
21+
1322
loop {
1423
let mut digit = num & 0b11111;
1524
num >>= 5;
1625
if num > 0 {
1726
digit |= 1 << 5;
1827
}
19-
out.push(B64_CHARS[digit as usize]);
28+
buf[len] = B64_CHARS[digit as usize];
29+
len += 1;
2030
if num == 0 {
2131
break;
2232
}
2333
}
34+
35+
out.extend_from_slice(&buf[..len]);
2436
}
2537

26-
pub(crate) trait MappingsEncoder {
27-
fn encode(&mut self, mapping: &Mapping);
28-
fn drain(&mut self) -> String;
38+
/// A source map mappings encoder that eliminates dynamic dispatch by using
39+
/// an enum instead of `Box<dyn Trait>`.
40+
pub(crate) enum MappingsEncoder {
41+
Full(FullMappingsEncoder),
42+
LinesOnly(LinesOnlyMappingsEncoder),
2943
}
3044

31-
pub fn create_encoder(columns: bool) -> Box<dyn MappingsEncoder> {
32-
if columns {
33-
Box::new(FullMappingsEncoder::new())
34-
} else {
35-
Box::new(LinesOnlyMappingsEncoder::new())
45+
impl MappingsEncoder {
46+
/// Create a new encoder. `columns: true` produces full mappings,
47+
/// `columns: false` produces lines-only mappings.
48+
#[inline]
49+
pub fn new(columns: bool) -> Self {
50+
if columns {
51+
Self::Full(FullMappingsEncoder::new())
52+
} else {
53+
Self::LinesOnly(LinesOnlyMappingsEncoder::new())
54+
}
55+
}
56+
57+
#[inline]
58+
pub fn encode(&mut self, mapping: &Mapping) {
59+
match self {
60+
Self::Full(enc) => enc.encode(mapping),
61+
Self::LinesOnly(enc) => enc.encode(mapping),
62+
}
63+
}
64+
65+
#[inline]
66+
pub fn drain(&mut self) -> String {
67+
match self {
68+
Self::Full(enc) => enc.drain(),
69+
Self::LinesOnly(enc) => enc.drain(),
70+
}
3671
}
3772
}
3873

39-
struct FullMappingsEncoder {
74+
pub(crate) struct FullMappingsEncoder {
4075
current_line: u32,
4176
current_column: u32,
4277
current_original_line: u32,
@@ -50,7 +85,7 @@ struct FullMappingsEncoder {
5085
}
5186

5287
impl FullMappingsEncoder {
53-
pub fn new() -> Self {
88+
fn new() -> Self {
5489
Self {
5590
current_line: 1,
5691
current_column: 0,
@@ -64,9 +99,7 @@ impl FullMappingsEncoder {
6499
mappings: Default::default(),
65100
}
66101
}
67-
}
68102

69-
impl MappingsEncoder for FullMappingsEncoder {
70103
fn encode(&mut self, mapping: &Mapping) {
71104
if self.active_mapping && self.current_line == mapping.generated_line {
72105
// A mapping is still active
@@ -89,8 +122,10 @@ impl MappingsEncoder for FullMappingsEncoder {
89122
}
90123

91124
if self.current_line < mapping.generated_line {
92-
(0..mapping.generated_line - self.current_line)
93-
.for_each(|_| self.mappings.push(b';'));
125+
let delta = (mapping.generated_line - self.current_line) as usize;
126+
self
127+
.mappings
128+
.extend(std::iter::repeat_n(b';', delta));
94129
self.current_line = mapping.generated_line;
95130
self.current_column = 0;
96131
self.initial = false;
@@ -173,9 +208,7 @@ impl LinesOnlyMappingsEncoder {
173208
mappings: Default::default(),
174209
}
175210
}
176-
}
177211

178-
impl MappingsEncoder for LinesOnlyMappingsEncoder {
179212
fn encode(&mut self, mapping: &Mapping) {
180213
if let Some(original) = &mapping.original {
181214
if self.last_written_line == mapping.generated_line {
@@ -196,9 +229,9 @@ impl MappingsEncoder for LinesOnlyMappingsEncoder {
196229
if original.source_index == self.current_source_index {
197230
if original.original_line == self.current_original_line + 1 {
198231
self.current_original_line = original.original_line;
199-
self.mappings.extend(b"AACA");
232+
self.mappings.extend_from_slice(b"AACA");
200233
} else {
201-
self.mappings.extend(b"AA");
234+
self.mappings.extend_from_slice(b"AA");
202235
encode_vlq(
203236
&mut self.mappings,
204237
original.original_line,
@@ -208,7 +241,7 @@ impl MappingsEncoder for LinesOnlyMappingsEncoder {
208241
self.mappings.push(b'A');
209242
}
210243
} else {
211-
self.mappings.extend(b"A");
244+
self.mappings.push(b'A');
212245
encode_vlq(
213246
&mut self.mappings,
214247
original.source_index,

src/helpers.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use rustc_hash::FxHashMap as HashMap;
99

1010
use crate::{
1111
decoder::MappingsDecoder,
12-
encoder::create_encoder,
12+
encoder::MappingsEncoder,
1313
linear_map::LinearMap,
1414
object_pool::ObjectPool,
1515
source::{Mapping, OriginalLocation},
@@ -23,7 +23,7 @@ pub fn get_map<'a>(
2323
stream: &'a dyn Stream,
2424
options: &MapOptions,
2525
) -> (GeneratedInfo, Option<SourceMap>) {
26-
let mut mappings_encoder = create_encoder(options.columns);
26+
let mut mappings_encoder = MappingsEncoder::new(options.columns);
2727
let mut sources: Vec<String> = Vec::new();
2828
let mut sources_content: Vec<Arc<str>> = Vec::new();
2929
let mut names: Vec<String> = Vec::new();
@@ -168,7 +168,7 @@ pub fn decode_mappings(
168168

169169
/// Encodes the given iterator of `Mapping` items into a `String`.
170170
pub fn encode_mappings(mappings: impl Iterator<Item = Mapping>) -> String {
171-
let mut encoder = create_encoder(true);
171+
let mut encoder = MappingsEncoder::new(true);
172172
mappings.for_each(|mapping| encoder.encode(&mapping));
173173
encoder.drain()
174174
}
@@ -1257,7 +1257,7 @@ pub fn stream_and_get_source_and_map<'a>(
12571257
on_source: OnSource<'_, 'a>,
12581258
on_name: OnName<'_, 'a>,
12591259
) -> (GeneratedInfo, Option<SourceMap>) {
1260-
let mut mappings_encoder = create_encoder(options.columns);
1260+
let mut mappings_encoder = MappingsEncoder::new(options.columns);
12611261
let mut sources: Vec<String> = Vec::new();
12621262
let mut sources_content: Vec<Arc<str>> = Vec::new();
12631263
let mut names: Vec<String> = Vec::new();

0 commit comments

Comments
 (0)