package audio import ( "runtime" "sync" "sync/atomic" "time" "unsafe" "github.com/jetkvm/kvm/internal/logging" ) // Lock-free buffer cache for per-goroutine optimization type lockFreeBufferCache struct { buffers [4]*[]byte // Small fixed-size array for lock-free access } // Per-goroutine buffer cache using goroutine-local storage var goroutineBufferCache = make(map[int64]*lockFreeBufferCache) var goroutineCacheMutex sync.RWMutex // getGoroutineID extracts goroutine ID from runtime stack for cache key func getGoroutineID() int64 { b := make([]byte, 64) b = b[:runtime.Stack(b, false)] // Parse "goroutine 123 [running]:" format for i := 10; i < len(b); i++ { if b[i] == ' ' { id := int64(0) for j := 10; j < i; j++ { if b[j] >= '0' && b[j] <= '9' { id = id*10 + int64(b[j]-'0') } } return id } } return 0 } type AudioBufferPool struct { // Atomic fields MUST be first for ARM32 alignment (int64 fields need 8-byte alignment) currentSize int64 // Current pool size (atomic) hitCount int64 // Pool hit counter (atomic) missCount int64 // Pool miss counter (atomic) // Other fields pool sync.Pool bufferSize int maxPoolSize int mutex sync.RWMutex // Memory optimization fields preallocated []*[]byte // Pre-allocated buffers for immediate use preallocSize int // Number of pre-allocated buffers } func NewAudioBufferPool(bufferSize int) *AudioBufferPool { // Validate buffer size parameter if err := ValidateBufferSize(bufferSize); err != nil { // Log validation error and use default value logger := logging.GetDefaultLogger().With().Str("component", "AudioBufferPool").Logger() logger.Warn().Err(err).Int("bufferSize", bufferSize).Msg("invalid buffer size, using default") bufferSize = GetConfig().AudioFramePoolSize } // Optimize preallocation based on buffer size to reduce memory footprint var preallocSize int if bufferSize <= GetConfig().AudioFramePoolSize { // For frame buffers, use configured percentage preallocSize = GetConfig().PreallocPercentage } else { // For larger buffers, reduce preallocation to save memory preallocSize = GetConfig().PreallocPercentage / 2 } // Pre-allocate with exact capacity to avoid slice growth preallocated := make([]*[]byte, 0, preallocSize) // Pre-allocate buffers with optimized capacity for i := 0; i < preallocSize; i++ { // Use exact buffer size to prevent over-allocation buf := make([]byte, 0, bufferSize) preallocated = append(preallocated, &buf) } return &AudioBufferPool{ bufferSize: bufferSize, maxPoolSize: GetConfig().MaxPoolSize, preallocated: preallocated, preallocSize: preallocSize, pool: sync.Pool{ New: func() interface{} { // Allocate exact size to minimize memory waste buf := make([]byte, 0, bufferSize) return &buf }, }, } } func (p *AudioBufferPool) Get() []byte { start := time.Now() wasHit := false defer func() { latency := time.Since(start) // Record metrics for frame pool (assuming this is the main usage) if p.bufferSize >= GetConfig().AudioFramePoolSize { GetGranularMetricsCollector().RecordFramePoolGet(latency, wasHit) } else { GetGranularMetricsCollector().RecordControlPoolGet(latency, wasHit) } }() // Fast path: Try lock-free per-goroutine cache first gid := getGoroutineID() goroutineCacheMutex.RLock() cache, exists := goroutineBufferCache[gid] goroutineCacheMutex.RUnlock() if exists && cache != nil { // Try to get buffer from lock-free cache for i := 0; i < len(cache.buffers); i++ { bufPtr := (*unsafe.Pointer)(unsafe.Pointer(&cache.buffers[i])) buf := (*[]byte)(atomic.LoadPointer(bufPtr)) if buf != nil && atomic.CompareAndSwapPointer(bufPtr, unsafe.Pointer(buf), nil) { atomic.AddInt64(&p.hitCount, 1) wasHit = true *buf = (*buf)[:0] return *buf } } } // Fallback: Try pre-allocated pool with mutex p.mutex.Lock() if len(p.preallocated) > 0 { lastIdx := len(p.preallocated) - 1 buf := p.preallocated[lastIdx] p.preallocated = p.preallocated[:lastIdx] p.mutex.Unlock() // Update hit counter atomic.AddInt64(&p.hitCount, 1) wasHit = true // Ensure buffer is properly reset *buf = (*buf)[:0] return *buf } p.mutex.Unlock() // Try sync.Pool next if poolBuf := p.pool.Get(); poolBuf != nil { buf := poolBuf.(*[]byte) // Update hit counter atomic.AddInt64(&p.hitCount, 1) // Ensure buffer is properly reset and check capacity if cap(*buf) >= p.bufferSize { wasHit = true *buf = (*buf)[:0] return *buf } else { // Buffer too small, allocate new one atomic.AddInt64(&p.missCount, 1) return make([]byte, 0, p.bufferSize) } } // Pool miss - allocate new buffer with exact capacity atomic.AddInt64(&p.missCount, 1) return make([]byte, 0, p.bufferSize) } func (p *AudioBufferPool) Put(buf []byte) { start := time.Now() defer func() { latency := time.Since(start) // Record metrics for frame pool (assuming this is the main usage) if p.bufferSize >= GetConfig().AudioFramePoolSize { GetGranularMetricsCollector().RecordFramePoolPut(latency, cap(buf)) } else { GetGranularMetricsCollector().RecordControlPoolPut(latency, cap(buf)) } }() // Validate buffer capacity - reject buffers that are too small or too large bufCap := cap(buf) if bufCap < p.bufferSize || bufCap > p.bufferSize*2 { return // Buffer size mismatch, don't pool it to prevent memory bloat } // Reset buffer for reuse - clear any sensitive data resetBuf := buf[:0] // Fast path: Try to put in lock-free per-goroutine cache gid := getGoroutineID() goroutineCacheMutex.RLock() cache, exists := goroutineBufferCache[gid] goroutineCacheMutex.RUnlock() if !exists { // Create new cache for this goroutine cache = &lockFreeBufferCache{} goroutineCacheMutex.Lock() goroutineBufferCache[gid] = cache goroutineCacheMutex.Unlock() } if cache != nil { // Try to store in lock-free cache for i := 0; i < len(cache.buffers); i++ { bufPtr := (*unsafe.Pointer)(unsafe.Pointer(&cache.buffers[i])) if atomic.CompareAndSwapPointer(bufPtr, nil, unsafe.Pointer(&buf)) { return // Successfully cached } } } // Fallback: Try to return to pre-allocated pool for fastest reuse p.mutex.Lock() if len(p.preallocated) < p.preallocSize { p.preallocated = append(p.preallocated, &resetBuf) p.mutex.Unlock() return } p.mutex.Unlock() // Check sync.Pool size limit to prevent excessive memory usage currentSize := atomic.LoadInt64(&p.currentSize) if currentSize >= int64(p.maxPoolSize) { return // Pool is full, let GC handle this buffer } // Return to sync.Pool p.pool.Put(&resetBuf) // Update pool size counter atomically atomic.AddInt64(&p.currentSize, 1) } var ( audioFramePool = NewAudioBufferPool(GetConfig().AudioFramePoolSize) audioControlPool = NewAudioBufferPool(GetConfig().OutputHeaderSize) ) func GetAudioFrameBuffer() []byte { return audioFramePool.Get() } func PutAudioFrameBuffer(buf []byte) { audioFramePool.Put(buf) } func GetAudioControlBuffer() []byte { return audioControlPool.Get() } func PutAudioControlBuffer(buf []byte) { audioControlPool.Put(buf) } // GetPoolStats returns detailed statistics about this buffer pool func (p *AudioBufferPool) GetPoolStats() AudioBufferPoolDetailedStats { p.mutex.RLock() preallocatedCount := len(p.preallocated) currentSize := p.currentSize p.mutex.RUnlock() hitCount := atomic.LoadInt64(&p.hitCount) missCount := atomic.LoadInt64(&p.missCount) totalRequests := hitCount + missCount var hitRate float64 if totalRequests > 0 { hitRate = float64(hitCount) / float64(totalRequests) * GetConfig().PercentageMultiplier } return AudioBufferPoolDetailedStats{ BufferSize: p.bufferSize, MaxPoolSize: p.maxPoolSize, CurrentPoolSize: currentSize, PreallocatedCount: int64(preallocatedCount), PreallocatedMax: int64(p.preallocSize), HitCount: hitCount, MissCount: missCount, HitRate: hitRate, } } // AudioBufferPoolDetailedStats provides detailed pool statistics type AudioBufferPoolDetailedStats struct { BufferSize int MaxPoolSize int CurrentPoolSize int64 PreallocatedCount int64 PreallocatedMax int64 HitCount int64 MissCount int64 HitRate float64 // Percentage } // GetAudioBufferPoolStats returns statistics about the audio buffer pools type AudioBufferPoolStats struct { FramePoolSize int64 FramePoolMax int ControlPoolSize int64 ControlPoolMax int // Enhanced statistics FramePoolHitRate float64 ControlPoolHitRate float64 FramePoolDetails AudioBufferPoolDetailedStats ControlPoolDetails AudioBufferPoolDetailedStats } func GetAudioBufferPoolStats() AudioBufferPoolStats { audioFramePool.mutex.RLock() frameSize := audioFramePool.currentSize frameMax := audioFramePool.maxPoolSize audioFramePool.mutex.RUnlock() audioControlPool.mutex.RLock() controlSize := audioControlPool.currentSize controlMax := audioControlPool.maxPoolSize audioControlPool.mutex.RUnlock() // Get detailed statistics frameDetails := audioFramePool.GetPoolStats() controlDetails := audioControlPool.GetPoolStats() return AudioBufferPoolStats{ FramePoolSize: frameSize, FramePoolMax: frameMax, ControlPoolSize: controlSize, ControlPoolMax: controlMax, FramePoolHitRate: frameDetails.HitRate, ControlPoolHitRate: controlDetails.HitRate, FramePoolDetails: frameDetails, ControlPoolDetails: controlDetails, } }