1
1
mirror of https://github.com/wez/wezterm.git synced 2024-12-24 13:52:55 +03:00

term: restore line length pre-allocation

A while back I made the line lengths lazily grown; the reduction
in memory was nice, and it helped with render performance for
really wide screens.

Unfortunately, it puts a bunch of reallocation into the hot path
of the parser and updating the terminal model when people run
the inevitable `cat giant-file.txt` benchmark.

This commit reinstates pre-allocating lines to match the physical
terminal width, and tweaks the code a bit to take advantage of
const Cell allocation and to avoid some clones (a really micro
optimization).
This commit is contained in:
Wez Furlong 2021-08-11 21:43:14 -07:00
parent 9134da8c07
commit 6c2f9f2d18
6 changed files with 207 additions and 107 deletions

View File

@ -63,7 +63,7 @@ impl Screen {
let mut lines =
VecDeque::with_capacity(physical_rows + scrollback_size(config, allow_scrollback));
for _ in 0..physical_rows {
lines.push_back(Line::with_width(0));
lines.push_back(Line::with_width(physical_cols));
}
Screen {
@ -215,7 +215,7 @@ impl Screen {
// lines than the viewport size, or we resized taller,
// pad us back out to the viewport size
while self.lines.len() < physical_rows {
self.lines.push_back(Line::with_width(0));
self.lines.push_back(Line::with_width(self.physical_cols));
}
let new_cursor_y;
@ -236,7 +236,7 @@ impl Screen {
physical_rows.saturating_sub(new_cursor_y as usize);
let actual_num_rows_after_cursor = self.lines.len().saturating_sub(cursor_y);
for _ in actual_num_rows_after_cursor..required_num_rows_after_cursor {
self.lines.push_back(Line::with_width(0));
self.lines.push_back(Line::with_width(self.physical_cols));
}
} else {
// Compute the new cursor location; this is logically the inverse
@ -595,7 +595,7 @@ impl Screen {
for _ in 0..to_move {
let mut line = self.lines.remove(remove_idx).unwrap();
// Make the line like a new one of the appropriate width
line.resize_and_clear(0, seqno);
line.resize_and_clear(self.physical_cols, seqno);
line.update_last_change_seqno(seqno);
if scroll_region.end as usize == self.physical_rows {
self.lines.push_back(line);
@ -620,11 +620,12 @@ impl Screen {
if scroll_region.end as usize == self.physical_rows {
// It's cheaper to push() than it is insert() at the end
for _ in 0..to_add {
self.lines.push_back(Line::with_width(0));
self.lines.push_back(Line::with_width(self.physical_cols));
}
} else {
for _ in 0..to_add {
self.lines.insert(phys_scroll.end, Line::with_width(0));
self.lines
.insert(phys_scroll.end, Line::with_width(self.physical_cols));
}
}
}
@ -672,7 +673,8 @@ impl Screen {
}
for _ in 0..num_rows {
self.lines.insert(phys_scroll.start, Line::with_width(0));
self.lines
.insert(phys_scroll.start, Line::with_width(self.physical_cols));
}
}

View File

@ -59,10 +59,7 @@ impl<'a> Performer<'a> {
let seqno = self.seqno;
let mut p = std::mem::take(&mut self.print);
let mut graphemes =
unicode_segmentation::UnicodeSegmentation::graphemes(p.as_str(), true).peekable();
while let Some(g) = graphemes.next() {
for g in unicode_segmentation::UnicodeSegmentation::graphemes(p.as_str(), true) {
let g = if (self.shift_out && self.g1_charset == CharSet::DecLineDrawing)
|| (!self.shift_out && self.g0_charset == CharSet::DecLineDrawing)
{
@ -138,7 +135,6 @@ impl<'a> Performer<'a> {
// If we didn't do this, then we'd effectively filter them out from
// the model, which seems like a lossy design choice.
let print_width = grapheme_column_width(g).max(1);
let is_last = graphemes.peek().is_none();
let wrappable = x + print_width >= width;
let cell = Cell::new_grapheme_with_width(g, print_width, pen);
@ -153,10 +149,9 @@ impl<'a> Performer<'a> {
// Assign the cell
log::trace!(
"print x={} y={} is_last={} print_width={} width={} cell={:?}",
"print x={} y={} print_width={} width={} cell={:?}",
x,
y,
is_last,
print_width,
width,
cell

View File

@ -34,7 +34,12 @@ fn test_irm() {
term.print("foo");
term.cup(0, 0);
term.print("\x1b[4hBAR");
assert_visible_contents(&term, file!(), line!(), &["BARfoo", "", ""]);
assert_visible_contents(
&term,
file!(),
line!(),
&["BARfoo ", " ", " "],
);
}
#[test]

View File

@ -327,7 +327,18 @@ fn test_semantic() {
));
term.print("there");
assert_visible_contents(&term, file!(), line!(), &["hello", "there", "", "", ""]);
assert_visible_contents(
&term,
file!(),
line!(),
&[
"hello ",
"there ",
" ",
" ",
" ",
],
);
term.cup(0, 2);
term.print(format!(
@ -339,7 +350,13 @@ fn test_semantic() {
&term,
file!(),
line!(),
&["hello", "there", "three", "", ""],
&[
"hello ",
"there ",
"three ",
" ",
" ",
],
);
k9::snapshot!(
@ -349,8 +366,8 @@ fn test_semantic() {
SemanticZone {
start_y: 0,
start_x: 0,
end_y: 2,
end_x: 4,
end_y: 4,
end_x: 9,
semantic_type: Output,
},
]
@ -457,7 +474,18 @@ fn basic_output() {
term.set_auto_wrap(false);
term.print("hello, world!");
assert_visible_contents(&term, file!(), line!(), &["", " hello, w!", "", "", ""]);
assert_visible_contents(
&term,
file!(),
line!(),
&[
" ",
" hello, w!",
" ",
" ",
" ",
],
);
term.set_auto_wrap(true);
term.erase_in_display(EraseInDisplay::EraseToStartOfDisplay);
@ -467,7 +495,13 @@ fn basic_output() {
&term,
file!(),
line!(),
&[" ", " hello, wo", "rld!", "", ""],
&[
" ",
" hello, wo",
"rld! ",
" ",
" ",
],
);
term.erase_in_display(EraseInDisplay::EraseToStartOfDisplay);
@ -475,7 +509,13 @@ fn basic_output() {
&term,
file!(),
line!(),
&[" ", " ", " ", "", ""],
&[
" ",
" ",
" ",
" ",
" ",
],
);
term.cup(0, 2);
@ -494,7 +534,13 @@ fn basic_output() {
&term,
file!(),
line!(),
&[" ", " ", " ", "", ""],
&[
" ",
" ",
" ",
" ",
" ",
],
);
}
@ -553,7 +599,7 @@ fn scroll_up_within_left_and_right_margins() {
&term,
file!(),
line!(),
&[&ones, &twos, &threes, &fours, &fives],
&["111 ", "222 ", "333 ", "44444", "555 "],
);
term.set_mode("?69", true); // allow left/right margins to be set
@ -566,8 +612,8 @@ fn scroll_up_within_left_and_right_margins() {
file!(),
line!(),
&[
&ones,
&twos,
"111 ",
"222 ",
&format!("3{}", "4".repeat(NUM_COLS + 1)),
&format!("4{}", "5".repeat(NUM_COLS - 1)),
&format!("5{} ", " ".repeat(NUM_COLS - 1)),
@ -599,7 +645,7 @@ fn scroll_down_within_left_and_right_margins() {
&term,
file!(),
line!(),
&[&ones, &twos, &threes, &fours, &fives],
&["111 ", "222 ", "333 ", "44444", "555 "],
);
term.set_mode("?69", true); // allow left/right margins to be set
@ -616,8 +662,8 @@ fn scroll_down_within_left_and_right_margins() {
file!(),
line!(),
&[
&ones,
&twos,
"111 ",
"222 ",
&format!("3{} ", " ".repeat(NUM_COLS - 1)),
&format!("4{}", "3".repeat(NUM_COLS - 1)),
&format!("5{}", "4".repeat(NUM_COLS + 1)),
@ -647,7 +693,12 @@ fn test_delete_lines() {
let seqno = term.current_seqno();
term.assert_dirty_lines(seqno, &[], None);
term.delete_lines(2);
assert_visible_contents(&term, file!(), line!(), &["111", "444", "555", "", ""]);
assert_visible_contents(
&term,
file!(),
line!(),
&["111", "444", "555", " ", " "],
);
term.assert_dirty_lines(seqno, &[1, 2, 3, 4], None);
term.cup(0, 3);
@ -668,7 +719,12 @@ fn test_delete_lines() {
print_all_lines(&term);
term.delete_lines(2);
assert_visible_contents(&term, file!(), line!(), &["111", "aaa", "", "", "bbb"]);
assert_visible_contents(
&term,
file!(),
line!(),
&["111", "aaa", " ", " ", "bbb"],
);
term.assert_dirty_lines(seqno, &[1, 2, 3], None);
// expand the scroll region to fill the screen
@ -678,7 +734,12 @@ fn test_delete_lines() {
print_all_lines(&term);
term.delete_lines(1);
assert_visible_contents(&term, file!(), line!(), &["aaa", "", "", "bbb", ""]);
assert_visible_contents(
&term,
file!(),
line!(),
&["aaa", " ", " ", "bbb", " "],
);
term.assert_dirty_lines(seqno, &[4], None);
}
@ -692,7 +753,10 @@ fn test_dec_special_graphics() {
&term,
file!(),
line!(),
&["ABC▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥DEF", "hello"],
&[
"ABC▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥DEF ",
"hello ",
],
);
term = TestTerm::new(2, 50, 0);
@ -701,7 +765,10 @@ fn test_dec_special_graphics() {
&term,
file!(),
line!(),
&["SO-ABC▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥DEF", "SI-hello"],
&[
"SO-ABC▒␉␌␍␊°±␤␋┘┐┌└┼⎺⎻─⎼⎽├┤┴┬│≤≥DEF ",
"SI-hello ",
],
);
}
@ -715,7 +782,12 @@ fn test_dec_double_width() {
&term,
file!(),
line!(),
&["line1", "line2", "line3", "line4"],
&[
"line1 ",
"line2 ",
"line3 ",
"line4 ",
],
);
let lines = term.screen().visible_lines();
@ -736,7 +808,9 @@ fn test_resize_wrap() {
&term,
file!(),
line!(),
&["111", "2222", "aa", "333", "", "", "", ""],
&[
"111 ", "2222", "aa ", "333 ", " ", " ", " ", " ",
],
);
term.resize(LINES, 5, 0, 0);
assert_visible_contents(
@ -750,21 +824,27 @@ fn test_resize_wrap() {
&term,
file!(),
line!(),
&["111", "2222aa", "333", "", "", "", "", ""],
&[
"111 ", "2222aa", "333 ", " ", " ", " ", " ", " ",
],
);
term.resize(LINES, 7, 0, 0);
assert_visible_contents(
&term,
file!(),
line!(),
&["111", "2222aa", "333", "", "", "", "", ""],
&[
"111 ", "2222aa", "333 ", " ", " ", " ", " ", " ",
],
);
term.resize(LINES, 8, 0, 0);
assert_visible_contents(
&term,
file!(),
line!(),
&["111", "2222aa", "333", "", "", "", "", ""],
&[
"111 ", "2222aa", "333 ", " ", " ", " ", " ", " ",
],
);
// Resize smaller again
@ -773,28 +853,36 @@ fn test_resize_wrap() {
&term,
file!(),
line!(),
&["111", "2222aa", "333", "", "", "", "", ""],
&[
"111 ", "2222aa", "333 ", " ", " ", " ", " ", " ",
],
);
term.resize(LINES, 6, 0, 0);
assert_visible_contents(
&term,
file!(),
line!(),
&["111", "2222aa", "333", "", "", "", "", ""],
&[
"111 ", "2222aa", "333 ", " ", " ", " ", " ", " ",
],
);
term.resize(LINES, 5, 0, 0);
assert_visible_contents(
&term,
file!(),
line!(),
&["111", "2222a", "a", "333", "", "", "", ""],
&[
"111 ", "2222a", "a", "333 ", " ", " ", " ", " ",
],
);
term.resize(LINES, 4, 0, 0);
assert_visible_contents(
&term,
file!(),
line!(),
&["111", "2222", "aa", "333", "", "", "", ""],
&[
"111 ", "2222", "aa", "333 ", " ", " ", " ", " ",
],
);
}
@ -901,7 +989,12 @@ fn test_scroll_margins() {
term.cup(0, 1);
term.print("W\n");
assert_all_contents(&term, file!(), line!(), &["1", "2", "z", "a", "W", "", ""]);
assert_all_contents(
&term,
file!(),
line!(),
&["1", "2", "z", "a", "W", " ", " "],
);
}
#[test]
@ -941,7 +1034,11 @@ fn test_hyperlinks() {
file!(),
line!(),
&term.screen().visible_lines(),
&[Line::from_text("hello", &linked), "".into(), "".into()],
&[
Line::from_text("hello", &linked),
" ".into(),
" ".into(),
],
Compare::TEXT | Compare::ATTRS,
);

View File

@ -62,7 +62,7 @@ pub enum DoubleClickRange {
impl Line {
pub fn with_width(width: usize) -> Self {
let mut cells = Vec::with_capacity(width);
cells.resize(width, Cell::default());
cells.resize_with(width, Cell::blank);
let bits = LineBits::NONE;
Self {
bits,
@ -99,16 +99,17 @@ impl Line {
}
pub fn resize_and_clear(&mut self, width: usize, seqno: SequenceNo) {
let blank = Cell::default();
self.cells.clear();
self.cells.resize(width, blank);
for c in &mut self.cells {
*c = Cell::blank();
}
self.cells.resize_with(width, Cell::blank);
self.cells.shrink_to_fit();
self.update_last_change_seqno(seqno);
self.bits = LineBits::NONE;
}
pub fn resize(&mut self, width: usize, seqno: SequenceNo) {
self.cells.resize(width, Cell::default());
self.cells.resize_with(width, Cell::blank);
self.update_last_change_seqno(seqno);
}
@ -116,7 +117,7 @@ impl Line {
/// Returns the list of resultant line(s)
pub fn wrap(mut self, width: usize, seqno: SequenceNo) -> Vec<Self> {
if let Some(end_idx) = self.cells.iter().rposition(|c| c.str() != " ") {
self.cells.resize(end_idx + 1, Cell::default());
self.cells.resize_with(end_idx + 1, Cell::blank);
let mut lines: Vec<_> = self
.cells
@ -448,7 +449,7 @@ impl Line {
// if the line isn't wide enough, pad it out with the default attributes.
if idx + width > self.cells.len() {
self.cells.resize(idx + width, Cell::default());
self.cells.resize_with(idx + width, Cell::blank);
}
self.invalidate_implicit_hyperlinks(seqno);
@ -511,7 +512,7 @@ impl Line {
}
if x >= self.cells.len() {
self.cells.resize(x, Cell::default());
self.cells.resize_with(x, Cell::blank);
}
// If we're inserting a wide cell, we should also insert the overlapped cells.
@ -557,7 +558,7 @@ impl Line {
.iter()
.rposition(|c| c.str() != " " || c.attrs() != &def_attr)
{
self.cells.resize(end_idx + 1, Cell::default());
self.cells.resize_with(end_idx + 1, Cell::blank);
self.update_last_change_seqno(seqno);
}
}