Skip to content

Commit e59dab5

Browse files
committed
Auto merge of #65198 - nnethercote:fix-65080, r=Mark-Simulacrum
Speed up `TokenStream` concatenation This PR fixes the quadratic behaviour identified in #65080. r? @Mark-Simulacrum
2 parents 275cf4b + 75e0078 commit e59dab5

File tree

1 file changed

+80
-61
lines changed

1 file changed

+80
-61
lines changed

src/libsyntax/tokenstream.rs

+80-61
Original file line numberDiff line numberDiff line change
@@ -249,20 +249,47 @@ impl TokenStream {
249249
0 => TokenStream::empty(),
250250
1 => streams.pop().unwrap(),
251251
_ => {
252-
// rust-lang/rust#57735: pre-allocate vector to avoid
253-
// quadratic blow-up due to on-the-fly reallocations.
254-
let tree_count = streams.iter()
255-
.map(|ts| match &ts.0 { None => 0, Some(s) => s.len() })
252+
// We are going to extend the first stream in `streams` with
253+
// the elements from the subsequent streams. This requires
254+
// using `make_mut()` on the first stream, and in practice this
255+
// doesn't cause cloning 99.9% of the time.
256+
//
257+
// One very common use case is when `streams` has two elements,
258+
// where the first stream has any number of elements within
259+
// (often 1, but sometimes many more) and the second stream has
260+
// a single element within.
261+
262+
// Determine how much the first stream will be extended.
263+
// Needed to avoid quadratic blow up from on-the-fly
264+
// reallocations (#57735).
265+
let num_appends = streams.iter()
266+
.skip(1)
267+
.map(|ts| ts.len())
256268
.sum();
257-
let mut vec = Vec::with_capacity(tree_count);
258269

259-
for stream in streams {
260-
match stream.0 {
261-
None => {},
262-
Some(stream2) => vec.extend(stream2.iter().cloned()),
270+
// Get the first stream. If it's `None`, create an empty
271+
// stream.
272+
let mut iter = streams.drain();
273+
let mut first_stream_lrc = match iter.next().unwrap().0 {
274+
Some(first_stream_lrc) => first_stream_lrc,
275+
None => Lrc::new(vec![]),
276+
};
277+
278+
// Append the elements to the first stream, after reserving
279+
// space for them.
280+
let first_vec_mut = Lrc::make_mut(&mut first_stream_lrc);
281+
first_vec_mut.reserve(num_appends);
282+
for stream in iter {
283+
if let Some(stream) = stream.0 {
284+
first_vec_mut.extend(stream.iter().cloned());
263285
}
264286
}
265-
TokenStream::new(vec)
287+
288+
// Create the final `TokenStream`.
289+
match first_vec_mut.len() {
290+
0 => TokenStream(None),
291+
_ => TokenStream(Some(first_stream_lrc)),
292+
}
266293
}
267294
}
268295
}
@@ -363,25 +390,6 @@ impl TokenStream {
363390
.collect())
364391
}))
365392
}
366-
367-
fn first_tree_and_joint(&self) -> Option<TreeAndJoint> {
368-
self.0.as_ref().map(|stream| {
369-
stream.first().unwrap().clone()
370-
})
371-
}
372-
373-
fn last_tree_if_joint(&self) -> Option<TokenTree> {
374-
match self.0 {
375-
None => None,
376-
Some(ref stream) => {
377-
if let (tree, Joint) = stream.last().unwrap() {
378-
Some(tree.clone())
379-
} else {
380-
None
381-
}
382-
}
383-
}
384-
}
385393
}
386394

387395
// 99.5%+ of the time we have 1 or 2 elements in this vector.
@@ -394,18 +402,49 @@ impl TokenStreamBuilder {
394402
}
395403

396404
pub fn push<T: Into<TokenStream>>(&mut self, stream: T) {
397-
let stream = stream.into();
398-
let last_tree_if_joint = self.0.last().and_then(TokenStream::last_tree_if_joint);
399-
if let Some(TokenTree::Token(last_token)) = last_tree_if_joint {
400-
if let Some((TokenTree::Token(token), is_joint)) = stream.first_tree_and_joint() {
401-
if let Some(glued_tok) = last_token.glue(&token) {
402-
let last_stream = self.0.pop().unwrap();
403-
self.push_all_but_last_tree(&last_stream);
404-
let glued_tt = TokenTree::Token(glued_tok);
405-
let glued_tokenstream = TokenStream::new(vec![(glued_tt, is_joint)]);
406-
self.0.push(glued_tokenstream);
407-
self.push_all_but_first_tree(&stream);
408-
return
405+
let mut stream = stream.into();
406+
407+
// If `self` is not empty and the last tree within the last stream is a
408+
// token tree marked with `Joint`...
409+
if let Some(TokenStream(Some(ref mut last_stream_lrc))) = self.0.last_mut() {
410+
if let Some((TokenTree::Token(last_token), Joint)) = last_stream_lrc.last() {
411+
412+
// ...and `stream` is not empty and the first tree within it is
413+
// a token tree...
414+
if let TokenStream(Some(ref mut stream_lrc)) = stream {
415+
if let Some((TokenTree::Token(token), is_joint)) = stream_lrc.first() {
416+
417+
// ...and the two tokens can be glued together...
418+
if let Some(glued_tok) = last_token.glue(&token) {
419+
420+
// ...then do so, by overwriting the last token
421+
// tree in `self` and removing the first token tree
422+
// from `stream`. This requires using `make_mut()`
423+
// on the last stream in `self` and on `stream`,
424+
// and in practice this doesn't cause cloning 99.9%
425+
// of the time.
426+
427+
// Overwrite the last token tree with the merged
428+
// token.
429+
let last_vec_mut = Lrc::make_mut(last_stream_lrc);
430+
*last_vec_mut.last_mut().unwrap() =
431+
(TokenTree::Token(glued_tok), *is_joint);
432+
433+
// Remove the first token tree from `stream`. (This
434+
// is almost always the only tree in `stream`.)
435+
let stream_vec_mut = Lrc::make_mut(stream_lrc);
436+
stream_vec_mut.remove(0);
437+
438+
// Don't push `stream` if it's empty -- that could
439+
// block subsequent token gluing, by getting
440+
// between two token trees that should be glued
441+
// together.
442+
if !stream.is_empty() {
443+
self.0.push(stream);
444+
}
445+
return;
446+
}
447+
}
409448
}
410449
}
411450
}
@@ -415,26 +454,6 @@ impl TokenStreamBuilder {
415454
pub fn build(self) -> TokenStream {
416455
TokenStream::from_streams(self.0)
417456
}
418-
419-
fn push_all_but_last_tree(&mut self, stream: &TokenStream) {
420-
if let Some(ref streams) = stream.0 {
421-
let len = streams.len();
422-
match len {
423-
1 => {}
424-
_ => self.0.push(TokenStream(Some(Lrc::new(streams[0 .. len - 1].to_vec())))),
425-
}
426-
}
427-
}
428-
429-
fn push_all_but_first_tree(&mut self, stream: &TokenStream) {
430-
if let Some(ref streams) = stream.0 {
431-
let len = streams.len();
432-
match len {
433-
1 => {}
434-
_ => self.0.push(TokenStream(Some(Lrc::new(streams[1 .. len].to_vec())))),
435-
}
436-
}
437-
}
438457
}
439458

440459
#[derive(Clone)]

0 commit comments

Comments
 (0)