use std::collections::{HashMap, HashSet};
use crate::Gpu;
use crate::buffer::DeviceBuffer;
pub const OOM_TAG: &str = "out of GPU memory";
pub fn is_oom(e: &str) -> bool {
e.starts_with(OOM_TAG)
}
pub(crate) fn capacity_class(len: usize) -> usize {
if len == 0 {
return 0;
}
let unit = 1usize << (usize::BITS - len.leading_zeros()).saturating_sub(4);
len.div_ceil(unit) * unit
}
pub(crate) struct Pool<'g> {
free: HashMap<usize, Vec<DeviceBuffer<'g>>>,
used: HashSet<usize>,
held: u64,
peak: u64,
in_use: u64,
in_use_peak: u64,
}
impl<'g> Pool<'g> {
pub fn new() -> Pool<'g> {
Pool {
free: HashMap::new(),
used: HashSet::new(),
held: 0,
peak: 0,
in_use: 0,
in_use_peak: 0,
}
}
pub fn take(&mut self, gpu: &'g Gpu, cap: usize) -> Result<DeviceBuffer<'g>, String> {
self.used.insert(cap);
let b = match self.free.get_mut(&cap).and_then(Vec::pop) {
Some(b) => b,
None => {
let b = match gpu.alloc(cap) {
Ok(b) => b,
Err(_) => {
self.trim();
gpu.alloc(cap)
.map_err(|e| format!("{OOM_TAG}: need {} bytes ({e})", bytes(cap)))?
}
};
self.held += bytes(cap);
self.peak = self.peak.max(self.held);
b
}
};
self.in_use += bytes(cap);
self.in_use_peak = self.in_use_peak.max(self.in_use);
Ok(b)
}
pub fn give(&mut self, cap: usize, b: DeviceBuffer<'g>) {
self.in_use -= bytes(cap);
self.free.entry(cap).or_default().push(b);
}
pub fn trim(&mut self) {
self.held -= self.idle();
self.free.clear();
}
pub fn end_batch(&mut self) {
let used = std::mem::take(&mut self.used);
let mut freed = 0;
self.free.retain(|cap, v| {
let keep = used.contains(cap);
if !keep {
freed += bytes(*cap) * v.len() as u64;
}
keep
});
self.held -= freed;
}
pub fn peak(&self) -> u64 {
self.peak
}
pub fn in_use_peak(&self) -> u64 {
self.in_use_peak
}
pub fn reset_peak(&mut self) {
self.peak = self.held;
self.in_use_peak = self.in_use;
}
pub fn idle(&self) -> u64 {
self.free
.iter()
.map(|(&cap, v)| bytes(cap) * v.len() as u64)
.sum()
}
}
pub(crate) fn bytes(len: usize) -> u64 {
len as u64 * size_of::<f32>() as u64
}
#[cfg(test)]
mod tests {
use super::capacity_class;
#[test]
fn 容量档不小于请求且浪费有界() {
assert_eq!(capacity_class(0), 0);
for len in 1..100_000usize {
let c = capacity_class(len);
assert!(c >= len);
assert!((c - len) * 8 <= len.max(8), "{len} → {c}");
}
assert_eq!(capacity_class(1000), capacity_class(1020));
}
}