#![allow(dead_code)]
use std::cell::RefCell;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct TlabConfig {
pub tlab_size: usize,
pub max_tlabs: usize,
pub enabled: bool,
}
impl Default for TlabConfig {
fn default() -> Self {
Self {
tlab_size: 64 * 1024, max_tlabs: 16,
enabled: true,
}
}
}
pub struct Tlab {
base: *mut u8,
top: *mut u8,
end: *mut u8,
thread_id: u32,
size: usize,
allocation_count: usize,
bytes_allocated: usize,
}
unsafe impl Send for Tlab {}
impl Tlab {
pub fn new(size: usize, thread_id: u32) -> Self {
let layout = std::alloc::Layout::from_size_align(size, 8)
.expect("Invalid TLAB size or alignment");
let base = unsafe { std::alloc::alloc(layout) };
if base.is_null() {
panic!("Failed to allocate TLAB of {} bytes", size);
}
Self {
base,
top: base,
end: unsafe { base.add(size) },
thread_id,
size,
allocation_count: 0,
bytes_allocated: 0,
}
}
pub fn allocate(&mut self, size: usize, alignment: usize) -> Option<*mut u8> {
let aligned_top = (self.top as usize + alignment - 1) & !(alignment - 1);
let aligned_top = aligned_top as *mut u8;
if unsafe { aligned_top.add(size) } > self.end {
return None; }
let ptr = aligned_top;
self.top = unsafe { aligned_top.add(size) };
self.allocation_count += 1;
self.bytes_allocated += size;
Some(ptr)
}
pub fn remaining(&self) -> usize {
self.end as usize - self.top as usize
}
pub fn thread_id(&self) -> u32 {
self.thread_id
}
pub fn stats(&self) -> TlabStats {
TlabStats {
allocation_count: self.allocation_count,
bytes_allocated: self.bytes_allocated,
remaining: self.remaining(),
utilization: if self.size > 0 {
(self.bytes_allocated as f64 / self.size as f64) * 100.0
} else {
0.0
},
}
}
pub fn reset(&mut self) {
self.top = self.base;
self.allocation_count = 0;
self.bytes_allocated = 0;
}
}
impl Drop for Tlab {
fn drop(&mut self) {
if !self.base.is_null() {
unsafe {
if let Ok(layout) = std::alloc::Layout::from_size_align(self.size, 8) {
std::alloc::dealloc(self.base, layout);
}
log::error!("Failed to create layout for TLAB deallocation");
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct TlabStats {
pub allocation_count: usize,
pub bytes_allocated: usize,
pub remaining: usize,
pub utilization: f64,
}
pub struct TlabManager {
config: TlabConfig,
pool: RefCell<Vec<Option<Tlab>>>,
thread_tlabs: RefCell<Vec<Option<Tlab>>>,
total_allocations: Arc<AtomicUsize>,
tlab_hits: Arc<AtomicUsize>,
tlab_misses: Arc<AtomicUsize>,
}
impl TlabManager {
pub fn new(config: TlabConfig) -> Self {
let pool = (0..config.max_tlabs).map(|_| None).collect();
Self {
config,
pool: RefCell::new(pool),
thread_tlabs: RefCell::new(Vec::new()),
total_allocations: Arc::new(AtomicUsize::new(0)),
tlab_hits: Arc::new(AtomicUsize::new(0)),
tlab_misses: Arc::new(AtomicUsize::new(0)),
}
}
pub fn get_tlab(&self, thread_id: u32) -> Option<*mut Tlab> {
if !self.config.enabled {
return None;
}
{
let mut tlabs = self.thread_tlabs.borrow_mut();
if let Some(idx) = tlabs.iter().position(|t| {
t.as_ref()
.map(|t| t.thread_id() == thread_id)
.unwrap_or(false)
}) {
return tlabs[idx].as_mut().map(|t| t as *mut Tlab);
}
}
let mut pool = self.pool.borrow_mut();
for tlab in pool.iter_mut() {
if tlab.is_some() {
let tlab_obj = tlab.take().unwrap();
let mut tlabs = self.thread_tlabs.borrow_mut();
tlabs.push(Some(tlab_obj));
return tlabs
.last_mut()
.and_then(|t| t.as_mut())
.map(|t| t as *mut Tlab);
}
}
let tlab_obj = Tlab::new(self.config.tlab_size, thread_id);
let mut tlabs = self.thread_tlabs.borrow_mut();
tlabs.push(Some(tlab_obj));
tlabs
.last_mut()
.and_then(|t| t.as_mut())
.map(|t| t as *mut Tlab)
}
pub fn allocate(&self, size: usize, alignment: usize, thread_id: u32) -> Option<*mut u8> {
self.total_allocations.fetch_add(1, Ordering::Relaxed);
if let Some(tlab_ptr) = self.get_tlab(thread_id) {
unsafe {
let tlab = &mut *tlab_ptr;
if let Some(ptr) = tlab.allocate(size, alignment) {
self.tlab_hits.fetch_add(1, Ordering::Relaxed);
return Some(ptr);
}
self.tlab_misses.fetch_add(1, Ordering::Relaxed);
let mut pool = self.pool.borrow_mut();
for slot in pool.iter_mut() {
if slot.is_none() {
let mut new_tlab = Tlab::new(self.config.tlab_size, thread_id);
std::mem::swap(tlab, &mut new_tlab);
*slot = Some(new_tlab);
break;
}
}
}
}
None
}
pub fn stats(&self) -> TlabManagerStats {
let total = self.total_allocations.load(Ordering::Relaxed);
let hits = self.tlab_hits.load(Ordering::Relaxed);
let misses = self.tlab_misses.load(Ordering::Relaxed);
TlabManagerStats {
total_allocations: total,
tlab_hits: hits,
tlab_misses: misses,
hit_rate: if total > 0 {
(hits as f64 / total as f64) * 100.0
} else {
0.0
},
active_tlabs: self.thread_tlabs.borrow().len(),
}
}
pub fn reset_all(&self) {
let mut tlabs = self.thread_tlabs.borrow_mut();
for tlab in tlabs.iter_mut() {
if let Some(t) = tlab {
t.reset();
}
}
}
pub fn clear_all(&self) {
self.thread_tlabs.borrow_mut().clear();
}
}
#[derive(Debug, Clone, Copy)]
pub struct TlabManagerStats {
pub total_allocations: usize,
pub tlab_hits: usize,
pub tlab_misses: usize,
pub hit_rate: f64,
pub active_tlabs: usize,
}
impl Default for TlabManager {
fn default() -> Self {
Self::new(TlabConfig::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tlab_allocation() {
let mut tlab = Tlab::new(1024, 1);
let ptr1 = tlab.allocate(16, 8);
assert!(ptr1.is_some());
assert_eq!(tlab.stats().allocation_count, 1);
let ptr2 = tlab.allocate(32, 8);
assert!(ptr2.is_some());
assert_eq!(tlab.stats().allocation_count, 2);
assert_ne!(ptr1.unwrap(), ptr2.unwrap());
}
#[test]
fn test_tlab_full() {
let mut tlab = Tlab::new(100, 1);
let ptr1 = tlab.allocate(90, 8);
assert!(ptr1.is_some());
let ptr2 = tlab.allocate(20, 8);
assert!(ptr2.is_none());
}
#[test]
fn test_tlab_reset() {
let mut tlab = Tlab::new(1024, 1);
tlab.allocate(100, 8);
tlab.allocate(200, 8);
assert_eq!(tlab.stats().allocation_count, 2);
tlab.reset();
assert_eq!(tlab.stats().allocation_count, 0);
assert_eq!(tlab.stats().bytes_allocated, 0);
}
#[test]
fn test_tlab_manager() {
let manager = TlabManager::new(TlabConfig {
tlab_size: 1024,
max_tlabs: 2,
enabled: true,
});
let ptr = manager.allocate(100, 8, 1);
assert!(ptr.is_some());
let stats = manager.stats();
assert_eq!(stats.total_allocations, 1);
assert_eq!(stats.tlab_hits, 1);
}
#[test]
fn test_tlab_stats() {
let mut tlab = Tlab::new(1024, 1);
tlab.allocate(100, 8);
tlab.allocate(200, 8);
let stats = tlab.stats();
assert_eq!(stats.allocation_count, 2);
assert_eq!(stats.bytes_allocated, 300);
assert!(stats.utilization > 0.0);
}
#[test]
fn test_tlab_alignment() {
let mut tlab = Tlab::new(1024, 1);
let ptr1 = tlab.allocate(16, 8);
let ptr2 = tlab.allocate(32, 16);
let ptr3 = tlab.allocate(64, 32);
assert!(ptr1.is_some());
assert!(ptr2.is_some());
assert!(ptr3.is_some());
assert_eq!(ptr1.unwrap() as usize % 8, 0);
assert_eq!(ptr2.unwrap() as usize % 16, 0);
assert_eq!(ptr3.unwrap() as usize % 32, 0);
}
}