Skip to content

Commit

Permalink
refactor: use bumpalo collections to reduce memory footprint (#41)
Browse files Browse the repository at this point in the history
* chore: use bumpalo string

* chore: use bumpalo vec

* chore: use hashmap in bumpalo

* chore: use bumpalo str for map keys
  • Loading branch information
tommy authored Jun 21, 2024
1 parent 001f1b6 commit 7ab1542
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 48 deletions.
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ categories = ["command-line-utilities", "compilers", "parser-implementations"]
chrono = "0.4.38"
clap = { version = "4.5.4", features = ["derive"] }
bitflags = "2.5.0"
bumpalo = { version = "3.16.0", features = ["collections", "boxed"] }
bumpalo = { version = "3.16.0", features = [
"collections",
"boxed",
"allocator-api2",
] }
hashbrown = "0.14.5"
dtoa = "1.0.9"
base64 = "0.22.1"
serde_json = "1.0.117"
Expand Down
9 changes: 5 additions & 4 deletions src/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ impl<'a> Evaluator<'a> {
let mut result = match node.kind {
AstKind::Null => Value::null(self.arena),
AstKind::Bool(b) => Value::bool(self.arena, b),
AstKind::String(ref s) => Value::string(self.arena, String::from(s)),
AstKind::String(ref s) => Value::string(self.arena, s),
AstKind::Number(n) => Value::number(self.arena, n),
AstKind::Block(ref exprs) => self.evaluate_block(exprs, input, frame)?,
AstKind::Unary(ref op) => self.evaluate_unary_op(node, op, input, frame)?,
Expand Down Expand Up @@ -557,7 +557,7 @@ impl<'a> Evaluator<'a> {
.as_str(),
);
}
Ok(Value::string(self.arena, result))
Ok(Value::string(self.arena, &result))
}

BinaryOp::And => Ok(Value::bool(
Expand Down Expand Up @@ -767,7 +767,7 @@ impl<'a> Evaluator<'a> {
return Ok(result);
}

let result = Value::array(self.arena, ArrayFlags::SEQUENCE);
let result = Value::array_with_capacity(self.arena, input.len(), ArrayFlags::SEQUENCE);

// Evaluate the step on each member of the input
for (item_index, item) in input.members().enumerate() {
Expand Down Expand Up @@ -799,7 +799,8 @@ impl<'a> Evaluator<'a> {
result.get_member(0)
} else {
// Flatten the result sequence
let result_sequence = Value::array(self.arena, ArrayFlags::SEQUENCE);
let result_sequence =
Value::array_with_capacity(self.arena, result.len(), ArrayFlags::SEQUENCE);

for result_item in result.members() {
if !result_item.is_array() || result_item.has_flags(ArrayFlags::CONS) {
Expand Down
2 changes: 1 addition & 1 deletion src/evaluator/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl<'a> Frame<'a> {
pub fn from_tuple(parent: &Frame<'a>, tuple: &'a Value<'a>) -> Frame<'a> {
let mut bindings = HashMap::with_capacity(tuple.entries().len());
for (key, value) in tuple.entries() {
bindings.insert(key.clone(), *value);
bindings.insert(key.to_string(), *value);
}

Frame(Rc::new(RefCell::new(FrameData {
Expand Down
33 changes: 15 additions & 18 deletions src/evaluator/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ pub fn fn_each<'a>(context: FunctionContext<'a, '_>, args: &'a Value<'a>) -> Res

for (key, value) in obj.entries() {
let args = Value::array(context.arena, ArrayFlags::empty());
let key = Value::string(context.arena, key.to_string());
let key = Value::string(context.arena, key);

args.push(value);
args.push(key);
Expand Down Expand Up @@ -357,7 +357,7 @@ pub fn fn_keys<'a>(context: FunctionContext<'a, '_>, args: &'a Value<'a>) -> Res

let result = Value::array(context.arena, ArrayFlags::SEQUENCE);
for key in keys {
result.push(Value::string(context.arena, key));
result.push(Value::string(context.arena, &key));
}

Ok(result)
Expand Down Expand Up @@ -429,17 +429,17 @@ pub fn fn_string<'a>(
if input.is_string() {
Ok(input)
} else if input.is_function() {
Ok(Value::string(context.arena, String::from("")))
Ok(Value::string(context.arena, ""))
} else if input.is_number() && !input.is_finite() {
Err(Error::D3001StringNotFinite(context.char_index))
} else if *pretty == true {
let serializer = Serializer::new(PrettyFormatter::default(), true);
let output = serializer.serialize(input)?;
Ok(Value::string(context.arena, output))
Ok(Value::string(context.arena, &output))
} else {
let serializer = Serializer::new(DumpFormatter, true);
let output = serializer.serialize(input)?;
Ok(Value::string(context.arena, output))
Ok(Value::string(context.arena, &output))
}
}

Expand All @@ -462,7 +462,7 @@ pub fn fn_lowercase<'a>(
Ok(if !arg.is_string() {
Value::undefined()
} else {
Value::string(context.arena, arg.as_str().to_lowercase())
Value::string(context.arena, &arg.as_str().to_lowercase())
})
}

Expand All @@ -475,7 +475,7 @@ pub fn fn_uppercase<'a>(
if !arg.is_string() {
Ok(Value::undefined())
} else {
Ok(Value::string(context.arena, arg.as_str().to_uppercase()))
Ok(Value::string(context.arena, &arg.as_str().to_uppercase()))
}
}

Expand All @@ -501,7 +501,7 @@ pub fn fn_trim<'a>(context: FunctionContext<'a, '_>, args: &'a Value<'a>) -> Res
result
}
};
Ok(Value::string(context.arena, trimmed))
Ok(Value::string(context.arena, &trimmed))
}
}

Expand Down Expand Up @@ -538,16 +538,13 @@ pub fn fn_substring<'a>(
let start = if start < 0 { len + start } else { start };

if length.is_undefined() {
Ok(Value::string(
context.arena,
string[start as usize..].to_string(),
))
Ok(Value::string(context.arena, &string[start as usize..]))
} else {
assert_arg!(length.is_number(), context, 3);

let length = length.as_isize();
if length < 0 {
Ok(Value::string(context.arena, String::from("")))
Ok(Value::string(context.arena, ""))
} else {
let end = if start >= 0 {
(start + length) as usize
Expand All @@ -561,7 +558,7 @@ pub fn fn_substring<'a>(
.take(end - start as usize)
.collect::<String>();

Ok(Value::string(context.arena, substring))
Ok(Value::string(context.arena, &substring))
}
}
}
Expand Down Expand Up @@ -633,7 +630,7 @@ pub fn fn_replace<'a>(
str_value.replace(&pattern_value.to_string(), &replacement_value)
};

Ok(Value::string(context.arena, replaced_string))
Ok(Value::string(context.arena, &replaced_string))
}

pub fn fn_split<'a>(
Expand Down Expand Up @@ -1045,7 +1042,7 @@ pub fn fn_join<'a>(context: FunctionContext<'a, '_>, args: &'a Value<'a>) -> Res
}
}

Ok(Value::string(context.arena, result))
Ok(Value::string(context.arena, &result))
}

pub fn fn_sort<'a, 'e>(
Expand Down Expand Up @@ -1165,7 +1162,7 @@ pub fn fn_base64_encode<'a>(

let encoded = base64.encode(arg.as_str().as_bytes());

Ok(Value::string(context.arena, encoded))
Ok(Value::string(context.arena, &encoded))
}

pub fn fn_base64_decode<'a>(
Expand All @@ -1185,5 +1182,5 @@ pub fn fn_base64_decode<'a>(
let data = decoded.map_err(|e| Error::D3137Error(e.to_string()))?;
let decoded = String::from_utf8(data).map_err(|e| Error::D3137Error(e.to_string()))?;

Ok(Value::string(context.arena, decoded))
Ok(Value::string(context.arena, &decoded))
}
52 changes: 28 additions & 24 deletions src/evaluator/value.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::borrow::Cow;
use std::collections::{hash_map, HashMap};

use bitflags::bitflags;
use bumpalo::boxed::Box;
use bumpalo::collections::String as BumpString;
use bumpalo::Bump;
use hashbrown::hash_map::DefaultHashBuilder;
use hashbrown::HashMap;

use super::frame::Frame;
use super::functions::FunctionContext;
Expand Down Expand Up @@ -43,9 +45,9 @@ pub enum Value<'a> {
Null,
Number(f64),
Bool(bool),
String(String),
Array(Box<'a, Vec<&'a Value<'a>>>, ArrayFlags),
Object(Box<'a, HashMap<String, &'a Value<'a>>>),
String(bumpalo::collections::String<'a>),
Array(bumpalo::collections::Vec<'a, &'a Value<'a>>, ArrayFlags),
Object(HashMap<bumpalo::collections::String<'a>, &'a Value<'a>, DefaultHashBuilder, &'a Bump>),
Range(Range<'a>),
Lambda {
ast: Box<'a, Ast>,
Expand Down Expand Up @@ -84,12 +86,15 @@ impl<'a> Value<'a> {
arena.alloc(Value::Number(value.into()))
}

pub fn string(arena: &Bump, value: impl Into<String>) -> &mut Value {
arena.alloc(Value::String(value.into()))
pub fn string(arena: &'a Bump, value: &str) -> &'a mut Value<'a> {
arena.alloc(Value::String(bumpalo::collections::String::from_str_in(
value, arena,
)))
}

pub fn array(arena: &Bump, flags: ArrayFlags) -> &mut Value {
arena.alloc(Value::Array(Box::new_in(Vec::new(), arena), flags))
let v = bumpalo::collections::Vec::new_in(arena);
arena.alloc(Value::Array(v, flags))
}

pub fn array_from(
Expand All @@ -106,17 +111,17 @@ impl<'a> Value<'a> {

pub fn array_with_capacity(arena: &Bump, capacity: usize, flags: ArrayFlags) -> &mut Value {
arena.alloc(Value::Array(
Box::new_in(Vec::with_capacity(capacity), arena),
bumpalo::collections::Vec::with_capacity_in(capacity, arena),
flags,
))
}

pub fn object(arena: &Bump) -> &mut Value {
arena.alloc(Value::Object(Box::new_in(HashMap::new(), arena)))
arena.alloc(Value::Object(HashMap::new_in(arena)))
}

pub fn object_from(
hash: &HashMap<String, &'a Value<'a>>,
pub fn object_from<H>(
hash: &HashMap<BumpString<'a>, &'a Value<'a>, H, &'a Bump>,
arena: &'a Bump,
) -> &'a mut Value<'a> {
let result = Value::object_with_capacity(arena, hash.len());
Expand All @@ -127,10 +132,7 @@ impl<'a> Value<'a> {
}

pub fn object_with_capacity(arena: &Bump, capacity: usize) -> &mut Value {
arena.alloc(Value::Object(Box::new_in(
HashMap::with_capacity(capacity),
arena,
)))
arena.alloc(Value::Object(HashMap::with_capacity_in(capacity, arena)))
}

pub fn lambda(
Expand Down Expand Up @@ -326,7 +328,9 @@ impl<'a> Value<'a> {
}
}

pub fn entries(&self) -> hash_map::Iter<'_, String, &'a Value> {
pub fn entries(
&self,
) -> hashbrown::hash_map::Iter<'_, bumpalo::collections::String<'a>, &'a Value> {
match self {
Value::Object(map) => map.iter(),
_ => panic!("Not an object"),
Expand Down Expand Up @@ -380,7 +384,7 @@ impl<'a> Value<'a> {

pub fn as_str(&self) -> Cow<'_, str> {
match *self {
Value::String(ref s) => Cow::from(s),
Value::String(ref s) => Cow::from(s.as_str()),
_ => panic!("Not a string"),
}
}
Expand Down Expand Up @@ -428,7 +432,7 @@ impl<'a> Value<'a> {
pub fn insert(&mut self, key: &str, value: &'a Value<'a>) {
match *self {
Value::Object(ref mut map) => {
map.insert(key.to_owned(), value);
map.insert(BumpString::from_str_in(key, map.allocator()), value);
}
_ => panic!("Not an object"),
}
Expand Down Expand Up @@ -465,7 +469,7 @@ impl<'a> Value<'a> {
value: &'a Value<'a>,
flags: ArrayFlags,
) -> &'a mut Value<'a> {
arena.alloc(Value::Array(Box::new_in(vec![value], arena), flags))
arena.alloc(Value::Array(bumpalo::vec![in arena; value], flags))
}

pub fn wrap_in_array_if_needed(
Expand Down Expand Up @@ -500,7 +504,10 @@ impl<'a> Value<'a> {
Self::Null => Value::null(arena),
Self::Number(n) => Value::number(arena, *n),
Self::Bool(b) => Value::bool(arena, *b),
Self::String(s) => Value::string(arena, s),
// TODO: clean up
Self::String(s) => arena.alloc(Value::String(
bumpalo::collections::String::from_str_in(s.as_str(), arena),
)),
Self::Array(a, f) => Value::array_from(a, arena, f.clone()),
Self::Object(o) => Value::object_from(o, arena),
Self::Lambda { ast, input, frame } => Value::lambda(arena, ast, input, frame.clone()),
Expand All @@ -516,10 +523,7 @@ impl<'a> Value<'a> {

pub fn clone_array_with_flags(&self, arena: &'a Bump, flags: ArrayFlags) -> &'a mut Value<'a> {
match *self {
Value::Array(ref array, _) => arena.alloc(Value::Array(
Box::new_in(array.as_ref().clone(), arena),
flags,
)),
Value::Array(ref array, _) => arena.alloc(Value::Array(array.clone(), flags)),
_ => panic!("Not an array"),
}
}
Expand Down

0 comments on commit 7ab1542

Please sign in to comment.