Load embedded fonts directly from .rdata instead of cloning (#6932)

The fonts we embed in Zed binary (Zed Sans & Zed Mono) weigh about 30Mb in total and we are cloning them several times during startup and loading of embedded assets (once explicitly in Zed and then under the hood in font-kit). Moreover, after loading we have at least 2 copies of each font in our program; one in .rdata and the other on the heap for use by font-kit.
This commit does away with that distinction (we're no longer allocating the font data) and slightly relaxes the interface of `TextSystem::add_fonts` by expecting one to pass `Cow<[u8]>` instead of `Arc<Vec<u8>>`. Additionally, `AssetSource::get` now returns `Cow<'static, [u8]>` instead of `Cow<'self, [u8]>`; all existing implementations conform with that change.

Note that this optimization takes effect only in Release builds, as the library we use for asset embedding - rust-embed - embeds the assets only in Release mode and in Dev builds it simply loads data from disk. Thus it returns `Cow<[u8]>` in it's interface. Therefore, we still copy that memory around in Dev builds, but that's not really an issue. 
This patch makes no assumptions about the build profile we're running under, that's just an intrinsic property of rust-embed.

Tl;dr: this should shave off about 30Mb of memory usage and a fair chunk (~30ms) of startup time.

Release Notes:
- Improved startup time and memory usage.
This commit is contained in:
Piotr Osiewicz 2024-01-29 10:06:57 +01:00 committed by GitHub
parent e22ffb6740
commit 8fbc88b708
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 36 additions and 34 deletions

View File

@ -16,7 +16,7 @@ use rust_embed::RustEmbed;
pub struct Assets; pub struct Assets;
impl AssetSource for Assets { impl AssetSource for Assets {
fn load(&self, path: &str) -> Result<std::borrow::Cow<[u8]>> { fn load(&self, path: &str) -> Result<std::borrow::Cow<'static, [u8]>> {
Self::get(path) Self::get(path)
.map(|f| f.data) .map(|f| f.data)
.ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path)) .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))

View File

@ -11,14 +11,14 @@ use std::{
/// A source of assets for this app to use. /// A source of assets for this app to use.
pub trait AssetSource: 'static + Send + Sync { pub trait AssetSource: 'static + Send + Sync {
/// Load the given asset from the source path. /// Load the given asset from the source path.
fn load(&self, path: &str) -> Result<Cow<[u8]>>; fn load(&self, path: &str) -> Result<Cow<'static, [u8]>>;
/// List the assets at the given path. /// List the assets at the given path.
fn list(&self, path: &str) -> Result<Vec<SharedString>>; fn list(&self, path: &str) -> Result<Vec<SharedString>>;
} }
impl AssetSource for () { impl AssetSource for () {
fn load(&self, path: &str) -> Result<Cow<[u8]>> { fn load(&self, path: &str) -> Result<Cow<'static, [u8]>> {
Err(anyhow!( Err(anyhow!(
"get called on empty asset provider with \"{}\"", "get called on empty asset provider with \"{}\"",
path path

View File

@ -204,7 +204,7 @@ pub trait PlatformDispatcher: Send + Sync {
} }
pub(crate) trait PlatformTextSystem: Send + Sync { pub(crate) trait PlatformTextSystem: Send + Sync {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()>; fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()>;
fn all_font_names(&self) -> Vec<String>; fn all_font_names(&self) -> Vec<String>;
fn all_font_families(&self) -> Vec<String>; fn all_font_families(&self) -> Vec<String>;
fn font_id(&self, descriptor: &Font) -> Result<FontId>; fn font_id(&self, descriptor: &Font) -> Result<FontId>;

View File

@ -34,7 +34,7 @@ use pathfinder_geometry::{
vector::{Vector2F, Vector2I}, vector::{Vector2F, Vector2I},
}; };
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{char, cmp, convert::TryFrom, ffi::c_void, sync::Arc}; use std::{borrow::Cow, char, cmp, convert::TryFrom, ffi::c_void, sync::Arc};
use super::open_type; use super::open_type;
@ -74,7 +74,7 @@ impl Default for MacTextSystem {
} }
impl PlatformTextSystem for MacTextSystem { impl PlatformTextSystem for MacTextSystem {
fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> { fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
self.0.write().add_fonts(fonts) self.0.write().add_fonts(fonts)
} }
@ -183,12 +183,23 @@ impl PlatformTextSystem for MacTextSystem {
} }
impl MacTextSystemState { impl MacTextSystemState {
fn add_fonts(&mut self, fonts: &[Arc<Vec<u8>>]) -> Result<()> { fn add_fonts(&mut self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
self.memory_source.add_fonts( let fonts = fonts
fonts .into_iter()
.iter() .map(|bytes| match bytes {
.map(|bytes| Handle::from_memory(bytes.clone(), 0)), Cow::Borrowed(embedded_font) => {
)?; let data_provider = unsafe {
core_graphics::data_provider::CGDataProvider::from_slice(embedded_font)
};
let font = core_graphics::font::CGFont::from_data_provider(data_provider)
.map_err(|_| anyhow!("Could not load an embedded font."))?;
let font = font_kit::loaders::core_text::Font::from_core_graphics_font(font);
Ok(Handle::from_native(&font))
}
Cow::Owned(bytes) => Ok(Handle::from_memory(Arc::new(bytes), 0)),
})
.collect::<Result<Vec<_>>>()?;
self.memory_source.add_fonts(fonts.into_iter())?;
Ok(()) Ok(())
} }

View File

@ -19,6 +19,7 @@ use itertools::Itertools;
use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use std::{ use std::{
borrow::Cow,
cmp, cmp,
fmt::{Debug, Display, Formatter}, fmt::{Debug, Display, Formatter},
hash::{Hash, Hasher}, hash::{Hash, Hasher},
@ -85,7 +86,7 @@ impl TextSystem {
} }
/// Add a font's data to the text system. /// Add a font's data to the text system.
pub fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> { pub fn add_fonts(&self, fonts: Vec<Cow<'static, [u8]>>) -> Result<()> {
self.platform_text_system.add_fonts(fonts) self.platform_text_system.add_fonts(fonts)
} }

View File

@ -15,7 +15,7 @@ use rust_embed::RustEmbed;
pub struct Assets; pub struct Assets;
impl AssetSource for Assets { impl AssetSource for Assets {
fn load(&self, path: &str) -> Result<Cow<[u8]>> { fn load(&self, path: &str) -> Result<Cow<'static, [u8]>> {
Self::get(path) Self::get(path)
.map(|f| f.data) .map(|f| f.data)
.ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path)) .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))

View File

@ -2,8 +2,6 @@ mod assets;
mod stories; mod stories;
mod story_selector; mod story_selector;
use std::sync::Arc;
use clap::Parser; use clap::Parser;
use dialoguer::FuzzySelect; use dialoguer::FuzzySelect;
use gpui::{ use gpui::{
@ -128,10 +126,10 @@ fn load_embedded_fonts(cx: &AppContext) -> gpui::Result<()> {
let mut embedded_fonts = Vec::new(); let mut embedded_fonts = Vec::new();
for font_path in font_paths { for font_path in font_paths {
if font_path.ends_with(".ttf") { if font_path.ends_with(".ttf") {
let font_bytes = cx.asset_source().load(&font_path)?.to_vec(); let font_bytes = cx.asset_source().load(&font_path)?;
embedded_fonts.push(Arc::from(font_bytes)); embedded_fonts.push(font_bytes);
} }
} }
cx.text_system().add_fonts(&embedded_fonts) cx.text_system().add_fonts(embedded_fonts)
} }

View File

@ -11,7 +11,7 @@ use rust_embed::RustEmbed;
pub struct Assets; pub struct Assets;
impl AssetSource for Assets { impl AssetSource for Assets {
fn load(&self, path: &str) -> Result<Cow<[u8]>> { fn load(&self, path: &str) -> Result<Cow<'static, [u8]>> {
Self::get(path) Self::get(path)
.map(|f| f.data) .map(|f| f.data)
.ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path)) .ok_or_else(|| anyhow!("could not find asset at path \"{}\"", path))

View File

@ -815,14 +815,14 @@ fn load_embedded_fonts(cx: &AppContext) {
} }
scope.spawn(async { scope.spawn(async {
let font_bytes = asset_source.load(font_path).unwrap().to_vec(); let font_bytes = asset_source.load(font_path).unwrap();
embedded_fonts.lock().push(Arc::from(font_bytes)); embedded_fonts.lock().push(font_bytes);
}); });
} }
})); }));
cx.text_system() cx.text_system()
.add_fonts(&embedded_fonts.into_inner()) .add_fonts(embedded_fonts.into_inner())
.unwrap(); .unwrap();
} }

View File

@ -2676,17 +2676,9 @@ mod tests {
#[gpui::test] #[gpui::test]
fn test_bundled_settings_and_themes(cx: &mut AppContext) { fn test_bundled_settings_and_themes(cx: &mut AppContext) {
cx.text_system() cx.text_system()
.add_fonts(&[ .add_fonts(vec![
Assets Assets.load("fonts/zed-sans/zed-sans-extended.ttf").unwrap(),
.load("fonts/zed-sans/zed-sans-extended.ttf") Assets.load("fonts/zed-mono/zed-mono-extended.ttf").unwrap(),
.unwrap()
.to_vec()
.into(),
Assets
.load("fonts/zed-mono/zed-mono-extended.ttf")
.unwrap()
.to_vec()
.into(),
]) ])
.unwrap(); .unwrap();
let themes = ThemeRegistry::default(); let themes = ThemeRegistry::default();