difftreelog
perf reserve for large comps
in: master
2 files changed
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -111,13 +111,14 @@
pub fn evaluate_comp(
ctx: Context,
specs: &[CompSpec],
- callback: &mut impl FnMut(Context) -> Result<()>,
+ mut guaranteed_reserve: usize,
+ callback: &mut impl FnMut(Context, usize) -> Result<()>,
) -> Result<()> {
match specs.first() {
- None => callback(ctx)?,
+ None => callback(ctx, guaranteed_reserve)?,
Some(CompSpec::IfSpec(IfSpecData { cond, span: _ })) => {
if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {
- evaluate_comp(ctx, &specs[1..], callback)?;
+ evaluate_comp(ctx, &specs[1..], 0, callback)?;
}
}
Some(CompSpec::ForSpec(ForSpecData {
@@ -126,13 +127,24 @@
})) => {
match evaluate(ctx.clone(), over)? {
Val::Arr(list) => {
- for item in list.iter_lazy() {
+ guaranteed_reserve = guaranteed_reserve.max(1) * list.len();
+ for (i, item) in list.iter_lazy().enumerate() {
let fctx = Pending::new();
let mut new_bindings = FxHashMap::with_capacity(into.binds_len());
destruct(into, item, fctx.clone(), &mut new_bindings)?;
let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);
- evaluate_comp(ctx, &specs[1..], callback)?;
+ let specs = &specs[1..];
+ evaluate_comp(
+ ctx,
+ specs,
+ if i == 0 || !specs.is_empty() {
+ guaranteed_reserve
+ } else {
+ 0
+ },
+ callback,
+ )?;
}
}
#[cfg(feature = "exp-object-iteration")]
@@ -364,8 +376,9 @@
builder.with_super(super_obj);
}
let locals = obj.locals.clone();
- evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {
+ evaluate_comp(ctx, &obj.compspecs, 0, &mut |ctx, reserve| {
let uctx = evaluate_object_locals(ctx.clone(), locals.clone());
+ builder.reserve_cores(reserve);
evaluate_field_member(&mut builder, ctx, uctx, &obj.field)
})?;
@@ -635,7 +648,10 @@
}
ArrComp(expr, comp_specs) => {
let mut out = Vec::new();
- evaluate_comp(ctx, comp_specs, &mut |ctx| {
+ evaluate_comp(ctx, comp_specs, 0, &mut |ctx, reserve| {
+ if reserve != 0 {
+ out.reserve(reserve);
+ }
let expr = expr.clone();
out.push(Thunk!(move || evaluate(ctx, &expr)));
Ok(())
crates/jrsonnet-evaluator/src/obj/oop.rsdiffbeforeafterboth1use std::{2 cell::{Cell, RefCell},3 fmt, mem,4 ops::ControlFlow,5};67use jrsonnet_gcmodule::{Cc, Trace};8use jrsonnet_ir::IStr;9use rustc_hash::{FxHashMap, FxHashSet};1011use super::{12 CcObjectAssertion, CcObjectCore, EnumFields, EnumFieldsHandler, FieldVisibility, GetFor,13 HasFieldIncludeHidden, ObjMember, ObjMemberBuilder, ObjValue, ObjValueInner, ObjectAssertion,14 ObjectCore, OmitFieldsCore, SupThis,15 ordering::{FieldIndex, SuperDepth},16};17use crate::{18 CcUnbound, MaybeUnbound, Result, Thunk, Unbound, Val, bail,19 error::ErrorKind::*,20 function::{CallLocation, FuncVal},21 gc::WithCapacityExt as _,22 in_frame,23};2425#[allow(clippy::module_name_repetitions)]26#[derive(Trace, Default)]27#[trace(tracking(force))]28pub struct OopObject {29 assertion: Option<CcObjectAssertion>,30 this_entries: FxHashMap<IStr, ObjMember>,31}32impl fmt::Debug for OopObject {33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {34 f.debug_struct("OopObject")35 .field("this_entries", &self.this_entries)36 .finish_non_exhaustive()37 }38}39impl OopObject {40 fn is_empty(&self) -> bool {41 self.assertion.is_none() && self.this_entries.is_empty()42 }43}44impl OopObject {45 pub fn new(46 this_entries: FxHashMap<IStr, ObjMember>,47 assertion: Option<CcObjectAssertion>,48 ) -> Self {49 Self {50 assertion,51 this_entries,52 }53 }54}5556impl ObjectCore for OopObject {57 fn enum_fields_core(58 &self,59 super_depth: &mut SuperDepth,60 handler: &mut EnumFieldsHandler<'_>,61 ) -> bool {62 for (name, member) in &self.this_entries {63 if matches!(64 handler(65 *super_depth,66 member.original_index,67 name.clone(),68 EnumFields::Normal(member.flags.visibility()),69 ),70 ControlFlow::Break(())71 ) {72 return false;73 }74 }75 true76 }7778 fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden {79 if self.this_entries.contains_key(&name) {80 HasFieldIncludeHidden::Exists81 } else {82 HasFieldIncludeHidden::NotFound83 }84 }8586 fn get_for_core(&self, key: IStr, sup_this: SupThis, omit_only: bool) -> Result<GetFor> {87 if omit_only {88 return Ok(GetFor::NotFound);89 }90 match self.this_entries.get(&key) {91 Some(k) => {92 let v = k.invoke.evaluate(sup_this)?;93 Ok(if k.flags.add() {94 GetFor::SuperPlus(v)95 } else {96 GetFor::Final(v)97 })98 }99 None => Ok(GetFor::NotFound),100 }101 }102 fn field_visibility_core(&self, name: IStr) -> FieldVisibility {103 self.this_entries104 .get(&name)105 .map_or(FieldVisibility::NotFound, |f| {106 FieldVisibility::Found(f.flags.visibility())107 })108 }109110 fn run_assertions_core(&self, sup_this: SupThis) -> Result<()> {111 if let Some(assertion) = &self.assertion {112 assertion.0.run(sup_this)?;113 }114 Ok(())115 }116}117118#[allow(clippy::module_name_repetitions)]119pub struct ObjValueBuilder {120 sup: Vec<CcObjectCore>,121 has_assertions: bool,122123 new: OopObject,124 next_field_index: FieldIndex,125}126impl ObjValueBuilder {127 pub fn new() -> Self {128 Self::with_capacity(0)129 }130 pub fn with_capacity(capacity: usize) -> Self {131 Self {132 sup: vec![],133 has_assertions: false,134 new: OopObject::new(FxHashMap::with_capacity(capacity), None),135 next_field_index: FieldIndex::default(),136 }137 }138 pub fn reserve_fields(&mut self, capacity: usize) {139 self.new.this_entries.reserve(capacity);140 }141 pub fn reserve_cores(&mut self, capacity: usize) -> &mut Self {142 self.sup.reserve_exact(capacity);143 self144 }145 pub fn with_super(&mut self, super_obj: ObjValue) -> &mut Self {146 self.has_assertions |= super_obj.0.has_assertions;147 self.sup.clone_from(&super_obj.0.cores);148 self149 }150151 pub fn assert(&mut self, assertion: impl ObjectAssertion + 'static) -> &mut Self {152 assert!(153 self.new.assertion.is_none(),154 "one OopObject can only have one assertion"155 );156 self.has_assertions = true;157 self.new.assertion = Some(CcObjectAssertion::new(assertion));158 self159 }160 pub fn field(&mut self, name: impl Into<IStr>) -> ObjMemberBuilder<ValueBuilder<'_>> {161 let field_index = self.next_field_index;162 self.next_field_index = self.next_field_index.next();163 ObjMemberBuilder::new(ValueBuilder(self), name.into(), field_index)164 }165 /// Preset for common method definiton pattern:166 /// Create a hidden field with the function value.167 ///168 /// `.field(name).hide().value(Val::function(value))`169 pub fn method(&mut self, name: impl Into<IStr>, value: impl Into<FuncVal>) -> &mut Self {170 self.field(name).hide().value(Val::Func(value.into()));171 self172 }173 pub fn try_method(174 &mut self,175 name: impl Into<IStr>,176 value: impl Into<FuncVal>,177 ) -> Result<&mut Self> {178 self.field(name).hide().try_value(Val::Func(value.into()))?;179 Ok(self)180 }181182 pub fn extend_with_core(&mut self, core: impl ObjectCore) {183 self.commit();184 self.sup.push(CcObjectCore::new(core));185 }186187 fn commit(&mut self) {188 if !self.new.is_empty() {189 self.sup.push(CcObjectCore::new(mem::take(&mut self.new)));190 }191 self.next_field_index = FieldIndex::default();192 }193194 pub fn with_fields_omitted(&mut self, omit: FxHashSet<IStr>) {195 self.commit();196 self.sup.push(CcObjectCore::new(OmitFieldsCore {197 omit,198 prev_layers: self.sup.len(),199 }));200 }201202 pub fn build(mut self) -> ObjValue {203 self.commit();204 if self.sup.is_empty() {205 return ObjValue::empty();206 }207 let has_assertions = self.has_assertions;208 ObjValue(Cc::new(ObjValueInner {209 cores: self.sup,210 assertions_ran: Cell::new(!has_assertions),211 has_assertions,212 value_cache: RefCell::default(),213 }))214 }215}216impl Default for ObjValueBuilder {217 fn default() -> Self {218 Self::with_capacity(0)219 }220}221222pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder);223impl ObjMemberBuilder<ValueBuilder<'_>> {224 /// Inserts value, replacing if it is already defined225 pub fn value(self, value: impl Into<Val>) {226 let (receiver, name, member) =227 self.build_member(MaybeUnbound::Bound(Thunk::evaluated(value.into())));228 let entry = receiver.0.new.this_entries.entry(name);229 entry.insert_entry(member);230 }231 /// Inserts thunk, replacing if it is already defined232 pub fn thunk(self, value: impl Into<Thunk<Val>>) {233 let (receiver, name, member) = self.build_member(MaybeUnbound::Bound(value.into()));234 let entry = receiver.0.new.this_entries.entry(name);235 entry.insert_entry(member);236 }237238 /// Tries to insert value, returns an error if it was already defined239 pub fn try_value(self, value: impl Into<Val>) -> Result<()> {240 self.try_thunk(Thunk::evaluated(value.into()))241 }242 pub fn try_thunk(self, value: impl Into<Thunk<Val>>) -> Result<()> {243 self.binding(MaybeUnbound::Bound(value.into()))244 }245 pub fn bindable(self, bindable: impl Unbound<Bound = Val>) -> Result<()> {246 self.binding(MaybeUnbound::Unbound(CcUnbound::new(bindable)))247 }248 pub fn binding(self, binding: MaybeUnbound) -> Result<()> {249 let (receiver, name, member) = self.build_member(binding);250 let location = member.location.clone();251 let old = receiver.0.new.this_entries.insert(name.clone(), member);252 if old.is_some() {253 in_frame(254 CallLocation(location.as_ref()),255 || format!("field <{}> initializtion", name.clone()),256 || bail!(DuplicateFieldName(name.clone())),257 )?;258 }259 Ok(())260 }261}