fonts: Measure more FontContext heap usage. (#38733)

Replace a hand-written MallocSizeOf implementation with an automatically
derived one. This exposes more than 1MB of previously-untracked heap
data on servo.org.

Testing: Compared about:memory output for servo.org before and after.
Part of: #11559

Signed-off-by: Josh Matthews <josh@joshmatthews.net>
This commit is contained in:
Josh Matthews 2025-08-17 14:59:30 -04:00 committed by GitHub
parent d490c5c06b
commit 9da8142e2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 59 additions and 32 deletions

1
Cargo.lock generated
View file

@ -7715,6 +7715,7 @@ dependencies = [
"keyboard-types", "keyboard-types",
"markup5ever", "markup5ever",
"mime", "mime",
"parking_lot",
"resvg", "resvg",
"servo_allocator", "servo_allocator",
"servo_arc", "servo_arc",

View file

@ -4,6 +4,7 @@
use std::borrow::ToOwned; use std::borrow::ToOwned;
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Deref;
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, OnceLock}; use std::sync::{Arc, OnceLock};
use std::time::Instant; use std::time::Instant;
@ -548,7 +549,15 @@ impl Font {
} }
} }
pub type FontRef = Arc<Font>; #[derive(Clone, MallocSizeOf)]
pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
impl Deref for FontRef {
type Target = Arc<Font>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// A `FontGroup` is a prioritised list of fonts for a given set of font styles. It is used by /// A `FontGroup` is a prioritised list of fonts for a given set of font styles. It is used by
/// `TextRun` to decide which font to render a character with. If none of the fonts listed in the /// `TextRun` to decide which font to render a character with. If none of the fonts listed in the

View file

@ -5,6 +5,7 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::default::Default; use std::default::Default;
use std::hash::{BuildHasherDefault, Hash, Hasher}; use std::hash::{BuildHasherDefault, Hash, Hasher};
use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
@ -14,7 +15,6 @@ use compositing_traits::CrossProcessCompositorApi;
use fnv::FnvHasher; use fnv::FnvHasher;
use fonts_traits::StylesheetWebFontLoadFinishedCallback; use fonts_traits::StylesheetWebFontLoadFinishedCallback;
use log::{debug, trace}; use log::{debug, trace};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use malloc_size_of_derive::MallocSizeOf; use malloc_size_of_derive::MallocSizeOf;
use net_traits::request::{Destination, Referrer, RequestBuilder}; use net_traits::request::{Destination, Referrer, RequestBuilder};
use net_traits::{CoreResourceThread, FetchResponseMsg, ResourceThreads, fetch_async}; use net_traits::{CoreResourceThread, FetchResponseMsg, ResourceThreads, fetch_async};
@ -45,12 +45,25 @@ use crate::{FontData, LowercaseFontFamilyName, PlatformFontMethods, SystemFontSe
static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h) static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h)
#[derive(MallocSizeOf)]
struct FontGroupRef(#[conditional_malloc_size_of] Arc<RwLock<FontGroup>>);
impl Deref for FontGroupRef {
type Target = Arc<RwLock<FontGroup>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
/// The FontContext represents the per-thread/thread state necessary for /// The FontContext represents the per-thread/thread state necessary for
/// working with fonts. It is the public API used by the layout and /// working with fonts. It is the public API used by the layout and
/// paint code. It talks directly to the system font service where /// paint code. It talks directly to the system font service where
/// required. /// required.
#[derive(MallocSizeOf)]
pub struct FontContext { pub struct FontContext {
#[conditional_malloc_size_of]
system_font_service_proxy: Arc<SystemFontServiceProxy>, system_font_service_proxy: Arc<SystemFontServiceProxy>,
resource_threads: Mutex<CoreResourceThread>, resource_threads: Mutex<CoreResourceThread>,
/// A sender that can send messages and receive replies from the compositor. /// A sender that can send messages and receive replies from the compositor.
@ -64,7 +77,7 @@ pub struct FontContext {
/// resolved [`FontGroup`] which contains information about all fonts that /// resolved [`FontGroup`] which contains information about all fonts that
/// can be selected with that style. /// can be selected with that style.
resolved_font_groups: resolved_font_groups:
RwLock<HashMap<FontGroupCacheKey, Arc<RwLock<FontGroup>>, BuildHasherDefault<FnvHasher>>>, RwLock<HashMap<FontGroupCacheKey, FontGroupRef, BuildHasherDefault<FnvHasher>>>,
web_fonts: CrossThreadFontStore, web_fonts: CrossThreadFontStore,
@ -83,26 +96,6 @@ pub struct FontContext {
have_removed_web_fonts: AtomicBool, have_removed_web_fonts: AtomicBool,
} }
impl MallocSizeOf for FontContext {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
let font_cache_size = self
.fonts
.read()
.iter()
.map(|(key, font)| {
key.size_of(ops) + font.as_ref().map_or(0, |font| (*font).size_of(ops))
})
.sum::<usize>();
let font_group_cache_size = self
.resolved_font_groups
.read()
.iter()
.map(|(key, font_group)| key.size_of(ops) + (*font_group.read()).size_of(ops))
.sum::<usize>();
font_cache_size + font_group_cache_size
}
}
impl FontContext { impl FontContext {
pub fn new( pub fn new(
system_font_service_proxy: Arc<SystemFontServiceProxy>, system_font_service_proxy: Arc<SystemFontServiceProxy>,
@ -116,7 +109,7 @@ impl FontContext {
compositor_api: Mutex::new(compositor_api), compositor_api: Mutex::new(compositor_api),
fonts: Default::default(), fonts: Default::default(),
resolved_font_groups: Default::default(), resolved_font_groups: Default::default(),
web_fonts: Arc::new(RwLock::default()), web_fonts: Default::default(),
webrender_font_keys: RwLock::default(), webrender_font_keys: RwLock::default(),
webrender_font_instance_keys: RwLock::default(), webrender_font_instance_keys: RwLock::default(),
have_removed_web_fonts: AtomicBool::new(false), have_removed_web_fonts: AtomicBool::new(false),
@ -152,7 +145,7 @@ impl FontContext {
) -> Arc<RwLock<FontGroup>> { ) -> Arc<RwLock<FontGroup>> {
let cache_key = FontGroupCacheKey { size, style }; let cache_key = FontGroupCacheKey { size, style };
if let Some(font_group) = self.resolved_font_groups.read().get(&cache_key) { if let Some(font_group) = self.resolved_font_groups.read().get(&cache_key) {
return font_group.clone(); return font_group.0.clone();
} }
let mut descriptor = FontDescriptor::from(&*cache_key.style); let mut descriptor = FontDescriptor::from(&*cache_key.style);
@ -161,7 +154,7 @@ impl FontContext {
let font_group = Arc::new(RwLock::new(FontGroup::new(&cache_key.style, descriptor))); let font_group = Arc::new(RwLock::new(FontGroup::new(&cache_key.style, descriptor)));
self.resolved_font_groups self.resolved_font_groups
.write() .write()
.insert(cache_key, font_group.clone()); .insert(cache_key, FontGroupRef(font_group.clone()));
font_group font_group
} }
@ -275,12 +268,12 @@ impl FontContext {
font_descriptor: FontDescriptor, font_descriptor: FontDescriptor,
synthesized_small_caps: Option<FontRef>, synthesized_small_caps: Option<FontRef>,
) -> Result<FontRef, &'static str> { ) -> Result<FontRef, &'static str> {
Ok(Arc::new(Font::new( Ok(FontRef(Arc::new(Font::new(
font_template.clone(), font_template.clone(),
font_descriptor.clone(), font_descriptor.clone(),
self.get_font_data(&font_template.identifier()), self.get_font_data(&font_template.identifier()),
synthesized_small_caps, synthesized_small_caps,
)?)) )?)))
} }
pub(crate) fn create_font_instance_key(&self, font: &Font) -> FontInstanceKey { pub(crate) fn create_font_instance_key(&self, font: &Font) -> FontInstanceKey {

View file

@ -3,6 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use log::warn; use log::warn;
@ -21,7 +22,16 @@ pub struct FontStore {
web_fonts_loading_for_stylesheets: Vec<(DocumentStyleSheet, usize)>, web_fonts_loading_for_stylesheets: Vec<(DocumentStyleSheet, usize)>,
web_fonts_loading_for_script: usize, web_fonts_loading_for_script: usize,
} }
pub(crate) type CrossThreadFontStore = Arc<RwLock<FontStore>>;
#[derive(Default, MallocSizeOf)]
pub(crate) struct CrossThreadFontStore(#[conditional_malloc_size_of] Arc<RwLock<FontStore>>);
impl Deref for CrossThreadFontStore {
type Target = Arc<RwLock<FontStore>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl FontStore { impl FontStore {
pub(crate) fn clear(&mut self) { pub(crate) fn clear(&mut self) {

View file

@ -22,6 +22,7 @@ pub use font_store::*;
pub use font_template::*; pub use font_template::*;
pub use glyph::*; pub use glyph::*;
use ipc_channel::ipc::IpcSharedMemory; use ipc_channel::ipc::IpcSharedMemory;
use malloc_size_of_derive::MallocSizeOf;
pub use platform::LocalFontIdentifier; pub use platform::LocalFontIdentifier;
pub use shaper::*; pub use shaper::*;
pub use system_font_service::*; pub use system_font_service::*;
@ -30,8 +31,8 @@ use unicode_properties::{EmojiStatus, UnicodeEmoji, emoji};
/// A data structure to store data for fonts. Data is stored internally in an /// A data structure to store data for fonts. Data is stored internally in an
/// [`IpcSharedMemory`] handle, so that it can be send without serialization /// [`IpcSharedMemory`] handle, so that it can be send without serialization
/// across IPC channels. /// across IPC channels.
#[derive(Clone)] #[derive(Clone, MallocSizeOf)]
pub struct FontData(pub(crate) Arc<IpcSharedMemory>); pub struct FontData(#[conditional_malloc_size_of] pub(crate) Arc<IpcSharedMemory>);
impl FontData { impl FontData {
pub fn from_bytes(bytes: &[u8]) -> Self { pub fn from_bytes(bytes: &[u8]) -> Self {

View file

@ -363,7 +363,7 @@ struct FontTemplateCacheKey {
/// The public interface to the [`SystemFontService`], used by per-Document /// The public interface to the [`SystemFontService`], used by per-Document
/// `FontContext` instances. /// `FontContext` instances.
#[derive(Debug)] #[derive(Debug, MallocSizeOf)]
pub struct SystemFontServiceProxy { pub struct SystemFontServiceProxy {
sender: Mutex<IpcSender<SystemFontServiceMessage>>, sender: Mutex<IpcSender<SystemFontServiceMessage>>,
templates: RwLock<HashMap<FontTemplateCacheKey, Vec<FontTemplateRef>>>, templates: RwLock<HashMap<FontTemplateCacheKey, Vec<FontTemplateRef>>>,

View file

@ -23,6 +23,7 @@ ipc-channel = { workspace = true }
keyboard-types = { workspace = true } keyboard-types = { workspace = true }
markup5ever = { workspace = true } markup5ever = { workspace = true }
mime = { workspace = true } mime = { workspace = true }
parking_lot = { workspace = true }
resvg = { workspace = true } resvg = { workspace = true }
servo_allocator = { path = "../allocator" } servo_allocator = { path = "../allocator" }
servo_arc = { workspace = true } servo_arc = { workspace = true }

View file

@ -613,6 +613,18 @@ impl<T: MallocSizeOf> MallocSizeOf for std::sync::Mutex<T> {
} }
} }
impl<T: MallocSizeOf> MallocSizeOf for parking_lot::Mutex<T> {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
(*self.lock()).size_of(ops)
}
}
impl<T: MallocSizeOf> MallocSizeOf for parking_lot::RwLock<T> {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
(*self.read()).size_of(ops)
}
}
impl<T: MallocSizeOf, Unit> MallocSizeOf for euclid::Length<T, Unit> { impl<T: MallocSizeOf, Unit> MallocSizeOf for euclid::Length<T, Unit> {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize { fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
self.0.size_of(ops) self.0.size_of(ops)