use crate::ast::Value;
use rustc_hash::FxHashMap;
use std::cell::{Cell, RefCell};
use std::sync::Arc;
const SMALL_STRING_SIZE: usize = 16;
const SMALL_VEC_SIZE: usize = 8;
pub struct TypedArena<T> {
current: RefCell<Vec<T>>,
chunk_size: usize,
chunks: RefCell<Vec<Vec<T>>>,
allocations: Cell<usize>,
}
impl<T> Default for TypedArena<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> TypedArena<T> {
pub fn new() -> Self {
Self::with_capacity(1024)
}
pub fn with_capacity(chunk_size: usize) -> Self {
TypedArena {
current: RefCell::new(Vec::with_capacity(chunk_size)),
chunk_size,
chunks: RefCell::new(Vec::new()),
allocations: Cell::new(0),
}
}
pub fn alloc(&self, value: T) -> &T {
self.allocations.set(self.allocations.get() + 1);
let mut current = self.current.borrow_mut();
if current.len() == current.capacity() {
let new_chunk = Vec::with_capacity(self.chunk_size);
let full_chunk = std::mem::replace(&mut *current, new_chunk);
self.chunks.borrow_mut().push(full_chunk);
}
current.push(value);
unsafe {
let ptr = current.as_ptr().add(current.len() - 1);
&*ptr
}
}
pub fn allocations(&self) -> usize {
self.allocations.get()
}
}
#[derive(Default, Debug, Clone)]
pub struct AllocationStats {
pub string_allocations: usize,
pub strings_interned: usize,
pub small_strings_optimized: usize,
pub value_allocations: usize,
pub array_allocations: usize,
pub object_allocations: usize,
pub total_bytes: usize,
}
#[derive(Clone, Debug)]
pub enum CompactString {
Small([u8; SMALL_STRING_SIZE]),
Heap(String),
Interned(&'static str),
}
impl CompactString {
pub fn new(s: &str) -> Self {
if s.len() <= SMALL_STRING_SIZE {
let mut bytes = [0u8; SMALL_STRING_SIZE];
bytes[..s.len()].copy_from_slice(s.as_bytes());
CompactString::Small(bytes)
} else {
CompactString::Heap(s.to_string())
}
}
pub fn as_str(&self) -> &str {
match self {
CompactString::Small(bytes) => {
let len = bytes
.iter()
.position(|&b| b == 0)
.unwrap_or(SMALL_STRING_SIZE);
unsafe { std::str::from_utf8_unchecked(&bytes[..len]) }
}
CompactString::Heap(s) => s.as_str(),
CompactString::Interned(s) => s,
}
}
}
pub enum SmallVec<T> {
Inline([Option<T>; SMALL_VEC_SIZE]),
Heap(Vec<T>),
}
impl<T> Default for SmallVec<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> SmallVec<T> {
pub fn new() -> Self {
SmallVec::Inline(Default::default())
}
pub fn with_capacity(capacity: usize) -> Self {
if capacity <= SMALL_VEC_SIZE {
SmallVec::Inline(Default::default())
} else {
SmallVec::Heap(Vec::with_capacity(capacity))
}
}
}
pub struct MemoryPoolV3 {
string_arena: TypedArena<String>,
value_arena: TypedArena<Value>,
array_arena: TypedArena<Vec<Value>>,
object_arena: TypedArena<FxHashMap<String, Value>>,
interned_strings: RefCell<FxHashMap<String, &'static str>>,
common_keys: FxHashMap<&'static str, &'static str>,
stats: RefCell<AllocationStats>,
}
impl Default for MemoryPoolV3 {
fn default() -> Self {
Self::new()
}
}
impl MemoryPoolV3 {
pub fn new() -> Self {
let mut pool = MemoryPoolV3 {
string_arena: TypedArena::new(),
value_arena: TypedArena::new(),
array_arena: TypedArena::new(),
object_arena: TypedArena::new(),
interned_strings: RefCell::new(FxHashMap::default()),
common_keys: FxHashMap::default(),
stats: RefCell::new(AllocationStats::default()),
};
let common_keys = [
"id",
"type",
"name",
"value",
"data",
"error",
"message",
"status",
"result",
"items",
"user",
"timestamp",
"created",
"updated",
"deleted",
"url",
"method",
"body",
"headers",
];
for key in &common_keys {
pool.intern_static(key);
}
pool
}
fn intern_static(&mut self, s: &'static str) {
self.common_keys.insert(s, s);
}
pub fn alloc_string(&self, s: String) -> &str {
let mut stats = self.stats.borrow_mut();
stats.string_allocations += 1;
stats.total_bytes += s.len();
if let Some(&interned) = self.common_keys.get(s.as_str()) {
stats.strings_interned += 1;
return interned;
}
if let Some(&interned) = self.interned_strings.borrow().get(&s) {
stats.strings_interned += 1;
return interned;
}
if s.len() <= SMALL_STRING_SIZE {
stats.small_strings_optimized += 1;
}
let allocated = self.string_arena.alloc(s);
let leaked: &'static str = unsafe { std::mem::transmute(allocated.as_str()) };
self.interned_strings
.borrow_mut()
.insert(allocated.clone(), leaked);
allocated.as_str()
}
pub fn alloc_value(&self, value: Value) -> &Value {
self.stats.borrow_mut().value_allocations += 1;
self.value_arena.alloc(value)
}
pub fn alloc_array(&self, array: Vec<Value>) -> &Vec<Value> {
self.stats.borrow_mut().array_allocations += 1;
self.stats.borrow_mut().total_bytes += array.capacity() * size_of::<Value>();
self.array_arena.alloc(array)
}
pub fn alloc_object(&self, object: FxHashMap<String, Value>) -> &FxHashMap<String, Value> {
self.stats.borrow_mut().object_allocations += 1;
self.stats.borrow_mut().total_bytes +=
object.capacity() * (size_of::<String>() + size_of::<Value>());
self.object_arena.alloc(object)
}
pub fn stats(&self) -> AllocationStats {
self.stats.borrow().clone()
}
pub fn cow_string(&self, s: &str) -> Arc<str> {
Arc::from(s)
}
}
thread_local! {
static POOL: RefCell<Option<Arc<MemoryPoolV3>>> = const { RefCell::new(None) };
}
#[allow(clippy::arc_with_non_send_sync)]
pub fn with_pool<F, R>(f: F) -> R
where
F: FnOnce(&MemoryPoolV3) -> R,
{
POOL.with(|pool| {
let mut pool_ref = pool.borrow_mut();
let pool = pool_ref.get_or_insert_with(|| Arc::new(MemoryPoolV3::new()));
f(pool)
})
}
#[allow(clippy::arc_with_non_send_sync)]
pub struct ScopedMemoryPoolV3 {
pool: Arc<MemoryPoolV3>,
}
impl Default for ScopedMemoryPoolV3 {
fn default() -> Self {
Self::new()
}
}
impl ScopedMemoryPoolV3 {
#[allow(clippy::arc_with_non_send_sync)]
pub fn new() -> Self {
ScopedMemoryPoolV3 {
pool: Arc::new(MemoryPoolV3::new()),
}
}
pub fn pool(&self) -> &MemoryPoolV3 {
&self.pool
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_typed_arena() {
let arena: TypedArena<String> = TypedArena::new();
let s1 = arena.alloc("hello".to_string());
let s2 = arena.alloc("world".to_string());
assert_eq!(s1, "hello");
assert_eq!(s2, "world");
assert_eq!(arena.allocations(), 2);
}
#[test]
fn test_compact_string() {
let small = CompactString::new("hello");
assert_eq!(small.as_str(), "hello");
let large = CompactString::new("this is a much longer string that won't fit inline");
assert_eq!(
large.as_str(),
"this is a much longer string that won't fit inline"
);
}
#[test]
fn test_memory_pool_v3() {
let pool = MemoryPoolV3::new();
let s1 = pool.alloc_string("test".to_string());
let s2 = pool.alloc_string("test".to_string());
assert_eq!(s1, "test");
assert_eq!(s2, "test");
let id = pool.alloc_string("id".to_string());
assert_eq!(id, "id");
let stats = pool.stats();
assert_eq!(stats.string_allocations, 3);
assert!(stats.strings_interned > 0);
}
}