Skip to content

Commit

Permalink
rune: Performance improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog committed Nov 3, 2024
1 parent 209cc47 commit 147741a
Show file tree
Hide file tree
Showing 16 changed files with 348 additions and 190 deletions.
63 changes: 42 additions & 21 deletions crates/rune/src/cli/benches.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ mod cli {
#[command(rename_all = "kebab-case")]
pub(crate) struct Flags {
/// Rounds of warmup to perform
#[arg(long, default_value = "10")]
pub(super) warmup: u32,
#[arg(long, default_value = "5.0")]
pub(super) warmup: f32,
/// Iterations to run of the benchmark
#[arg(long, default_value = "100")]
pub(super) iter: u32,
#[arg(long, default_value = "10.0")]
pub(super) iter: f32,
/// Explicit paths to benchmark.
pub(super) bench_path: Vec<PathBuf>,
}
Expand Down Expand Up @@ -152,35 +152,46 @@ where
}

fn bench_fn(io: &mut Io<'_>, item: &dyn fmt::Display, args: &Flags, f: &Function) -> Result<()> {
let mut section = io.section("Warming up", Stream::Stdout, Color::Ignore)?;
section.append(format_args!(" {item} ({} iters): ", args.warmup))?;
let mut section = io.section("Warming up", Stream::Stdout, Color::Progress)?;
section.append(format_args!(" {item} for {:.2}s:", args.warmup))?;
section.flush()?;

let step = (args.warmup / 10).max(1);

for n in 0..args.warmup {
if n % step == 0 {
section.append(".")?;
section.flush()?;
}
let start = Instant::now();
let mut warmup = 0;

let elapsed = loop {
let value = f.call::<Value>(()).into_result()?;
drop(hint::black_box(value));
}
warmup += 1;

section.close()?;
let elapsed = start.elapsed().as_secs_f32();

if elapsed >= args.warmup {
break elapsed;
}
};

section
.append(format_args!(" {warmup} iters in {elapsed:.2}s"))?
.close()?;

let iterations = usize::try_from(args.iter).expect("iterations out of bounds");
let iterations = (((args.iter * warmup as f32) / args.warmup).round() as usize).max(1);
let step = (iterations / 10).max(1);
let mut collected = Vec::try_with_capacity(iterations)?;

let step = (args.iter / 10).max(1);
let mut section = io.section("Running", Stream::Stdout, Color::Progress)?;
section.append(format_args!(
" {item} {} iterations for {:.2}s: ",
iterations, args.iter
))?;

let mut section = io.section("Running", Stream::Stdout, Color::Highlight)?;
section.append(format_args!(" {item} ({} iters): ", args.iter))?;
let mut added = 0;

for n in 0..args.iter {
for n in 0..=iterations {
if n % step == 0 {
section.append(".")?;
section.flush()?;
added += 1;
}

let start = Instant::now();
Expand All @@ -190,16 +201,25 @@ fn bench_fn(io: &mut Io<'_>, item: &dyn fmt::Display, args: &Flags, f: &Function
drop(hint::black_box(value));
}

for _ in added..10 {
section.append(".")?;
section.flush()?;
}

section.close()?;

collected.sort_unstable();

let len = collected.len() as f64;
let average = collected.iter().copied().sum::<i128>() as f64 / len;

let variance = collected
.iter()
.copied()
.map(|n| (n as f64 - average).powf(2.0))
.sum::<f64>()
/ len;

let stddev = variance.sqrt();

let format = Format {
Expand All @@ -208,7 +228,8 @@ fn bench_fn(io: &mut Io<'_>, item: &dyn fmt::Display, args: &Flags, f: &Function
iterations,
};

section.passed(format_args!(" {format}"))?.close()?;
let mut section = io.section("Result", Stream::Stdout, Color::Highlight)?;
section.append(format_args!(" {item}: {format}"))?.close()?;
Ok(())
}

Expand Down
6 changes: 5 additions & 1 deletion crates/rune/src/cli/out.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub(super) enum Color {
Ignore,
Highlight,
Important,
Progress,
}

impl Color {
Expand All @@ -37,6 +38,7 @@ impl Color {
Color::Ignore => &colors.ignored,
Color::Highlight => &colors.highlight,
Color::Important => &colors.important,
Color::Progress => &colors.progress,
}
}
}
Expand Down Expand Up @@ -105,6 +107,7 @@ struct Colors {
passed: ColorSpec,
highlight: ColorSpec,
important: ColorSpec,
progress: ColorSpec,
ignored: ColorSpec,
}

Expand All @@ -117,13 +120,14 @@ impl Colors {
this.highlight.set_bold(true);
this.important.set_fg(Some(termcolor::Color::White));
this.important.set_bold(true);
this.progress.set_fg(Some(termcolor::Color::Cyan));
this.progress.set_bold(true);
this.ignored.set_fg(Some(termcolor::Color::Yellow));
this.ignored.set_bold(true);
this
}
}

#[must_use = "Section must be closed"]
pub(super) struct Section<'a> {
pub(super) io: &'a mut StandardStream,
colors: &'a Colors,
Expand Down
8 changes: 4 additions & 4 deletions crates/rune/src/compile/meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate as rune;
use crate::alloc::borrow::Cow;
use crate::alloc::path::Path;
use crate::alloc::prelude::*;
use crate::alloc::{self, Box, HashMap, Vec};
use crate::alloc::{self, Box, Vec};
use crate::ast;
use crate::ast::{Span, Spanned};
use crate::compile::attrs::Parser;
Expand All @@ -15,7 +15,7 @@ use crate::compile::meta;
use crate::compile::{self, ItemId, Location, MetaInfo, ModId, Pool, Visibility};
use crate::module::{DocFunction, ModuleItemCommon};
use crate::parse::ResolveContext;
use crate::runtime::{Call, Protocol};
use crate::runtime::{Call, FieldMap, Protocol};
use crate::{Hash, Item, ItemBuf};

/// A meta reference to an item being compiled.
Expand Down Expand Up @@ -300,8 +300,8 @@ pub struct FieldsNamed {

impl FieldsNamed {
/// Coerce into a hashmap of fields.
pub(crate) fn to_fields(&self) -> alloc::Result<HashMap<Box<str>, usize>> {
let mut fields = HashMap::new();
pub(crate) fn to_fields(&self) -> alloc::Result<FieldMap<Box<str>, usize>> {
let mut fields = crate::runtime::new_field_hash_map_with_capacity(self.fields.len())?;

for f in self.fields.iter() {
fields.try_insert(f.name.try_clone()?, f.position)?;
Expand Down
10 changes: 5 additions & 5 deletions crates/rune/src/compile/unit_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ impl UnitBuilder {
hash,
variant_hash: Hash::EMPTY,
item: pool.item(meta.item_meta.item).try_to_owned()?,
fields: HashMap::new(),
fields: HashMap::default(),
});

self.constants
Expand Down Expand Up @@ -350,7 +350,7 @@ impl UnitBuilder {
hash: meta.hash,
variant_hash: Hash::EMPTY,
item: pool.item(meta.item_meta.item).try_to_owned()?,
fields: HashMap::new(),
fields: HashMap::default(),
});

if self
Expand Down Expand Up @@ -409,7 +409,7 @@ impl UnitBuilder {
hash: meta.hash,
variant_hash: Hash::EMPTY,
item: pool.item(meta.item_meta.item).try_to_owned()?,
fields: HashMap::new(),
fields: HashMap::default(),
});

if self
Expand Down Expand Up @@ -487,7 +487,7 @@ impl UnitBuilder {
hash: enum_hash,
variant_hash: meta.hash,
item: pool.item(meta.item_meta.item).try_to_owned()?,
fields: HashMap::new(),
fields: HashMap::default(),
});

if self
Expand Down Expand Up @@ -537,7 +537,7 @@ impl UnitBuilder {
hash: enum_hash,
variant_hash: meta.hash,
item: pool.item(meta.item_meta.item).try_to_owned()?,
fields: HashMap::new(),
fields: HashMap::default(),
});

if self
Expand Down
17 changes: 13 additions & 4 deletions crates/rune/src/module/module_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ use ::rust_alloc::sync::Arc;
use crate as rune;
use crate::alloc;
use crate::alloc::prelude::*;
use crate::alloc::HashMap;
use crate::compile::context::{AttributeMacroHandler, MacroHandler, TraitHandler};
use crate::compile::{meta, Docs};
use crate::function_meta::AssociatedName;
use crate::runtime::{ConstValue, FunctionHandler, TypeInfo};
use crate::runtime::{ConstValue, FieldMap, FunctionHandler, TypeInfo};
use crate::{Hash, ItemBuf};

#[doc(hidden)]
Expand Down Expand Up @@ -89,9 +88,19 @@ pub(crate) enum Fields {
}

impl Fields {
/// Get the number of named fields.
#[inline]
fn size(&self) -> usize {
match self {
Fields::Named(fields) => fields.len(),
_ => 0,
}
}

/// Coerce into fields hash map.
pub(crate) fn to_fields(&self) -> alloc::Result<HashMap<Box<str>, usize>> {
let mut fields = HashMap::new();
#[inline]
pub(crate) fn to_fields(&self) -> alloc::Result<FieldMap<Box<str>, usize>> {
let mut fields = crate::runtime::new_field_hash_map_with_capacity(self.size())?;

if let Fields::Named(names) = self {
for (index, name) in names.iter().copied().enumerate() {
Expand Down
72 changes: 72 additions & 0 deletions crates/rune/src/modules/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub fn module() -> Result<Module, ContextError> {
m.function_meta(Snapshot::debug)?;
m.function_meta(Snapshot::shared)?;
m.function_meta(Snapshot::is_exclusive)?;
m.function_meta(Snapshot::is_readable)?;
m.function_meta(Snapshot::is_writable)?;

Ok(m)
}
Expand Down Expand Up @@ -67,6 +69,8 @@ impl Snapshot {
/// let s = snapshot(v)?;
/// assert_eq!(s.shared(), 0);
/// assert!(!s.is_exclusive());
/// assert!(s.is_readable());
/// assert!(s.is_writable());
///
/// // Assign to a separate variable since the compiler will notice that `v` is moved.
/// let u = v;
Expand All @@ -78,12 +82,80 @@ impl Snapshot {
///
/// let s = snapshot(u)?;
/// assert!(s.is_exclusive());
/// assert!(!s.is_readable());
/// assert!(!s.is_writable());
/// ```
#[rune::function]
fn is_exclusive(&self) -> bool {
self.inner.is_exclusive()
}

/// Test if the snapshot indicates that the value is readable.
///
/// # Examples
///
/// ```rune
/// use std::mem::snapshot;
///
/// let v = [1, 2, 3];
///
/// let s = snapshot(v)?;
/// assert_eq!(s.shared(), 0);
/// assert!(!s.is_exclusive());
/// assert!(s.is_readable());
/// assert!(s.is_writable());
///
/// // Assign to a separate variable since the compiler will notice that `v` is moved.
/// let u = v;
///
/// // Move the value into a closure, causing the original reference to become exclusively held.
/// let closure = move || {
/// v
/// };
///
/// let s = snapshot(u)?;
/// assert!(s.is_exclusive());
/// assert!(!s.is_readable());
/// assert!(!s.is_writable());
/// ```
#[rune::function]
fn is_readable(&self) -> bool {
self.inner.is_readable()
}

/// Test if the snapshot indicates that the value is writable.
///
/// # Examples
///
/// ```rune
/// use std::mem::snapshot;
///
/// let v = [1, 2, 3];
///
/// let s = snapshot(v)?;
/// assert_eq!(s.shared(), 0);
/// assert!(!s.is_exclusive());
/// assert!(s.is_readable());
/// assert!(s.is_writable());
///
/// // Assign to a separate variable since the compiler will notice that `v` is moved.
/// let u = v;
///
/// // Move the value into a closure, causing the original reference to become exclusively held.
/// let closure = move || {
/// v
/// };
///
/// let s = snapshot(u)?;
/// assert!(s.is_exclusive());
/// assert!(!s.is_readable());
/// assert!(!s.is_writable());
/// ```
#[rune::function]
fn is_writable(&self) -> bool {
self.inner.is_writable()
}

#[rune::function(protocol = DISPLAY_FMT)]
fn display(&self, f: &mut Formatter) -> VmResult<()> {
vm_write!(f, "{}", self.inner)
Expand Down
5 changes: 5 additions & 0 deletions crates/rune/src/runtime/access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ impl Snapshot {
self.0 & MASK == 0
}

/// Test if the snapshot indicates that the value is writable.
pub(crate) fn is_writable(&self) -> bool {
self.0 & MASK == 0
}

/// Test if access is exclusively held.
pub(crate) fn is_exclusive(&self) -> bool {
self.0 & MASK != 0
Expand Down
2 changes: 2 additions & 0 deletions crates/rune/src/runtime/any_obj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,7 @@ struct Shared<T = ()> {

impl Shared {
/// Increment the reference count of the inner value.
#[inline]
unsafe fn inc(this: NonNull<Self>) {
let count_ref = &*addr_of!((*this.as_ptr()).count);
let count = count_ref.get();
Expand All @@ -726,6 +727,7 @@ impl Shared {
/// # Safety
///
/// ProtocolCaller needs to ensure that `this` is a valid pointer.
#[inline]
unsafe fn dec(this: NonNull<Self>) {
let count_ref = &*addr_of!((*this.as_ptr()).count);
let count = count_ref.get();
Expand Down
Loading

0 comments on commit 147741a

Please sign in to comment.