Skip to content

Commit 7ed840b

Browse files
committed
perf: Remove RcCharacterStyles::Default, allow enum niche optimisation
This reduces TerminalCharacter from 24 to 16 bytes With a 10MB single line catted into a fresh terminal, VmRSS goes from 478396 kB to 398108 kB (as reported by /proc/<pid>/status) before/after this patch Given a 10MB line catted into the terminal, a toggle-fullscreen + toggle-fullscreen + close-pane + `run true` goes from ~7s to ~4.75s
1 parent afd2f65 commit 7ed840b

File tree

3 files changed

+54
-57
lines changed

3 files changed

+54
-57
lines changed

zellij-server/src/output/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::panes::Row;
66
use crate::{
77
panes::sixel::SixelImageStore,
88
panes::terminal_character::{AnsiCode, CharacterStyles},
9-
panes::{LinkHandler, TerminalCharacter, EMPTY_TERMINAL_CHARACTER},
9+
panes::{LinkHandler, TerminalCharacter, DEFAULT_STYLES, EMPTY_TERMINAL_CHARACTER},
1010
ClientId,
1111
};
1212
use std::cell::RefCell;
@@ -92,7 +92,7 @@ fn serialize_chunks_with_newlines(
9292
for character_chunk in character_chunks {
9393
let chunk_changed_colors = character_chunk.changed_colors();
9494
let mut character_styles =
95-
CharacterStyles::new().enable_styled_underlines(styled_underlines);
95+
DEFAULT_STYLES.enable_styled_underlines(styled_underlines);
9696
vte_output.push_str("\n\r");
9797
let mut chunk_width = character_chunk.x;
9898
for t_character in character_chunk.terminal_characters.iter() {
@@ -132,7 +132,7 @@ fn serialize_chunks(
132132
for character_chunk in character_chunks {
133133
let chunk_changed_colors = character_chunk.changed_colors();
134134
let mut character_styles =
135-
CharacterStyles::new().enable_styled_underlines(styled_underlines);
135+
DEFAULT_STYLES.enable_styled_underlines(styled_underlines);
136136
vte_goto_instruction(character_chunk.x, character_chunk.y, &mut vte_output)
137137
.with_context(err_context)?;
138138
let mut chunk_width = character_chunk.x;

zellij-server/src/panes/terminal_character.rs

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ pub const RESET_STYLES: CharacterStyles = CharacterStyles {
3636
styled_underlines_enabled: false,
3737
};
3838

39-
// Have to manually write this out because Default::default
40-
// is not a const fn
41-
const DEFAULT_STYLES: CharacterStyles = CharacterStyles {
39+
// Prefer to use RcCharacterStyles::default() where it makes sense
40+
// as it will reduce memory usage
41+
pub const DEFAULT_STYLES: CharacterStyles = CharacterStyles {
4242
foreground: None,
4343
background: None,
4444
underline_color: None,
@@ -55,6 +55,11 @@ const DEFAULT_STYLES: CharacterStyles = CharacterStyles {
5555
styled_underlines_enabled: false,
5656
};
5757

58+
thread_local! {
59+
static RC_DEFAULT_STYLES: RcCharacterStyles =
60+
RcCharacterStyles::Rc(Rc::new(DEFAULT_STYLES));
61+
}
62+
5863
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5964
pub enum AnsiCode {
6065
On,
@@ -149,32 +154,36 @@ impl NamedColor {
149154
}
150155
}
151156

152-
#[derive(Clone, Debug, Default, PartialEq)]
157+
// This enum carefully only has two variants so
158+
// enum niche optimisations can keep it to 8 bytes
159+
#[derive(Clone, Debug, PartialEq)]
153160
pub enum RcCharacterStyles {
154-
#[default]
155-
Default,
156161
Reset,
157162
Rc(Rc<CharacterStyles>),
158163
}
164+
const _: [(); 8] = [(); std::mem::size_of::<RcCharacterStyles>()];
159165

160166
impl From<CharacterStyles> for RcCharacterStyles {
161167
fn from(styles: CharacterStyles) -> Self {
162-
if styles == DEFAULT_STYLES {
163-
RcCharacterStyles::Default
164-
} else if styles == RESET_STYLES {
168+
if styles == RESET_STYLES {
165169
RcCharacterStyles::Reset
166170
} else {
167171
RcCharacterStyles::Rc(Rc::new(styles))
168172
}
169173
}
170174
}
171175

176+
impl Default for RcCharacterStyles {
177+
fn default() -> Self {
178+
RC_DEFAULT_STYLES.with(|s| s.clone())
179+
}
180+
}
181+
172182
impl std::ops::Deref for RcCharacterStyles {
173183
type Target = CharacterStyles;
174184

175185
fn deref(&self) -> &Self::Target {
176186
match self {
177-
RcCharacterStyles::Default => &DEFAULT_STYLES,
178187
RcCharacterStyles::Reset => &RESET_STYLES,
179188
RcCharacterStyles::Rc(styles) => &*styles,
180189
}
@@ -189,6 +198,10 @@ impl Display for RcCharacterStyles {
189198
}
190199

191200
impl RcCharacterStyles {
201+
pub fn reset() -> Self {
202+
Self::Reset
203+
}
204+
192205
pub fn update(&mut self, f: impl FnOnce(&mut CharacterStyles)) {
193206
let mut styles: CharacterStyles = **self;
194207
f(&mut styles);
@@ -214,12 +227,6 @@ pub struct CharacterStyles {
214227
pub styled_underlines_enabled: bool,
215228
}
216229

217-
impl Default for CharacterStyles {
218-
fn default() -> Self {
219-
DEFAULT_STYLES
220-
}
221-
}
222-
223230
impl PartialEq for CharacterStyles {
224231
fn eq(&self, other: &Self) -> bool {
225232
self.foreground == other.foreground
@@ -239,9 +246,6 @@ impl PartialEq for CharacterStyles {
239246
}
240247

241248
impl CharacterStyles {
242-
pub fn new() -> Self {
243-
Self::default()
244-
}
245249
pub fn foreground(mut self, foreground_code: Option<AnsiCode>) -> Self {
246250
self.foreground = foreground_code;
247251
self
@@ -329,7 +333,7 @@ impl CharacterStyles {
329333

330334
// create diff from all changed styles
331335
let mut diff =
332-
CharacterStyles::new().enable_styled_underlines(self.styled_underlines_enabled);
336+
DEFAULT_STYLES.enable_styled_underlines(self.styled_underlines_enabled);
333337

334338
if self.foreground != new_styles.foreground {
335339
diff.foreground = new_styles.foreground;
@@ -388,7 +392,7 @@ impl CharacterStyles {
388392
}
389393
Some(diff)
390394
}
391-
pub fn reset_all(&mut self) {
395+
fn reset_all(&mut self) {
392396
self.foreground = Some(AnsiCode::Reset);
393397
self.background = Some(AnsiCode::Reset);
394398
self.underline_color = Some(AnsiCode::Reset);
@@ -917,6 +921,9 @@ pub struct TerminalCharacter {
917921
pub styles: RcCharacterStyles,
918922
width: u8,
919923
}
924+
// This size has significant memory and CPU implications for long lines,
925+
// be careful about allowing it to grow
926+
const _: [(); 16] = [(); std::mem::size_of::<TerminalCharacter>()];
920927

921928
impl TerminalCharacter {
922929
#[inline]

zellij-server/src/ui/pane_boundaries_frame.rs

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::output::CharacterChunk;
2-
use crate::panes::{AnsiCode, CharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER};
2+
use crate::panes::{AnsiCode, RcCharacterStyles, TerminalCharacter, EMPTY_TERMINAL_CHARACTER};
33
use crate::ui::boundaries::boundary_type;
44
use crate::ClientId;
55
use zellij_utils::data::{client_id_to_colors, PaletteColor, Style};
@@ -11,22 +11,17 @@ use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
1111
fn foreground_color(characters: &str, color: Option<PaletteColor>) -> Vec<TerminalCharacter> {
1212
let mut colored_string = Vec::new();
1313
for character in characters.chars() {
14-
let styles = match color {
15-
Some(palette_color) => {
16-
let mut styles = CharacterStyles::new();
17-
styles.reset_all();
18-
styles
19-
.foreground(Some(AnsiCode::from(palette_color)))
20-
.bold(Some(AnsiCode::On))
21-
},
22-
None => {
23-
let mut styles = CharacterStyles::new();
24-
styles.reset_all();
25-
styles.bold(Some(AnsiCode::On))
26-
},
27-
};
28-
let terminal_character =
29-
TerminalCharacter::new_styled(character, styles.into());
14+
let mut styles = RcCharacterStyles::reset();
15+
styles.update(|styles| {
16+
styles.bold = Some(AnsiCode::On);
17+
match color {
18+
Some(palette_color) => {
19+
styles.foreground = Some(AnsiCode::from(palette_color));
20+
},
21+
None => {},
22+
}
23+
});
24+
let terminal_character = TerminalCharacter::new_styled(character, styles);
3025
colored_string.push(terminal_character);
3126
}
3227
colored_string
@@ -35,22 +30,17 @@ fn foreground_color(characters: &str, color: Option<PaletteColor>) -> Vec<Termin
3530
fn background_color(characters: &str, color: Option<PaletteColor>) -> Vec<TerminalCharacter> {
3631
let mut colored_string = Vec::new();
3732
for character in characters.chars() {
38-
let styles = match color {
39-
Some(palette_color) => {
40-
let mut styles = CharacterStyles::new();
41-
styles.reset_all();
42-
styles
43-
.background(Some(AnsiCode::from(palette_color)))
44-
.bold(Some(AnsiCode::On))
45-
},
46-
None => {
47-
let mut styles = CharacterStyles::new();
48-
styles.reset_all();
49-
styles
50-
},
51-
};
52-
let terminal_character =
53-
TerminalCharacter::new_styled(character, styles.into());
33+
let mut styles = RcCharacterStyles::reset();
34+
styles.update(|styles| {
35+
match color {
36+
Some(palette_color) => {
37+
styles.background = Some(AnsiCode::from(palette_color));
38+
styles.bold(Some(AnsiCode::On));
39+
},
40+
None => {},
41+
}
42+
});
43+
let terminal_character = TerminalCharacter::new_styled(character, styles);
5444
colored_string.push(terminal_character);
5545
}
5646
colored_string

0 commit comments

Comments
 (0)