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",
"markup5ever",
"mime",
"parking_lot",
"resvg",
"servo_allocator",
"servo_arc",

View file

@ -4,6 +4,7 @@
use std::borrow::ToOwned;
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, OnceLock};
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
/// `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::default::Default;
use std::hash::{BuildHasherDefault, Hash, Hasher};
use std::ops::Deref;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
@ -14,7 +15,6 @@ use compositing_traits::CrossProcessCompositorApi;
use fnv::FnvHasher;
use fonts_traits::StylesheetWebFontLoadFinishedCallback;
use log::{debug, trace};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use malloc_size_of_derive::MallocSizeOf;
use net_traits::request::{Destination, Referrer, RequestBuilder};
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)
#[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
/// working with fonts. It is the public API used by the layout and
/// paint code. It talks directly to the system font service where
/// required.
#[derive(MallocSizeOf)]
pub struct FontContext {
#[conditional_malloc_size_of]
system_font_service_proxy: Arc<SystemFontServiceProxy>,
resource_threads: Mutex<CoreResourceThread>,
/// 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
/// can be selected with that style.
resolved_font_groups:
RwLock<HashMap<FontGroupCacheKey, Arc<RwLock<FontGroup>>, BuildHasherDefault<FnvHasher>>>,
RwLock<HashMap<FontGroupCacheKey, FontGroupRef, BuildHasherDefault<FnvHasher>>>,
web_fonts: CrossThreadFontStore,
@ -83,26 +96,6 @@ pub struct FontContext {
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 {
pub fn new(
system_font_service_proxy: Arc<SystemFontServiceProxy>,
@ -116,7 +109,7 @@ impl FontContext {
compositor_api: Mutex::new(compositor_api),
fonts: Default::default(),
resolved_font_groups: Default::default(),
web_fonts: Arc::new(RwLock::default()),
web_fonts: Default::default(),
webrender_font_keys: RwLock::default(),
webrender_font_instance_keys: RwLock::default(),
have_removed_web_fonts: AtomicBool::new(false),
@ -152,7 +145,7 @@ impl FontContext {
) -> Arc<RwLock<FontGroup>> {
let cache_key = FontGroupCacheKey { size, style };
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);
@ -161,7 +154,7 @@ impl FontContext {
let font_group = Arc::new(RwLock::new(FontGroup::new(&cache_key.style, descriptor)));
self.resolved_font_groups
.write()
.insert(cache_key, font_group.clone());
.insert(cache_key, FontGroupRef(font_group.clone()));
font_group
}
@ -275,12 +268,12 @@ impl FontContext {
font_descriptor: FontDescriptor,
synthesized_small_caps: Option<FontRef>,
) -> Result<FontRef, &'static str> {
Ok(Arc::new(Font::new(
Ok(FontRef(Arc::new(Font::new(
font_template.clone(),
font_descriptor.clone(),
self.get_font_data(&font_template.identifier()),
synthesized_small_caps,
)?))
)?)))
}
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/. */
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::Arc;
use log::warn;
@ -21,7 +22,16 @@ pub struct FontStore {
web_fonts_loading_for_stylesheets: Vec<(DocumentStyleSheet, 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 {
pub(crate) fn clear(&mut self) {

View file

@ -22,6 +22,7 @@ pub use font_store::*;
pub use font_template::*;
pub use glyph::*;
use ipc_channel::ipc::IpcSharedMemory;
use malloc_size_of_derive::MallocSizeOf;
pub use platform::LocalFontIdentifier;
pub use shaper::*;
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
/// [`IpcSharedMemory`] handle, so that it can be send without serialization
/// across IPC channels.
#[derive(Clone)]
pub struct FontData(pub(crate) Arc<IpcSharedMemory>);
#[derive(Clone, MallocSizeOf)]
pub struct FontData(#[conditional_malloc_size_of] pub(crate) Arc<IpcSharedMemory>);
impl FontData {
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
/// `FontContext` instances.
#[derive(Debug)]
#[derive(Debug, MallocSizeOf)]
pub struct SystemFontServiceProxy {
sender: Mutex<IpcSender<SystemFontServiceMessage>>,
templates: RwLock<HashMap<FontTemplateCacheKey, Vec<FontTemplateRef>>>,

View file

@ -23,6 +23,7 @@ ipc-channel = { workspace = true }
keyboard-types = { workspace = true }
markup5ever = { workspace = true }
mime = { workspace = true }
parking_lot = { workspace = true }
resvg = { workspace = true }
servo_allocator = { path = "../allocator" }
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> {
fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
self.0.size_of(ops)