use once_cell::sync::Lazy;
use std::collections::VecDeque;
use std::sync::Arc;
#[cfg(feature = "pool-metrics")]
use std::sync::atomic::AtomicUsize;
#[cfg(feature = "pool-metrics")]
use std::sync::atomic::Ordering;
pub struct PoolConfig {
pub max_buffers_per_size: usize,
pub initial_capacity: usize,
pub max_capacity_before_discard: usize,
}
impl Default for PoolConfig {
fn default() -> Self {
Self {
max_buffers_per_size: 4,
initial_capacity: 4096,
max_capacity_before_discard: 65536,
}
}
}
pub struct StringBufferPool {
pool: dashmap::DashMap<usize, VecDeque<String>>,
config: PoolConfig,
#[cfg(feature = "pool-metrics")]
acquire_count: AtomicUsize,
#[cfg(feature = "pool-metrics")]
reuse_count: AtomicUsize,
}
impl StringBufferPool {
pub fn new(config: PoolConfig) -> Self {
StringBufferPool {
pool: dashmap::DashMap::new(),
config,
#[cfg(feature = "pool-metrics")]
acquire_count: AtomicUsize::new(0),
#[cfg(feature = "pool-metrics")]
reuse_count: AtomicUsize::new(0),
}
}
fn find_bucket(&self, capacity: usize) -> usize {
if capacity <= 1024 {
1024
} else if capacity <= 4096 {
4096
} else if capacity <= 16384 {
16384
} else if capacity <= 65536 {
65536
} else {
262144
}
}
fn try_acquire_from_bucket(&self, bucket: usize) -> Option<String> {
if let Some(mut entry) = self.pool.get_mut(&bucket) {
entry.pop_front()
} else {
None
}
}
pub fn acquire(self: Arc<Self>) -> PooledString {
#[cfg(feature = "pool-metrics")]
self.acquire_count.fetch_add(1, Ordering::Relaxed);
let default_bucket = self.config.initial_capacity;
if let Some(buffer) = self.try_acquire_from_bucket(default_bucket) {
#[cfg(feature = "pool-metrics")]
self.reuse_count.fetch_add(1, Ordering::Relaxed);
return PooledString { buffer, pool: self };
}
for &bucket in &[1024, 16384, 65536] {
if let Some(buffer) = self.try_acquire_from_bucket(bucket) {
#[cfg(feature = "pool-metrics")]
self.reuse_count.fetch_add(1, Ordering::Relaxed);
return PooledString { buffer, pool: self };
}
}
PooledString {
buffer: String::with_capacity(self.config.initial_capacity),
pool: self,
}
}
pub fn release(&self, mut buffer: String) {
if buffer.capacity() > self.config.max_capacity_before_discard {
return;
}
let bucket = self.find_bucket(buffer.capacity());
buffer.clear();
if let Some(mut queue) = self.pool.get_mut(&bucket) {
if queue.len() < self.config.max_buffers_per_size {
queue.push_back(buffer);
}
} else {
let mut queue = VecDeque::with_capacity(self.config.max_buffers_per_size);
queue.push_back(buffer);
self.pool.insert(bucket, queue);
}
}
#[cfg(feature = "pool-metrics")]
pub fn metrics(&self) -> StringBufferPoolMetrics {
let acquire = self.acquire_count.load(Ordering::Relaxed);
let reuse = self.reuse_count.load(Ordering::Relaxed);
let hit_rate = if acquire == 0 {
0.0
} else {
(reuse as f64 / acquire as f64) * 100.0
};
StringBufferPoolMetrics {
total_acquires: acquire,
total_reuses: reuse,
hit_rate,
}
}
}
#[cfg(feature = "pool-metrics")]
#[derive(Debug, Clone, Copy)]
pub struct StringBufferPoolMetrics {
pub total_acquires: usize,
pub total_reuses: usize,
pub hit_rate: f64,
}
pub struct PooledString {
buffer: String,
pool: Arc<StringBufferPool>,
}
impl PooledString {
pub fn buffer_mut(&mut self) -> &mut String {
&mut self.buffer
}
pub fn as_str(&self) -> &str {
self.buffer.as_str()
}
}
impl std::ops::Deref for PooledString {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.buffer
}
}
impl std::ops::DerefMut for PooledString {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.buffer
}
}
impl Drop for PooledString {
fn drop(&mut self) {
let buffer = std::mem::take(&mut self.buffer);
self.pool.release(buffer);
}
}
impl std::fmt::Display for PooledString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.buffer)
}
}
impl std::fmt::Debug for PooledString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("PooledString").field(&self.buffer).finish()
}
}
pub static STRING_BUFFER_POOL: Lazy<Arc<StringBufferPool>> =
Lazy::new(|| Arc::new(StringBufferPool::new(PoolConfig::default())));
pub fn acquire_string_buffer() -> PooledString {
Arc::clone(&*STRING_BUFFER_POOL).acquire()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_buffer_pool_acquire_and_release() {
let config = PoolConfig::default();
let pool = Arc::new(StringBufferPool::new(config));
let mut buffer = pool.clone().acquire();
buffer.push_str("test content");
let capacity = buffer.capacity();
drop(buffer);
let buffer2 = pool.clone().acquire();
assert_eq!(buffer2.capacity(), capacity);
assert!(buffer2.is_empty());
}
#[test]
fn test_buffer_pool_global() {
let buffer1 = acquire_string_buffer();
drop(buffer1);
let buffer2 = acquire_string_buffer();
assert!(buffer2.capacity() >= 4096);
}
#[test]
fn test_pooled_string_deref() {
let mut buffer = acquire_string_buffer();
buffer.push_str("hello");
assert_eq!(&*buffer, "hello");
assert_eq!(buffer.as_str(), "hello");
assert!(!buffer.is_empty());
}
#[test]
fn test_pooled_string_deref_mut() {
let mut buffer = acquire_string_buffer();
buffer.push_str("test");
buffer.buffer_mut().push_str(" more");
assert_eq!(buffer.as_str(), "test more");
}
}