Commit 3d7e88e8 authored by Christian Duerr's avatar Christian Duerr Committed by GitHub

Dynamically initialize grid storage

Previously Alacritty has initialized all lines in the buffer as soon as
it is started. This had the effect that terminals which aren't making
use of the scrollback buffer yet, would still consume large amounts of
memory, potentially even freezing the system at startup.

To resolve this problem, the grid is now dynamically resized in chunks
of `1000` rows. The initial size is just the visible area itself, then
every time lines are written to the terminal emulator, the grid storage
is grown when required.

With the worst-case scenario of having 100_000 lines scrollback
configured, this change improves startup performance at the cost of
scrolling performance.

On my machine the startup changes from ~0.3 to ~0.2 seconds.

The scrolling performance with large throughput is not affected, however
it is slowed down when the number of lines scrolled are close to the
100_000 configured as scrollback. The most taxing benchmark I've found
for this was running `yes | dd count=500 > 500.txt` (note the relatively
small file size). This will cause a slowdown on the first run from 0.05s
to 0.15s. While this is significant, it lines up with the time saved at
startup.

This fixes #1236.
parent a752066b
......@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Inverse/Selection color is now modelled after XTerm/VTE instead of URxvt to improve consistency
- First click on unfocused Alacritty windows is no longer ignored on platforms other than macOS
- Reduce memory usage significantly by only initializing part of the scrollback buffer at startup
### Fixed
......
......@@ -29,6 +29,8 @@ mod tests;
mod storage;
use self::storage::Storage;
const MIN_INIT_SIZE: usize = 1_000;
/// Bidirection iterator
pub trait BidirectionalIterator: Iterator {
fn prev(&mut self) -> Option<Self::Item>;
......@@ -92,6 +94,9 @@ pub struct Grid<T> {
/// Selected region
#[serde(skip)]
pub selection: Option<Selection>,
#[serde(default)]
max_scroll_limit: usize,
}
pub struct GridIterator<'a, T: 'a> {
......@@ -113,7 +118,7 @@ pub enum Scroll {
impl<T: Copy + Clone> Grid<T> {
pub fn new(lines: index::Line, cols: index::Column, scrollback: usize, template: T) -> Grid<T> {
let raw = Storage::with_capacity(*lines + scrollback, lines, Row::new(cols, &template));
let raw = Storage::with_capacity(lines, Row::new(cols, &template));
Grid {
raw,
cols,
......@@ -121,6 +126,7 @@ impl<T: Copy + Clone> Grid<T> {
display_offset: 0,
scroll_limit: 0,
selection: None,
max_scroll_limit: scrollback,
}
}
......@@ -206,11 +212,19 @@ impl<T: Copy + Clone> Grid<T> {
}
}
fn increase_scroll_limit(&mut self, count: usize) {
self.scroll_limit = min(
self.scroll_limit + count,
self.raw.len().saturating_sub(*self.lines),
);
fn increase_scroll_limit(&mut self, count: usize, template: &T)
{
self.scroll_limit = min(self.scroll_limit + count, self.max_scroll_limit);
// Initialize new lines when the history buffer is smaller than the scroll limit
let history_size = self.raw.len().saturating_sub(*self.lines);
if history_size < self.scroll_limit {
let new = min(
max(self.scroll_limit - history_size, MIN_INIT_SIZE),
self.max_scroll_limit - history_size,
);
self.raw.initialize(new, Row::new(self.cols, template));
}
}
fn decrease_scroll_limit(&mut self, count: usize) {
......@@ -356,7 +370,7 @@ impl<T: Copy + Clone> Grid<T> {
);
}
self.increase_scroll_limit(*positions);
self.increase_scroll_limit(*positions, template);
// Rotate the entire line buffer. If there's a scrolling region
// active, the bottom lines are restored in the next step.
......
......@@ -79,18 +79,18 @@ impl<T: PartialEq> ::std::cmp::PartialEq for Storage<T> {
impl<T> Storage<T> {
#[inline]
pub fn with_capacity(cap: usize, lines: Line, template: Row<T>) -> Storage<T>
pub fn with_capacity(lines: Line, template: Row<T>) -> Storage<T>
where
T: Clone,
{
// Allocate all lines in the buffer, including scrollback history
let inner = vec![template; cap];
// Initialize visible lines, the scrollback buffer is initialized dynamically
let inner = vec![template; lines.0];
Storage {
inner,
zero: 0,
visible_lines: lines - 1,
len: cap,
len: lines.0,
}
}
......@@ -169,23 +169,24 @@ impl<T> Storage<T> {
/// Truncate the invisible elements from the raw buffer
pub fn truncate(&mut self) {
// Calculate shrinkage/offset for indexing
let shrinkage = self.inner.len() - self.len;
let shrinkage_start = ::std::cmp::min(self.zero, shrinkage);
self.inner.rotate_left(self.zero);
self.inner.truncate(self.len);
// Create two vectors with correct ordering
let mut split = self.inner.split_off(self.zero);
self.zero = 0;
}
// Truncate the buffers
let len = self.inner.len();
let split_len = split.len();
self.inner.truncate(len - shrinkage_start);
split.truncate(split_len - (shrinkage - shrinkage_start));
/// Dynamically grow the storage buffer at runtime
pub fn initialize(&mut self, num_rows: usize, template_row: Row<T>)
where T: Clone
{
let mut new = vec![template_row; num_rows];
// Merge buffers again and reset zero
split.append(&mut self.inner);
self.inner = split;
self.zero = 0;
let mut split = self.inner.split_off(self.zero);
self.inner.append(&mut new);
self.inner.append(&mut split);
self.zero += num_rows;
self.len += num_rows;
}
#[inline]
......@@ -649,3 +650,45 @@ fn shrink_then_grow() {
assert_eq!(storage.zero, growing_expected.zero);
assert_eq!(storage.len, growing_expected.len);
}
#[test]
fn initialize() {
// Setup storage area
let mut storage = Storage {
inner: vec![
Row::new(Column(1), &'4'),
Row::new(Column(1), &'5'),
Row::new(Column(1), &'0'),
Row::new(Column(1), &'1'),
Row::new(Column(1), &'2'),
Row::new(Column(1), &'3'),
],
zero: 2,
visible_lines: Line(0),
len: 6,
};
// Initialize additional lines
storage.initialize(3, Row::new(Column(1), &'-'));
// Make sure the lines are present and at the right location
let shrinking_expected = Storage {
inner: vec![
Row::new(Column(1), &'4'),
Row::new(Column(1), &'5'),
Row::new(Column(1), &'-'),
Row::new(Column(1), &'-'),
Row::new(Column(1), &'-'),
Row::new(Column(1), &'0'),
Row::new(Column(1), &'1'),
Row::new(Column(1), &'2'),
Row::new(Column(1), &'3'),
],
zero: 5,
visible_lines: Line(0),
len: 9,
};
assert_eq!(storage.inner, shrinking_expected.inner);
assert_eq!(storage.zero, shrinking_expected.zero);
assert_eq!(storage.len, shrinking_expected.len);
}
%  UL  ~/…/tests/ref/grid_reset  issue-1244 ↑  [?2004hfor i in {100..2}; do echo $i; donefor i in {100..2}; do echo $i; done[?2004l
100
99
98
97
96
95
94
93
92
91
90
89
88
87
86
85
84
83
82
81
80
79
78
77
76
75
74
73
72
71
70
69
68
67
66
65
64
63
62
61
60
59
58
57
56
55
54
53
52
51
50
49
48
47
46
45
44
43
42
41
40
39
38
37
36
35
34
33
32
31
30
29
28
27
26
25
24
23
22
21
20
19
18
17
16
15
14
13
12
11
10
9
8
7
6
5
4
3
%  UL  ~/…/tests/ref/grid_reset  dynamic-alloc  [?2004hfor i in {0..100}; do echo $i; donefor i in {0..100}; do echo $i; done[?2004l
0
1
2
%  UL  ~/…/tests/ref/grid_reset  issue-1244 ↑  [?2004hresetreset[?2004l
c]104[!p[?3;4l> %  UL  ~/…/tests/ref/grid_reset  issue-1244 ↑  [?2004h
\ No newline at end of file
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
%  UL  ~/…/tests/ref/grid_reset  dynamic-alloc  [?2004hrm *resetset[?2004l
c]104[!p[?3;4l> %  UL  ~/…/tests/ref/grid_reset  dynamic-alloc  [?2004h
\ No newline at end of file
......
This diff is collapsed.
{"width":939.0,"height":1020.0,"cell_width":8.0,"cell_height":18.0,"padding_x":2.0,"padding_y":2.0}
\ No newline at end of file
{"width":2532.0,"height":1380.0,"cell_width":9.0,"cell_height":19.0,"padding_x":3.0,"padding_y":3.0}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment