Basic multi-threading (#4366)

This commit is contained in:
Laurenz 2024-06-10 15:28:40 +02:00 committed by GitHub
parent a68a241570
commit 7fa86eed0e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 163 additions and 56 deletions

View File

@ -217,6 +217,11 @@ pub struct SharedArgs {
/// Arguments related to storage of packages in the system /// Arguments related to storage of packages in the system
#[clap(flatten)] #[clap(flatten)]
pub package_storage_args: PackageStorageArgs, pub package_storage_args: PackageStorageArgs,
/// Number of parallel jobs spawned during compilation,
/// defaults to number of CPUs.
#[clap(long, short)]
pub jobs: Option<usize>,
} }
/// Arguments related to where packages are stored in the system. /// Arguments related to where packages are stored in the system.

View File

@ -56,6 +56,11 @@ pub struct SystemWorld {
impl SystemWorld { impl SystemWorld {
/// Create a new system world. /// Create a new system world.
pub fn new(command: &SharedArgs) -> Result<Self, WorldCreationError> { pub fn new(command: &SharedArgs) -> Result<Self, WorldCreationError> {
// Set up the thread pool.
if let Some(jobs) = command.jobs {
rayon::ThreadPoolBuilder::new().num_threads(jobs).build_global().ok();
}
// Resolve the system-global input path. // Resolve the system-global input path.
let input = match &command.input { let input = match &command.input {
Input::Stdin => None, Input::Stdin => None,

View File

@ -145,6 +145,7 @@ pub fn deferred_image(image: Image) -> (Deferred<EncodedImage>, Option<ColorSpac
/// whether the image has color. /// whether the image has color.
/// ///
/// Skips the alpha channel as that's encoded separately. /// Skips the alpha channel as that's encoded separately.
#[typst_macros::time(name = "encode raster image")]
fn encode_raster_image(image: &RasterImage) -> (Vec<u8>, Filter, bool) { fn encode_raster_image(image: &RasterImage) -> (Vec<u8>, Filter, bool) {
let dynamic = image.dynamic(); let dynamic = image.dynamic();
let channel_count = dynamic.color().channel_count(); let channel_count = dynamic.color().channel_count();
@ -169,6 +170,7 @@ fn encode_raster_image(image: &RasterImage) -> (Vec<u8>, Filter, bool) {
} }
/// Encode an image's alpha channel if present. /// Encode an image's alpha channel if present.
#[typst_macros::time(name = "encode alpha")]
fn encode_alpha(raster: &RasterImage) -> (Vec<u8>, Filter) { fn encode_alpha(raster: &RasterImage) -> (Vec<u8>, Filter) {
let pixels: Vec<_> = raster let pixels: Vec<_> = raster
.dynamic() .dynamic()
@ -179,6 +181,7 @@ fn encode_alpha(raster: &RasterImage) -> (Vec<u8>, Filter) {
} }
/// Encode an SVG into a chunk of PDF objects. /// Encode an SVG into a chunk of PDF objects.
#[typst_macros::time(name = "encode svg")]
fn encode_svg(svg: &SvgImage) -> (Chunk, Ref) { fn encode_svg(svg: &SvgImage) -> (Chunk, Ref) {
svg2pdf::to_chunk(svg.tree(), svg2pdf::ConversionOptions::default()) svg2pdf::to_chunk(svg.tree(), svg2pdf::ConversionOptions::default())
} }

View File

@ -5,6 +5,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
use comemo::{Track, Tracked, TrackedMut, Validate}; use comemo::{Track, Tracked, TrackedMut, Validate};
use ecow::EcoVec; use ecow::EcoVec;
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
use crate::diag::{SourceDiagnostic, SourceResult}; use crate::diag::{SourceDiagnostic, SourceResult};
use crate::foundations::{Styles, Value}; use crate::foundations::{Styles, Value};
@ -44,6 +45,46 @@ impl Engine<'_> {
} }
} }
} }
/// Runs tasks on the engine in parallel.
pub fn parallelize<P, I, T, U, F>(&mut self, iter: P, f: F) -> impl Iterator<Item = U>
where
P: IntoIterator<IntoIter = I>,
I: Iterator<Item = T>,
T: Send,
U: Send,
F: Fn(&mut Engine, T) -> U + Send + Sync,
{
let Engine { world, introspector, traced, ref route, .. } = *self;
// We collect into a vector and then call `into_par_iter` instead of
// using `par_bridge` because it does not retain the ordering.
let work: Vec<T> = iter.into_iter().collect();
// Work in parallel.
let mut pairs: Vec<(U, Sink)> = Vec::with_capacity(work.len());
work.into_par_iter()
.map(|value| {
let mut sink = Sink::new();
let mut engine = Engine {
world,
introspector,
traced,
sink: sink.track_mut(),
route: route.clone(),
};
(f(&mut engine, value), sink)
})
.collect_into_vec(&mut pairs);
// Apply the subsinks to the outer sink.
for (_, sink) in &mut pairs {
let sink = std::mem::take(sink);
self.sink.extend(sink.delayed, sink.warnings, sink.values);
}
pairs.into_iter().map(|(output, _)| output)
}
} }
/// May hold a span that is currently under inspection. /// May hold a span that is currently under inspection.
@ -143,6 +184,22 @@ impl Sink {
self.values.push((value, styles)); self.values.push((value, styles));
} }
} }
/// Extend from another sink.
fn extend(
&mut self,
delayed: EcoVec<SourceDiagnostic>,
warnings: EcoVec<SourceDiagnostic>,
values: EcoVec<(Value, Option<Styles>)>,
) {
self.delayed.extend(delayed);
for warning in warnings {
self.warn(warning);
}
if let Some(remaining) = Self::MAX_VALUES.checked_sub(self.values.len()) {
self.values.extend(values.into_iter().take(remaining));
}
}
} }
/// The route the engine took during compilation. This is used to detect /// The route the engine took during compilation. This is used to detect

View File

@ -13,7 +13,7 @@ use crate::foundations::{
Packed, Resolve, Smart, StyleChain, Value, Packed, Resolve, Smart, StyleChain, Value,
}; };
use crate::introspection::{ use crate::introspection::{
Counter, CounterDisplayElem, CounterKey, Locator, ManualPageCounter, Counter, CounterDisplayElem, CounterKey, Locator, ManualPageCounter, SplitLocator,
}; };
use crate::layout::{ use crate::layout::{
Abs, AlignElem, Alignment, Axes, ColumnsElem, Dir, Frame, HAlignment, Length, Abs, AlignElem, Alignment, Axes, ColumnsElem, Dir, Frame, HAlignment, Length,
@ -348,14 +348,13 @@ impl Packed<PageElem> {
/// a fragment consisting of multiple frames, one per output page of this /// a fragment consisting of multiple frames, one per output page of this
/// page run. /// page run.
#[typst_macros::time(name = "page", span = self.span())] #[typst_macros::time(name = "page", span = self.span())]
pub fn layout( pub fn layout<'a>(
&self, &'a self,
engine: &mut Engine, engine: &mut Engine,
locator: Locator, locator: Locator<'a>,
styles: StyleChain, styles: StyleChain<'a>,
page_counter: &mut ManualPageCounter,
extend_to: Option<Parity>, extend_to: Option<Parity>,
) -> SourceResult<Vec<Page>> { ) -> SourceResult<PageLayout<'a>> {
let mut locator = locator.split(); let mut locator = locator.split();
// When one of the lengths is infinite the page fits its content along // When one of the lengths is infinite the page fits its content along
@ -382,14 +381,6 @@ impl Packed<PageElem> {
.resolve(styles) .resolve(styles)
.relative_to(size); .relative_to(size);
// Determine the binding.
let binding =
self.binding(styles)
.unwrap_or_else(|| match TextElem::dir_in(styles) {
Dir::LTR => Binding::Left,
_ => Binding::Right,
});
// Realize columns. // Realize columns.
let mut child = self.body().clone(); let mut child = self.body().clone();
let columns = self.columns(styles); let columns = self.columns(styles);
@ -405,27 +396,70 @@ impl Packed<PageElem> {
regions.root = true; regions.root = true;
// Layout the child. // Layout the child.
let mut frames = child let frames = child
.layout(engine, locator.next(&self.span()), styles, regions)? .layout(engine, locator.next(&self.span()), styles, regions)?
.into_frames(); .into_frames();
// Align the child to the pagebreak's parity. Ok(PageLayout {
// Check for page count after adding the pending frames page: self,
if extend_to locator,
.is_some_and(|p| !p.matches(page_counter.physical().get() + frames.len())) styles,
{ extend_to,
// Insert empty page after the current pages. area,
let size = area.map(Abs::is_finite).select(area, Size::zero()); margin,
frames.push(Frame::hard(size)); two_sided,
frames,
})
}
} }
let fill = self.fill(styles); /// A prepared layout of a page run that can be finalized with access to the
let foreground = self.foreground(styles); /// page counter.
let background = self.background(styles); pub struct PageLayout<'a> {
let header_ascent = self.header_ascent(styles); page: &'a Packed<PageElem>,
let footer_descent = self.footer_descent(styles); locator: SplitLocator<'a>,
let numbering = self.numbering(styles); styles: StyleChain<'a>,
let number_align = self.number_align(styles); extend_to: Option<Parity>,
area: Size,
margin: Sides<Abs>,
two_sided: bool,
frames: Vec<Frame>,
}
impl PageLayout<'_> {
/// Finalize the layout with access to the next page counter.
#[typst_macros::time(name = "finalize page", span = self.page.span())]
pub fn finalize(
mut self,
engine: &mut Engine,
page_counter: &mut ManualPageCounter,
) -> SourceResult<Vec<Page>> {
let styles = self.styles;
// Align the child to the pagebreak's parity.
// Check for page count after adding the pending frames
if self.extend_to.is_some_and(|p| {
!p.matches(page_counter.physical().get() + self.frames.len())
}) {
// Insert empty page after the current pages.
let size = self.area.map(Abs::is_finite).select(self.area, Size::zero());
self.frames.push(Frame::hard(size));
}
let fill = self.page.fill(styles);
let foreground = self.page.foreground(styles);
let background = self.page.background(styles);
let header_ascent = self.page.header_ascent(styles);
let footer_descent = self.page.footer_descent(styles);
let numbering = self.page.numbering(styles);
let number_align = self.page.number_align(styles);
let binding =
self.page
.binding(styles)
.unwrap_or_else(|| match TextElem::dir_in(styles) {
Dir::LTR => Binding::Left,
_ => Binding::Right,
});
// Construct the numbering (for header or footer). // Construct the numbering (for header or footer).
let numbering_marginal = numbering.as_ref().map(|numbering| { let numbering_marginal = numbering.as_ref().map(|numbering| {
@ -440,7 +474,7 @@ impl Packed<PageElem> {
both, both,
) )
.pack() .pack()
.spanned(self.span()); .spanned(self.page.span());
// We interpret the Y alignment as selecting header or footer // We interpret the Y alignment as selecting header or footer
// and then ignore it for aligning the actual number. // and then ignore it for aligning the actual number.
@ -451,8 +485,8 @@ impl Packed<PageElem> {
counter counter
}); });
let header = self.header(styles); let header = self.page.header(styles);
let footer = self.footer(styles); let footer = self.page.footer(styles);
let (header, footer) = if matches!(number_align.y(), Some(OuterVAlignment::Top)) { let (header, footer) = if matches!(number_align.y(), Some(OuterVAlignment::Top)) {
( (
header.as_ref().unwrap_or(&numbering_marginal), header.as_ref().unwrap_or(&numbering_marginal),
@ -466,16 +500,16 @@ impl Packed<PageElem> {
}; };
// Post-process pages. // Post-process pages.
let mut pages = Vec::with_capacity(frames.len()); let mut pages = Vec::with_capacity(self.frames.len());
for mut frame in frames { for mut frame in self.frames {
// The padded width of the page's content without margins. // The padded width of the page's content without margins.
let pw = frame.width(); let pw = frame.width();
// If two sided, left becomes inside and right becomes outside. // If two sided, left becomes inside and right becomes outside.
// Thus, for left-bound pages, we want to swap on even pages and // Thus, for left-bound pages, we want to swap on even pages and
// for right-bound pages, we want to swap on odd pages. // for right-bound pages, we want to swap on odd pages.
let mut margin = margin; let mut margin = self.margin;
if two_sided && binding.swap(page_counter.physical()) { if self.two_sided && binding.swap(page_counter.physical()) {
std::mem::swap(&mut margin.left, &mut margin.right); std::mem::swap(&mut margin.left, &mut margin.right);
} }
@ -511,7 +545,7 @@ impl Packed<PageElem> {
let sub = content let sub = content
.clone() .clone()
.styled(AlignElem::set_alignment(align)) .styled(AlignElem::set_alignment(align))
.layout(engine, locator.next(&content.span()), styles, pod)? .layout(engine, self.locator.next(&content.span()), styles, pod)?
.into_frame(); .into_frame();
if ptr::eq(marginal, header) || ptr::eq(marginal, background) { if ptr::eq(marginal, header) || ptr::eq(marginal, background) {

View File

@ -79,29 +79,32 @@ impl Packed<DocumentElem> {
locator: Locator, locator: Locator,
styles: StyleChain, styles: StyleChain,
) -> SourceResult<Document> { ) -> SourceResult<Document> {
let mut pages = Vec::with_capacity(self.children().len());
let mut page_counter = ManualPageCounter::new();
let children = self.children(); let children = self.children();
let mut iter = children.chain(&styles).peekable(); let mut peekable = children.chain(&styles).peekable();
let mut locator = locator.split(); let mut locator = locator.split();
while let Some((child, styles)) = iter.next() { let iter = std::iter::from_fn(|| {
if let Some(page) = child.to_packed::<PageElem>() { let (child, styles) = peekable.next()?;
let extend_to = iter let extend_to = peekable
.peek() .peek()
.and_then(|(next, _)| *next.to_packed::<PageElem>()?.clear_to()?); .and_then(|(next, _)| *next.to_packed::<PageElem>()?.clear_to()?);
let run = page.layout( let locator = locator.next(&child.span());
engine, Some((child, styles, extend_to, locator))
locator.next(&page.span()), });
styles,
&mut page_counter, let layouts =
extend_to, engine.parallelize(iter, |engine, (child, styles, extend_to, locator)| {
)?; if let Some(page) = child.to_packed::<PageElem>() {
pages.extend(run); page.layout(engine, locator, styles, extend_to)
} else { } else {
bail!(child.span(), "unexpected document child"); bail!(child.span(), "unexpected document child");
} }
});
let mut page_counter = ManualPageCounter::new();
let mut pages = Vec::with_capacity(self.children().len());
for result in layouts {
pages.extend(result?.finalize(engine, &mut page_counter)?);
} }
Ok(Document { Ok(Document {