mirror of
https://github.com/typst/typst
synced 2025-05-21 12:35:29 +08:00
[WIP] Run iterations in parallel
This commit is contained in:
parent
7fa86eed0e
commit
59b183fbcb
@ -128,13 +128,13 @@ pub struct Sink {
|
|||||||
/// because the introspector is not yet ready. We first ignore that and
|
/// because the introspector is not yet ready. We first ignore that and
|
||||||
/// proceed with empty content and only if the error remains by the end
|
/// proceed with empty content and only if the error remains by the end
|
||||||
/// of the last iteration, we promote it.
|
/// of the last iteration, we promote it.
|
||||||
delayed: EcoVec<SourceDiagnostic>,
|
pub(crate) delayed: EcoVec<SourceDiagnostic>,
|
||||||
/// Warnings emitted during iteration.
|
/// Warnings emitted during iteration.
|
||||||
warnings: EcoVec<SourceDiagnostic>,
|
pub(crate) warnings: EcoVec<SourceDiagnostic>,
|
||||||
/// Hashes of all warning's spans and messages for warning deduplication.
|
/// Hashes of all warning's spans and messages for warning deduplication.
|
||||||
warnings_set: HashSet<u128>,
|
warnings_set: HashSet<u128>,
|
||||||
/// A sequence of traced values for a span.
|
/// A sequence of traced values for a span.
|
||||||
values: EcoVec<(Value, Option<Styles>)>,
|
pub(crate) values: EcoVec<(Value, Option<Styles>)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sink {
|
impl Sink {
|
||||||
@ -186,7 +186,7 @@ impl Sink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Extend from another sink.
|
/// Extend from another sink.
|
||||||
fn extend(
|
pub fn extend(
|
||||||
&mut self,
|
&mut self,
|
||||||
delayed: EcoVec<SourceDiagnostic>,
|
delayed: EcoVec<SourceDiagnostic>,
|
||||||
warnings: EcoVec<SourceDiagnostic>,
|
warnings: EcoVec<SourceDiagnostic>,
|
||||||
|
@ -38,21 +38,25 @@ pub struct Introspector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Introspector {
|
impl Introspector {
|
||||||
/// Applies new frames in-place, reusing the existing allocations.
|
/// Build a new introspector for a page collection.
|
||||||
#[typst_macros::time(name = "introspect")]
|
#[typst_macros::time(name = "introspect")]
|
||||||
pub fn rebuild(&mut self, pages: &[Page]) {
|
pub fn new(pages: &[Page]) -> Self {
|
||||||
self.pages = pages.len();
|
let mut this = Self {
|
||||||
self.elems.clear();
|
pages: pages.len(),
|
||||||
self.labels.clear();
|
elems: IndexMap::new(),
|
||||||
self.keys.clear();
|
labels: HashMap::new(),
|
||||||
self.page_numberings.clear();
|
keys: HashMap::new(),
|
||||||
self.queries.clear();
|
page_numberings: Vec::with_capacity(pages.len()),
|
||||||
|
queries: QueryCache::default(),
|
||||||
|
};
|
||||||
|
|
||||||
for (i, page) in pages.iter().enumerate() {
|
for (i, page) in pages.iter().enumerate() {
|
||||||
let page_nr = NonZeroUsize::new(1 + i).unwrap();
|
let page_nr = NonZeroUsize::new(1 + i).unwrap();
|
||||||
self.extract(&page.frame, page_nr, Transform::identity());
|
this.extract(&page.frame, page_nr, Transform::identity());
|
||||||
self.page_numberings.push(page.numbering.clone());
|
this.page_numberings.push(page.numbering.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract metadata from a frame.
|
/// Extract metadata from a frame.
|
||||||
@ -328,10 +332,6 @@ impl QueryCache {
|
|||||||
fn insert(&self, hash: u128, output: EcoVec<Content>) {
|
fn insert(&self, hash: u128, output: EcoVec<Content>) {
|
||||||
self.0.write().unwrap().insert(hash, output);
|
self.0.write().unwrap().insert(hash, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear(&mut self) {
|
|
||||||
self.0.get_mut().unwrap().clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for QueryCache {
|
impl Clone for QueryCache {
|
||||||
|
@ -58,15 +58,17 @@ pub use typst_utils as utils;
|
|||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::ops::{Deref, Range};
|
use std::ops::{Deref, Range};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use comemo::{Track, Tracked, Validate};
|
use comemo::{Track, Tracked, Validate};
|
||||||
use ecow::{EcoString, EcoVec};
|
use ecow::{EcoString, EcoVec};
|
||||||
|
use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
|
||||||
use typst_timing::{timed, TimingScope};
|
use typst_timing::{timed, TimingScope};
|
||||||
|
|
||||||
use crate::diag::{warning, FileResult, SourceDiagnostic, SourceResult, Warned};
|
use crate::diag::{warning, FileResult, SourceDiagnostic, SourceResult, Warned};
|
||||||
use crate::engine::{Engine, Route, Sink, Traced};
|
use crate::engine::{Engine, Route, Sink, Traced};
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
Array, Bytes, Datetime, Dict, Module, Scope, StyleChain, Styles, Value,
|
Array, Bytes, Content, Datetime, Dict, Module, Scope, StyleChain, Styles, Value,
|
||||||
};
|
};
|
||||||
use crate::introspection::Introspector;
|
use crate::introspection::Introspector;
|
||||||
use crate::layout::{Alignment, Dir};
|
use crate::layout::{Alignment, Dir};
|
||||||
@ -105,8 +107,8 @@ fn compile_inner(
|
|||||||
traced: Tracked<Traced>,
|
traced: Tracked<Traced>,
|
||||||
sink: &mut Sink,
|
sink: &mut Sink,
|
||||||
) -> SourceResult<Document> {
|
) -> SourceResult<Document> {
|
||||||
let library = world.library();
|
// Yes, this is a hack. But it's ok, this is just an experimental branch.
|
||||||
let styles = StyleChain::new(&library.styles);
|
static INTROSPECTORS: Mutex<Vec<Arc<Introspector>>> = Mutex::new(Vec::new());
|
||||||
|
|
||||||
// First evaluate the main source file into a module.
|
// First evaluate the main source file into a module.
|
||||||
let content = crate::eval::eval(
|
let content = crate::eval::eval(
|
||||||
@ -118,36 +120,57 @@ fn compile_inner(
|
|||||||
)?
|
)?
|
||||||
.content();
|
.content();
|
||||||
|
|
||||||
|
// If we have introspectors from previous compilations, we can speed up
|
||||||
|
// compilation with a small speculative trick: Since quite often the
|
||||||
|
// iterations we go through will be exactly the same ones as previously with
|
||||||
|
// no relevant introspector changes, we can run all iterations in parallel
|
||||||
|
// using the previous introspectors.
|
||||||
|
let mut speculation = INTROSPECTORS
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.clone()
|
||||||
|
.par_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(iter, introspector)| {
|
||||||
|
let mut sink = Sink::new();
|
||||||
|
(layout(world, &introspector, traced, &mut sink, &content, iter), sink)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter();
|
||||||
|
|
||||||
let mut iter = 0;
|
let mut iter = 0;
|
||||||
let mut document = Document::default();
|
let mut introspector = Arc::new(Introspector::new(&[]));
|
||||||
|
let mut introspectors = vec![];
|
||||||
|
|
||||||
// Relayout until all introspections stabilize.
|
// Relayout until all introspections stabilize.
|
||||||
// If that doesn't happen within five attempts, we give up.
|
// If that doesn't happen within five attempts, we give up.
|
||||||
loop {
|
let document = loop {
|
||||||
// The name of the iterations for timing scopes.
|
// Save this iteration's introspector.
|
||||||
const ITER_NAMES: &[&str] =
|
introspectors.push(introspector.clone());
|
||||||
&["layout (1)", "layout (2)", "layout (3)", "layout (4)", "layout (5)"];
|
|
||||||
let _scope = TimingScope::new(ITER_NAMES[iter], None);
|
|
||||||
|
|
||||||
// Clear delayed errors.
|
// Clear delayed errors.
|
||||||
sink.delayed();
|
sink.delayed();
|
||||||
|
|
||||||
let constraint = <Introspector as Validate>::Constraint::new();
|
// Try to use a speculative result if it was valid for the
|
||||||
let mut engine = Engine {
|
// real current introspector. If not, layout as usual.
|
||||||
world,
|
let (result, constraint) = if let Some((pair, subsink)) =
|
||||||
introspector: document.introspector.track_with(&constraint),
|
speculation.next().filter(|((_, constraint), _)| {
|
||||||
traced,
|
timed!("check speculation", introspector.validate(&constraint))
|
||||||
sink: sink.track_mut(),
|
}) {
|
||||||
route: Route::default(),
|
sink.extend(subsink.delayed, subsink.warnings, subsink.values);
|
||||||
|
pair
|
||||||
|
} else {
|
||||||
|
layout(world, &introspector, traced, sink, &content, iter)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Layout!
|
let document = result?;
|
||||||
document = content.layout_document(&mut engine, styles)?;
|
introspector = document.introspector.clone();
|
||||||
document.introspector.rebuild(&document.pages);
|
|
||||||
|
// Bump the iteration.
|
||||||
iter += 1;
|
iter += 1;
|
||||||
|
|
||||||
if timed!("check stabilized", document.introspector.validate(&constraint)) {
|
if timed!("check stabilized", introspector.validate(&constraint)) {
|
||||||
break;
|
break document;
|
||||||
}
|
}
|
||||||
|
|
||||||
if iter >= 5 {
|
if iter >= 5 {
|
||||||
@ -155,9 +178,12 @@ fn compile_inner(
|
|||||||
Span::detached(), "layout did not converge within 5 attempts";
|
Span::detached(), "layout did not converge within 5 attempts";
|
||||||
hint: "check if any states or queries are updating themselves"
|
hint: "check if any states or queries are updating themselves"
|
||||||
));
|
));
|
||||||
break;
|
break document;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// Save the introspectors for next time.
|
||||||
|
*INTROSPECTORS.lock().unwrap() = introspectors;
|
||||||
|
|
||||||
// Promote delayed errors.
|
// Promote delayed errors.
|
||||||
let delayed = sink.delayed();
|
let delayed = sink.delayed();
|
||||||
@ -168,6 +194,39 @@ fn compile_inner(
|
|||||||
Ok(document)
|
Ok(document)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Run a single layout iteration with the given introspector.
|
||||||
|
///
|
||||||
|
/// Returns the document and the introspector constraints for it.
|
||||||
|
fn layout(
|
||||||
|
world: Tracked<dyn World + '_>,
|
||||||
|
introspector: &Introspector,
|
||||||
|
traced: Tracked<Traced>,
|
||||||
|
sink: &mut Sink,
|
||||||
|
content: &Content,
|
||||||
|
iter: usize,
|
||||||
|
) -> (SourceResult<Document>, <Introspector as Validate>::Constraint) {
|
||||||
|
// The name of the iterations for timing scopes.
|
||||||
|
const ITERS: &[&str] =
|
||||||
|
&["layout (1)", "layout (2)", "layout (3)", "layout (4)", "layout (5)"];
|
||||||
|
let _scope = TimingScope::new(ITERS[iter], None);
|
||||||
|
|
||||||
|
let constraint = <Introspector as Validate>::Constraint::new();
|
||||||
|
let mut engine = Engine {
|
||||||
|
world,
|
||||||
|
introspector: introspector.track_with(&constraint),
|
||||||
|
traced,
|
||||||
|
sink: sink.track_mut(),
|
||||||
|
route: Route::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let library = world.library();
|
||||||
|
let styles = StyleChain::new(&library.styles);
|
||||||
|
|
||||||
|
// Layout!
|
||||||
|
let document = content.layout_document(&mut engine, styles);
|
||||||
|
(document, constraint)
|
||||||
|
}
|
||||||
|
|
||||||
/// Deduplicate diagnostics.
|
/// Deduplicate diagnostics.
|
||||||
fn deduplicate(mut diags: EcoVec<SourceDiagnostic>) -> EcoVec<SourceDiagnostic> {
|
fn deduplicate(mut diags: EcoVec<SourceDiagnostic>) -> EcoVec<SourceDiagnostic> {
|
||||||
let mut unique = HashSet::new();
|
let mut unique = HashSet::new();
|
||||||
@ -189,7 +248,7 @@ fn deduplicate(mut diags: EcoVec<SourceDiagnostic>) -> EcoVec<SourceDiagnostic>
|
|||||||
/// information on when something can change. For example, fonts typically don't
|
/// information on when something can change. For example, fonts typically don't
|
||||||
/// change and can thus even be cached across multiple compilations (for
|
/// change and can thus even be cached across multiple compilations (for
|
||||||
/// long-running applications like `typst watch`). Source files on the other
|
/// long-running applications like `typst watch`). Source files on the other
|
||||||
/// hand can change and should thus be cleared after each compilation. Advanced
|
/// hand can change and might thus be cleared after each compilation. Advanced
|
||||||
/// clients like language servers can also retain the source files and
|
/// clients like language servers can also retain the source files and
|
||||||
/// [edit](Source::edit) them in-place to benefit from better incremental
|
/// [edit](Source::edit) them in-place to benefit from better incremental
|
||||||
/// performance.
|
/// performance.
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
|
|
||||||
use crate::diag::{bail, HintedStrResult, SourceResult};
|
use crate::diag::{bail, HintedStrResult, SourceResult};
|
||||||
@ -107,13 +109,15 @@ impl Packed<DocumentElem> {
|
|||||||
pages.extend(result?.finalize(engine, &mut page_counter)?);
|
pages.extend(result?.finalize(engine, &mut page_counter)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let introspector = Introspector::new(&pages);
|
||||||
|
|
||||||
Ok(Document {
|
Ok(Document {
|
||||||
pages,
|
pages,
|
||||||
title: DocumentElem::title_in(styles).map(|content| content.plain_text()),
|
title: DocumentElem::title_in(styles).map(|content| content.plain_text()),
|
||||||
author: DocumentElem::author_in(styles).0,
|
author: DocumentElem::author_in(styles).0,
|
||||||
keywords: DocumentElem::keywords_in(styles).0,
|
keywords: DocumentElem::keywords_in(styles).0,
|
||||||
date: DocumentElem::date_in(styles),
|
date: DocumentElem::date_in(styles),
|
||||||
introspector: Introspector::default(),
|
introspector: Arc::new(introspector),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,7 +158,7 @@ pub struct Document {
|
|||||||
/// The document's creation date.
|
/// The document's creation date.
|
||||||
pub date: Smart<Option<Datetime>>,
|
pub date: Smart<Option<Datetime>>,
|
||||||
/// Provides the ability to execute queries on the document.
|
/// Provides the ability to execute queries on the document.
|
||||||
pub introspector: Introspector,
|
pub introspector: Arc<Introspector>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user