Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: add benchmarking #192

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,12 @@ serde_test = "1.0.102"
tempdir = "0.3.7"
maplit = "1.0.2"
pretty_assertions = "0.7.2"
criterion = "0.3"

[[bench]]
name = "load_family"
harness = false

[[bench]]
name = "load_progressively"
harness = false
90 changes: 90 additions & 0 deletions benches/load_family.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use criterion::{criterion_group, criterion_main, Criterion};

use norad::Font;

#[inline]
fn load_family_small() {
let _ = Font::load("testdata/mutatorSans/MutatorSansBoldCondensed.ufo").unwrap();
let _ = Font::load("testdata/mutatorSans/MutatorSansBoldWide.ufo").unwrap();
let _ = Font::load("testdata/mutatorSans/MutatorSansIntermediateCondensed.ufo").unwrap();
let _ = Font::load("testdata/mutatorSans/MutatorSansIntermediateWide.ufo").unwrap();
let _ = Font::load("testdata/mutatorSans/MutatorSansLightCondensed.ufo").unwrap();
let _ = Font::load("testdata/mutatorSans/MutatorSansLightWide.ufo").unwrap();
}

#[inline]
fn load_family_medium() {
let _ = Font::load("/tmp/NotoSans-Bold.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-Condensed.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-CondensedBold.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-CondensedLight.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-CondensedSemiBold.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-DisplayBold.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-DisplayCondensed.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-DisplayLight.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-DisplayLightCondensed.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-DisplayRegular.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-DisplaySemiBold.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-DisplaySemiBoldCondensed.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-Light.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-Regular.ufo").unwrap();
let _ = Font::load("/tmp/NotoSans-SemiBold.ufo").unwrap();
}

#[inline]
fn load_family_very_large() {
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-SemiLight.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-RegularCondensed.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-ThinCondensed.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-Semibold.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-CondensedExtraBlack.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-CondensedExtraThin.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-BlackCondensed.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-Medium.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-Thin.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-Black.ufo").unwrap();
let _ =
Font::load("../amalgamate-noto/NotoAmalgamated-DisplayBoldCondensedItalic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplayBoldItalic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplayCondensedItalic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplayItalic.ufo").unwrap();
let _ =
Font::load("../amalgamate-noto/NotoAmalgamated-DisplayLightCondensedItalic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplayLightItalic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplaySemiBoldCondensedItalic.ufo")
.unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplaySemiBoldItalic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplayBold.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplayBoldCondensed.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplayCondensed.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplayLight.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplayLightCondensed.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplayRegular.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplaySemiBold.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-DisplaySemiBoldCondensed.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-CondensedBoldItalic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-CondensedItalic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-CondensedLightItalic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-CondensedSemiBoldItalic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-LightItalic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-SemiBoldItalic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-BoldItalic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-Italic.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-CondensedSemiBold.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-Condensed.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-CondensedBold.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-CondensedLight.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-SemiBold.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-Light.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-Bold.ufo").unwrap();
let _ = Font::load("../amalgamate-noto/NotoAmalgamated-Regular.ufo").unwrap();
}

pub fn criterion_benchmark(c: &mut Criterion) {
c.bench_function("load_family_small", |b| b.iter(load_family_small));
c.bench_function("load_family_medium", |b| b.iter(load_family_medium));
c.bench_function("load_family_very_large", |b| b.iter(load_family_very_large));
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
61 changes: 61 additions & 0 deletions benches/load_progressively.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use std::path::Path;

use criterion::BenchmarkId;
use criterion::SamplingMode;
use criterion::Throughput;
use criterion::{criterion_group, criterion_main, Criterion};

use norad::DataRequest;
use norad::Font;

#[inline]
fn load(path: &Path) {
let _ = Font::load(path).unwrap();
}

#[inline]
fn load_no_glyph_lib(path: &Path) {
let mut request = DataRequest::default();
request.glyph_lib(false);
let _ = Font::load_requested_data(path, request).unwrap();
}

pub fn criterion_benchmark(c: &mut Criterion) {
let mut group = c.benchmark_group("load_progressively");
group.sampling_mode(SamplingMode::Flat);

for (glyph_number, ufo_name) in [
(107, "NotoAmalgamated-SemiLight.ufo"),
(347, "NotoAmalgamated-RegularCondensed.ufo"),
(1022, "NotoAmalgamated-Medium.ufo"),
(2234, "NotoAmalgamated-Thin.ufo"),
(3793, "NotoAmalgamated-DisplayBoldCondensedItalic.ufo"),
(7079, "NotoAmalgamated-DisplayBold.ufo"),
(12871, "NotoAmalgamated-CondensedBoldItalic.ufo"),
(24254, "NotoAmalgamated-SemiBold.ufo"),
(35358, "NotoAmalgamated-Bold.ufo"),
(60967, "NotoAmalgamated-Regular.ufo"),
] {
let path = Path::new("../amalgamate-noto/").join(ufo_name);
group.throughput(Throughput::Elements(glyph_number));
group.bench_with_input(
BenchmarkId::new("With glyph lib", glyph_number),
&path,
|b, path| {
b.iter(|| load(path));
},
);
group.bench_with_input(
BenchmarkId::new("Without glyph lib", glyph_number),
&path,
|b, path| {
b.iter(|| load_no_glyph_lib(path));
},
);
}

group.finish();
}

criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);
18 changes: 17 additions & 1 deletion src/data_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
pub struct DataRequest {
pub layers: bool,
pub lib: bool,
pub glyph_lib: bool,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a useful addition, or is it only useful in the context of benchmarking?

Copy link
Collaborator Author

@madig madig Oct 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was primarily added to test the impact of glyph lib loading on loading times. Result: it roughly doubles parsing time on my Linux and Windows machines. Ouch. I currently have no other use for it, but this PR is a WIP for a reason :)

pub groups: bool,
pub kerning: bool,
pub features: bool,
Expand All @@ -22,7 +23,16 @@ pub struct DataRequest {

impl DataRequest {
fn from_bool(b: bool) -> Self {
DataRequest { layers: b, lib: b, groups: b, kerning: b, features: b, data: b, images: b }
DataRequest {
layers: b,
lib: b,
glyph_lib: b,
groups: b,
kerning: b,
features: b,
data: b,
images: b,
}
}

/// Returns a `DataRequest` requesting all UFO data.
Expand All @@ -47,6 +57,12 @@ impl DataRequest {
self
}

/// Request that returned UFO data include glyph lib sections.
pub fn glyph_lib(&mut self, b: bool) -> &mut Self {
self.glyph_lib = b;
self
}

/// Request that returned UFO data include parsed `groups.plist`.
pub fn groups(&mut self, b: bool) -> &mut Self {
self.groups = b;
Expand Down
5 changes: 3 additions & 2 deletions src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ impl Font {

let glyph_names = NameList::default();
let layers = if request.layers {
load_layers(path, &meta, &glyph_names)?
load_layers(path, &meta, &glyph_names, &request)?
} else {
LayerSet::default()
};
Expand Down Expand Up @@ -447,12 +447,13 @@ fn load_layers(
ufo_path: &Path,
meta: &MetaInfo,
glyph_names: &NameList,
request: &DataRequest,
) -> Result<LayerSet, Error> {
let layercontents_path = ufo_path.join(LAYER_CONTENTS_FILE);
if meta.format_version == FormatVersion::V3 && !layercontents_path.exists() {
return Err(Error::MissingFile(layercontents_path.display().to_string()));
}
LayerSet::load(ufo_path, glyph_names)
LayerSet::load(ufo_path, glyph_names, request)
}

#[cfg(test)]
Expand Down
12 changes: 8 additions & 4 deletions src/glyph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use druid::{Data, Lens};
use crate::error::{Error, ErrorKind, GlifError, GlifErrorInternal};
use crate::names::NameList;
use crate::shared_types::PUBLIC_OBJECT_LIBS_KEY;
use crate::{Color, Guideline, Identifier, Line, Plist, WriteOptions};
use crate::{Color, DataRequest, Guideline, Identifier, Line, Plist, WriteOptions};

/// The name of a glyph.
pub type GlyphName = Arc<str>;
Expand Down Expand Up @@ -49,12 +49,16 @@ impl Glyph {
pub fn load(path: impl AsRef<Path>) -> Result<Self, Error> {
let path = path.as_ref();
let names = NameList::default();
Glyph::load_with_names(path, &names)
Glyph::load_with_names(path, &names, &DataRequest::default())
}

pub fn load_with_names(path: &Path, names: &NameList) -> Result<Self, Error> {
pub fn load_with_names(
path: &Path,
names: &NameList,
request: &DataRequest,
) -> Result<Self, Error> {
let data = std::fs::read(path)?;
parse::GlifParser::from_xml(&data, Some(names)).map_err(|e| match e {
parse::GlifParser::from_xml(&data, Some(names), request).map_err(|e| match e {
GlifErrorInternal::Xml(e) => e.into(),
GlifErrorInternal::Spec { kind, position } => {
GlifError { kind, position, path: Some(path.to_owned()) }.into()
Expand Down
19 changes: 15 additions & 4 deletions src/glyph/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use quick_xml::{

#[cfg(test)]
pub(crate) fn parse_glyph(xml: &[u8]) -> Result<Glyph, GlifErrorInternal> {
GlifParser::from_xml(xml, None)
GlifParser::from_xml(xml, None, &DataRequest::default())
}

macro_rules! err {
Expand All @@ -35,21 +35,26 @@ pub(crate) struct GlifParser<'names> {
}

impl<'names> GlifParser<'names> {
pub(crate) fn from_xml(xml: &[u8], names: Option<&'names NameList>) -> Result<Glyph, Error> {
pub(crate) fn from_xml(
xml: &[u8],
names: Option<&'names NameList>,
request: &DataRequest,
) -> Result<Glyph, Error> {
let mut reader = Reader::from_reader(xml);
let mut buf = Vec::new();
reader.trim_text(true);

let builder = start(&mut reader, &mut buf)?;
let this = GlifParser { builder, names };
this.parse_body(&mut reader, xml, &mut buf)
this.parse_body(&mut reader, xml, &mut buf, request)
}

fn parse_body(
mut self,
reader: &mut Reader<&[u8]>,
raw_xml: &[u8],
buf: &mut Vec<u8>,
request: &DataRequest,
) -> Result<Glyph, Error> {
loop {
match reader.read_event(buf)? {
Expand All @@ -58,7 +63,13 @@ impl<'names> GlifParser<'names> {
let tag_name = reader.decode(start.name())?;
match tag_name.borrow() {
"outline" => self.parse_outline(reader, buf)?,
"lib" => self.parse_lib(reader, raw_xml, buf)?, // do this at some point?
"lib" => {
if request.glyph_lib {
self.parse_lib(reader, raw_xml, buf)?
} else {
reader.read_to_end("lib", buf)?
}
}
"note" => self.parse_note(reader, buf)?,
_other => return Err(err!(reader, ErrorKind::UnexpectedTag)),
}
Expand Down
35 changes: 35 additions & 0 deletions src/glyph/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use super::parse::parse_glyph;
use super::*;
use crate::glyph::parse::GlifParser;
use crate::write::QuoteChar;
use std::path::PathBuf;
use std::str::FromStr;
Expand Down Expand Up @@ -704,3 +705,37 @@ fn has_component_with_base() {
assert!(glyph.has_component_with_base("dieresis"));
assert!(!glyph.has_component_with_base("Z"));
}

#[test]
fn load_no_glyph_lib() {
let data = r#"
<?xml version="1.0" encoding="UTF-8"?>
<glyph name="test" format="2">
<outline>
<component base="component"/>
</outline>
<lib>
<dict>
<key>test.key</key>
<string>I am a creative professional :)</string>
</dict>
</lib>
<note>durp</note>
</glyph>
"#;

let request = DataRequest { glyph_lib: false, ..Default::default() };
let test = GlifParser::from_xml(data.as_bytes(), None, &request).unwrap();
assert_eq!(
test.components,
vec![Component {
base: "component".into(),
transform: Default::default(),
identifier: None,
lib: None
}]
);
assert_eq!(test.contours, vec![]);
assert!(test.lib.is_empty());
assert_eq!(test.note, Some("durp".into()));
}
Loading