mirror of
https://github.com/jetkvm/kvm.git
synced 2025-09-16 08:38:14 +00:00
Add SizedBufferPool for efficient memory management with size tracking and statistics Introduce AudioConfigCache to minimize GetConfig() calls in hot paths Add batch processing support for audio frames to reduce CGO overhead Extend AudioBufferPoolDetailedStats with total bytes and average size metrics
218 lines
5.0 KiB
Go
218 lines
5.0 KiB
Go
package audio
|
|
|
|
import (
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// SizedBufferPool manages a pool of buffers with size tracking
|
|
type SizedBufferPool struct {
|
|
// The underlying sync.Pool
|
|
pool sync.Pool
|
|
|
|
// Statistics for monitoring
|
|
totalBuffers atomic.Int64
|
|
totalBytes atomic.Int64
|
|
gets atomic.Int64
|
|
puts atomic.Int64
|
|
misses atomic.Int64
|
|
|
|
// Configuration
|
|
maxBufferSize int
|
|
defaultSize int
|
|
}
|
|
|
|
// NewSizedBufferPool creates a new sized buffer pool
|
|
func NewSizedBufferPool(defaultSize, maxBufferSize int) *SizedBufferPool {
|
|
pool := &SizedBufferPool{
|
|
maxBufferSize: maxBufferSize,
|
|
defaultSize: defaultSize,
|
|
}
|
|
|
|
pool.pool = sync.Pool{
|
|
New: func() interface{} {
|
|
// Track pool misses
|
|
pool.misses.Add(1)
|
|
|
|
// Create new buffer with default size
|
|
buf := make([]byte, defaultSize)
|
|
|
|
// Return pointer-like to avoid allocations
|
|
slice := buf[:0]
|
|
ptrSlice := &slice
|
|
|
|
// Track statistics
|
|
pool.totalBuffers.Add(1)
|
|
pool.totalBytes.Add(int64(cap(buf)))
|
|
|
|
return ptrSlice
|
|
},
|
|
}
|
|
|
|
return pool
|
|
}
|
|
|
|
// Get returns a buffer from the pool with at least the specified capacity
|
|
func (p *SizedBufferPool) Get(minCapacity int) []byte {
|
|
// Track gets
|
|
p.gets.Add(1)
|
|
|
|
// Get buffer from pool - handle pointer-like storage
|
|
var buf []byte
|
|
poolObj := p.pool.Get()
|
|
switch v := poolObj.(type) {
|
|
case *[]byte:
|
|
// Handle pointer-like storage from Put method
|
|
if v != nil {
|
|
buf = (*v)[:0] // Get the underlying slice
|
|
} else {
|
|
buf = make([]byte, 0, p.defaultSize)
|
|
}
|
|
case []byte:
|
|
// Handle direct slice for backward compatibility
|
|
buf = v
|
|
default:
|
|
// Fallback for unexpected types
|
|
buf = make([]byte, 0, p.defaultSize)
|
|
p.misses.Add(1)
|
|
}
|
|
|
|
// Check if buffer has sufficient capacity
|
|
if cap(buf) < minCapacity {
|
|
// Track statistics for the old buffer
|
|
p.totalBytes.Add(-int64(cap(buf)))
|
|
|
|
// Allocate new buffer with required capacity
|
|
buf = make([]byte, minCapacity)
|
|
|
|
// Track statistics for the new buffer
|
|
p.totalBytes.Add(int64(cap(buf)))
|
|
} else {
|
|
// Resize existing buffer
|
|
buf = buf[:minCapacity]
|
|
}
|
|
|
|
return buf
|
|
}
|
|
|
|
// Put returns a buffer to the pool
|
|
func (p *SizedBufferPool) Put(buf []byte) {
|
|
// Track statistics
|
|
p.puts.Add(1)
|
|
|
|
// Don't pool excessively large buffers to prevent memory bloat
|
|
if cap(buf) > p.maxBufferSize {
|
|
// Track statistics
|
|
p.totalBuffers.Add(-1)
|
|
p.totalBytes.Add(-int64(cap(buf)))
|
|
return
|
|
}
|
|
|
|
// Clear buffer contents for security
|
|
for i := range buf {
|
|
buf[i] = 0
|
|
}
|
|
|
|
// Return to pool - use pointer-like approach to avoid allocations
|
|
slice := buf[:0]
|
|
p.pool.Put(&slice)
|
|
}
|
|
|
|
// GetStats returns statistics about the buffer pool
|
|
func (p *SizedBufferPool) GetStats() (buffers, bytes, gets, puts, misses int64) {
|
|
buffers = p.totalBuffers.Load()
|
|
bytes = p.totalBytes.Load()
|
|
gets = p.gets.Load()
|
|
puts = p.puts.Load()
|
|
misses = p.misses.Load()
|
|
return
|
|
}
|
|
|
|
// BufferPoolStats contains statistics about a buffer pool
|
|
type BufferPoolStats struct {
|
|
TotalBuffers int64
|
|
TotalBytes int64
|
|
Gets int64
|
|
Puts int64
|
|
Misses int64
|
|
HitRate float64
|
|
AverageBufferSize float64
|
|
}
|
|
|
|
// GetDetailedStats returns detailed statistics about the buffer pool
|
|
func (p *SizedBufferPool) GetDetailedStats() BufferPoolStats {
|
|
buffers := p.totalBuffers.Load()
|
|
bytes := p.totalBytes.Load()
|
|
gets := p.gets.Load()
|
|
puts := p.puts.Load()
|
|
misses := p.misses.Load()
|
|
|
|
// Calculate hit rate
|
|
hitRate := 0.0
|
|
if gets > 0 {
|
|
hitRate = float64(gets-misses) / float64(gets) * 100.0
|
|
}
|
|
|
|
// Calculate average buffer size
|
|
avgSize := 0.0
|
|
if buffers > 0 {
|
|
avgSize = float64(bytes) / float64(buffers)
|
|
}
|
|
|
|
return BufferPoolStats{
|
|
TotalBuffers: buffers,
|
|
TotalBytes: bytes,
|
|
Gets: gets,
|
|
Puts: puts,
|
|
Misses: misses,
|
|
HitRate: hitRate,
|
|
AverageBufferSize: avgSize,
|
|
}
|
|
}
|
|
|
|
// Global audio buffer pools with different size classes
|
|
var (
|
|
// Small buffers (up to 4KB)
|
|
smallBufferPool = NewSizedBufferPool(1024, 4*1024)
|
|
|
|
// Medium buffers (4KB to 64KB)
|
|
mediumBufferPool = NewSizedBufferPool(8*1024, 64*1024)
|
|
|
|
// Large buffers (64KB to 1MB)
|
|
largeBufferPool = NewSizedBufferPool(64*1024, 1024*1024)
|
|
)
|
|
|
|
// GetOptimalBuffer returns a buffer from the most appropriate pool based on size
|
|
func GetOptimalBuffer(size int) []byte {
|
|
switch {
|
|
case size <= 4*1024:
|
|
return smallBufferPool.Get(size)
|
|
case size <= 64*1024:
|
|
return mediumBufferPool.Get(size)
|
|
default:
|
|
return largeBufferPool.Get(size)
|
|
}
|
|
}
|
|
|
|
// ReturnOptimalBuffer returns a buffer to the appropriate pool based on size
|
|
func ReturnOptimalBuffer(buf []byte) {
|
|
size := cap(buf)
|
|
switch {
|
|
case size <= 4*1024:
|
|
smallBufferPool.Put(buf)
|
|
case size <= 64*1024:
|
|
mediumBufferPool.Put(buf)
|
|
default:
|
|
largeBufferPool.Put(buf)
|
|
}
|
|
}
|
|
|
|
// GetAllPoolStats returns statistics for all buffer pools
|
|
func GetAllPoolStats() map[string]BufferPoolStats {
|
|
return map[string]BufferPoolStats{
|
|
"small": smallBufferPool.GetDetailedStats(),
|
|
"medium": mediumBufferPool.GetDetailedStats(),
|
|
"large": largeBufferPool.GetDetailedStats(),
|
|
}
|
|
}
|