perf: parallelize font loading and sync wait it (#1470)

* x

* test memory fonts iter

* perf: impl par
This commit is contained in:
Myriad-Dreamin 2025-03-09 22:29:33 +08:00 committed by GitHub
parent 987758869f
commit 2ec7a26420
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 180 additions and 42 deletions

46
Cargo.lock generated
View file

@ -636,6 +636,12 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "condtype"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf0a07a401f374238ab8e2f11a104d2851bf9ce711ec69804834de8af45c7af"
[[package]]
name = "confy"
version = "0.6.1"
@ -1006,6 +1012,31 @@ dependencies = [
"syn 2.0.98",
]
[[package]]
name = "divan"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0583193020b29b03682d8d33bb53a5b0f50df6daacece12ca99b904cfdcb8c4"
dependencies = [
"cfg-if",
"clap",
"condtype",
"divan-macros",
"libc",
"regex-lite",
]
[[package]]
name = "divan-macros"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.98",
]
[[package]]
name = "downcast-rs"
version = "1.2.1"
@ -3003,6 +3034,12 @@ dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-lite"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
[[package]]
name = "regex-syntax"
version = "0.8.5"
@ -4040,6 +4077,14 @@ version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dab89c3820bd63478ff48eb22413c7d2b4afc280aff271a6706ec7b1dc6ab18"
[[package]]
name = "tinymist-bench-font-load"
version = "0.13.4"
dependencies = [
"divan",
"tinymist",
]
[[package]]
name = "tinymist-core"
version = "0.13.4"
@ -4262,6 +4307,7 @@ dependencies = [
"js-sys",
"log",
"parking_lot",
"rayon",
"reqwest",
"rpds",
"serde",

View file

@ -12,7 +12,7 @@ rust-version = "1.83"
[workspace]
resolver = "2"
members = ["crates/*", "tests"]
members = ["benches/*", "crates/*", "tests"]
[workspace.dependencies]

View file

@ -0,0 +1,21 @@
[package]
name = "tinymist-bench-font-load"
description = "Font loading bench for tinymist."
authors.workspace = true
version.workspace = true
license.workspace = true
edition.workspace = true
homepage.workspace = true
repository.workspace = true
[dependencies]
divan.workspace = true
tinymist.workspace = true
[[bench]]
name = "tinymist-bench-font-load"
path = "src/load.rs"
harness = false
[features]
the-thesis = []

View file

@ -0,0 +1,39 @@
use std::sync::Arc;
use tinymist::{project::LspUniverseBuilder, Config};
fn main() {
// initialize global variables
// Run registered benchmarks.
divan::main();
}
// Checks font loading performance of embedded fonts
#[divan::bench]
fn load_embedded() {
let _embedded_fonts = Arc::new(LspUniverseBuilder::only_embedded_fonts().unwrap());
}
// Checks font loading performance of system fonts
#[divan::bench]
fn load_system() {
let config = Config::default();
let _fonts = config.compile.determine_fonts();
}
/*
Without Parallelization
Timer precision: 17 ns
tinymist_bench_font_load fastest slowest median mean samples iters
load_embedded 1.167 ms 1.697 ms 1.176 ms 1.188 ms 100 100
load_system 111.8 ms 123 ms 113.6 ms 114.3 ms 100 100
With Parallelization
Timer precision: 17 ns
tinymist_bench_font_load fastest slowest median mean samples iters
load_embedded 130.8 µs 1.164 ms 157 µs 170.3 µs 100 100
load_system 14.44 ms 18.22 ms 15.37 ms 15.54 ms 100 100
*/

View file

@ -27,6 +27,7 @@ hex.workspace = true
js-sys = { workspace = true, optional = true }
log.workspace = true
parking_lot.workspace = true
rayon.workspace = true
reqwest = { workspace = true, optional = true }
rpds.workspace = true
serde.workspace = true

View file

@ -7,6 +7,7 @@ use std::{
};
use fontdb::Database;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use sha2::{Digest, Sha256};
use tinymist_std::debug_loc::{DataSource, MemoryDataSource};
use tinymist_std::error::prelude::*;
@ -134,12 +135,12 @@ impl SystemFontSearcher {
self.flush();
// Source3: add the fonts in memory.
for font_data in opts.with_embedded_fonts {
self.add_memory_font(match font_data {
self.add_memory_fonts(opts.with_embedded_fonts.into_par_iter().map(|font_data| {
match font_data {
Cow::Borrowed(data) => Bytes::new(data),
Cow::Owned(data) => Bytes::new(data),
});
}
}
}));
Ok(())
}
@ -186,7 +187,8 @@ impl SystemFontSearcher {
use fontdb::Source;
use tinymist_std::debug_loc::FsDataSource;
for face in self.db.faces() {
let face = self.db.faces().collect::<Vec<_>>();
let info = face.into_par_iter().map(|face| {
let path = match &face.source {
Source::File(path) | Source::SharedFile(path, _) => path,
// We never add binary sources to the database, so there
@ -199,20 +201,26 @@ impl SystemFontSearcher {
.with_face_data(face.id, FontInfo::new)
.expect("database must contain this font");
// eprintln!("searched font: {idx} {:?}", path);
info.map(|info| {
let slot = FontSlot::new_boxed(LazyBufferFontLoader::new(
LazyFile::new(path.clone()),
face.index,
))
.describe(DataSource::Fs(FsDataSource {
path: path.to_str().unwrap_or_default().to_owned(),
}));
if let Some(info) = info {
self.book.push(info);
self.fonts.push(
FontSlot::new_boxed(LazyBufferFontLoader::new(
LazyFile::new(path.clone()),
face.index,
))
.describe(DataSource::Fs(FsDataSource {
path: path.to_str().unwrap_or_default().to_owned(),
})),
);
}
(info, slot)
})
});
for face in info.collect::<Vec<_>>() {
let Some((info, slot)) = face else {
continue;
};
self.book.push(info);
self.fonts.push(slot);
}
self.db = Database::new();
@ -238,6 +246,41 @@ impl SystemFontSearcher {
}
}
// /// Add an in-memory font.
// pub fn add_memory_font(&mut self, data: Bytes) {
// self.add_memory_fonts([data].into_par_iter());
// }
/// Adds in-memory fonts.
pub fn add_memory_fonts(&mut self, data: impl ParallelIterator<Item = Bytes>) {
if !self.db.is_empty() {
panic!("dirty font search state, please flush the searcher before adding memory fonts");
}
let loaded = data.flat_map(|data| {
FontInfo::iter(&data)
.enumerate()
.map(|(index, info)| {
(
info,
FontSlot::new_boxed(BufferFontLoader {
buffer: Some(data.clone()),
index: index as u32,
})
.describe(DataSource::Memory(MemoryDataSource {
name: "<memory>".to_owned(),
})),
)
})
.collect::<Vec<_>>()
});
for (info, slot) in loaded.collect::<Vec<_>>() {
self.book.push(info);
self.fonts.push(slot);
}
}
pub fn search_system(&mut self) {
self.db.load_system_fonts();
}

View file

@ -22,7 +22,7 @@ use tinymist_query::{CompletionFeat, PositionEncoding};
use tinymist_render::PeriscopeArgs;
use tinymist_task::ExportTarget;
use typst::foundations::IntoValue;
use typst_shim::utils::{Deferred, LazyHash};
use typst_shim::utils::LazyHash;
// todo: svelte-language-server responds to a Goto Definition request with
// LocationLink[] even if the client does not report the
@ -579,7 +579,7 @@ pub struct CompileConfig {
/// Specifies the font paths
pub font_paths: Vec<PathBuf>,
/// Computed fonts based on configuration.
pub fonts: OnceCell<Derived<Deferred<Arc<TinymistFontResolver>>>>,
pub fonts: OnceCell<Derived<Arc<TinymistFontResolver>>>,
/// Notify the compile status to the editor.
pub notify_status: bool,
/// Enable periscope document in hover.
@ -750,17 +750,17 @@ impl CompileConfig {
}
/// Determines the font resolver.
pub fn determine_fonts(&self) -> Deferred<Arc<TinymistFontResolver>> {
pub fn determine_fonts(&self) -> Arc<TinymistFontResolver> {
// todo: on font resolving failure, downgrade to a fake font book
let font = || {
let opts = self.determine_font_opts();
log::info!("creating SharedFontResolver with {opts:?}");
Derived(Deferred::new(|| {
Derived(
crate::project::LspUniverseBuilder::resolve_fonts(opts)
.map(Arc::new)
.expect("failed to create font book")
}))
.expect("failed to create font book"),
)
};
self.fonts.get_or_init(font).clone().0
}

View file

@ -189,17 +189,11 @@ impl ServerState {
log::info!("ServerState: creating ProjectState, entry: {entry:?}, inputs: {inputs:?}");
// todo: never fail?
let embedded_fonts = Arc::new(LspUniverseBuilder::only_embedded_fonts().unwrap());
let fonts = config.compile.determine_fonts();
let package_registry =
LspUniverseBuilder::resolve_package(cert_path.clone(), Some(&package));
let verse = LspUniverseBuilder::build(
entry,
export_target,
inputs,
embedded_fonts,
package_registry,
);
let verse =
LspUniverseBuilder::build(entry, export_target, inputs, fonts, package_registry);
// todo: unify filesystem watcher
let (dep_tx, dep_rx) = mpsc::unbounded_channel();
@ -220,14 +214,6 @@ impl ServerState {
},
);
// Delayed Loads fonts
let font_client = client.clone();
let font_resolver = config.compile.determine_fonts();
client.handle.spawn_blocking(move || {
// Refresh fonts
font_client.send_event(LspInterrupt::Font(font_resolver.wait().clone()));
});
ProjectState {
compiler,
preview: handle.preview.clone(),

View file

@ -22,6 +22,8 @@
"docs:rs": "cargo doc --workspace --document-private-items --no-deps",
"lint": "eslint editors/vscode/src",
"lint-fix": "eslint editors/vscode/src --fix",
"benches": "cargo bench --workspace",
"bench": "cargo bench --workspace --bench",
"test:grammar": "cd syntaxes/textmate && yarn run test",
"build:typlite": "cargo build --bin typlite",
"typlite": "target/debug/typlite",