refactor(audio): remove redundant config variable assignments

Replace repeated local config variable assignments with direct Config access
to reduce memory allocations and improve code maintainability
This commit is contained in:
Alex P 2025-09-08 21:04:07 +00:00
parent 1d1658db15
commit 6f10010d71
3 changed files with 82 additions and 122 deletions

View File

@ -82,19 +82,16 @@ type batchWriteResult struct {
// NewBatchAudioProcessor creates a new batch audio processor
func NewBatchAudioProcessor(batchSize int, batchDuration time.Duration) *BatchAudioProcessor {
// Get cached config to avoid GetConfig() calls
cache := Config
// Validate input parameters with minimal overhead
if batchSize <= 0 || batchSize > 1000 {
batchSize = cache.BatchProcessorFramesPerBatch
batchSize = Config.BatchProcessorFramesPerBatch
}
if batchDuration <= 0 {
batchDuration = cache.BatchProcessingDelay
batchDuration = Config.BatchProcessingDelay
}
// Use optimized queue sizes from configuration
queueSize := cache.BatchProcessorMaxQueueSize
queueSize := Config.BatchProcessorMaxQueueSize
if queueSize <= 0 {
queueSize = batchSize * 2 // Fallback to double batch size
}
@ -103,8 +100,7 @@ func NewBatchAudioProcessor(batchSize int, batchDuration time.Duration) *BatchAu
// Pre-allocate logger to avoid repeated allocations
logger := logging.GetDefaultLogger().With().Str("component", "batch-audio").Logger()
// Pre-calculate frame size to avoid repeated GetConfig() calls
frameSize := cache.MinReadEncodeBuffer
frameSize := Config.MinReadEncodeBuffer
if frameSize == 0 {
frameSize = 1500 // Safe fallback
}
@ -119,13 +115,11 @@ func NewBatchAudioProcessor(batchSize int, batchDuration time.Duration) *BatchAu
writeQueue: make(chan batchWriteRequest, queueSize),
readBufPool: &sync.Pool{
New: func() interface{} {
// Use pre-calculated frame size to avoid GetConfig() calls
return make([]byte, 0, frameSize)
},
},
writeBufPool: &sync.Pool{
New: func() interface{} {
// Use pre-calculated frame size to avoid GetConfig() calls
return make([]byte, 0, frameSize)
},
},
@ -172,9 +166,6 @@ func (bap *BatchAudioProcessor) Stop() {
// BatchReadEncode performs batched audio read and encode operations
func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) {
// Get cached config to avoid GetConfig() calls in hot path
cache := Config
// Validate buffer before processing
if err := ValidateBufferSize(len(buffer)); err != nil {
// Only log validation errors in debug mode to reduce overhead
@ -219,7 +210,7 @@ func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) {
select {
case result := <-resultChan:
return result.length, result.err
case <-time.After(cache.BatchProcessorTimeout):
case <-time.After(Config.BatchProcessorTimeout):
// Timeout, fallback to single operation
// Use sampling to reduce atomic operations overhead
if atomic.LoadInt64(&bap.stats.SingleReads)%10 == 0 {
@ -233,9 +224,6 @@ func (bap *BatchAudioProcessor) BatchReadEncode(buffer []byte) (int, error) {
// BatchDecodeWrite performs batched audio decode and write operations
// This is the legacy version that uses a single buffer
func (bap *BatchAudioProcessor) BatchDecodeWrite(buffer []byte) (int, error) {
// Get cached config to avoid GetConfig() calls in hot path
cache := Config
// Validate buffer before processing
if err := ValidateBufferSize(len(buffer)); err != nil {
// Only log validation errors in debug mode to reduce overhead
@ -280,7 +268,7 @@ func (bap *BatchAudioProcessor) BatchDecodeWrite(buffer []byte) (int, error) {
select {
case result := <-resultChan:
return result.length, result.err
case <-time.After(cache.BatchProcessorTimeout):
case <-time.After(Config.BatchProcessorTimeout):
// Use sampling to reduce atomic operations overhead
if atomic.LoadInt64(&bap.stats.SingleWrites)%10 == 0 {
atomic.AddInt64(&bap.stats.SingleWrites, 10)
@ -292,9 +280,6 @@ func (bap *BatchAudioProcessor) BatchDecodeWrite(buffer []byte) (int, error) {
// BatchDecodeWriteWithBuffers performs batched audio decode and write operations with separate opus and PCM buffers
func (bap *BatchAudioProcessor) BatchDecodeWriteWithBuffers(opusData []byte, pcmBuffer []byte) (int, error) {
// Get cached config to avoid GetConfig() calls in hot path
cache := Config
// Validate buffers before processing
if len(opusData) == 0 {
return 0, fmt.Errorf("empty opus data buffer")
@ -335,7 +320,7 @@ func (bap *BatchAudioProcessor) BatchDecodeWriteWithBuffers(opusData []byte, pcm
select {
case result := <-resultChan:
return result.length, result.err
case <-time.After(cache.BatchProcessorTimeout):
case <-time.After(Config.BatchProcessorTimeout):
atomic.AddInt64(&bap.stats.SingleWrites, 1)
atomic.AddInt64(&bap.stats.WriteFrames, 1)
// Use the optimized function with separate buffers
@ -422,11 +407,9 @@ func (bap *BatchAudioProcessor) processBatchRead(batch []batchReadRequest) {
return
}
// Get cached config once - avoid repeated calls
cache := Config
threadPinningThreshold := cache.BatchProcessorThreadPinningThreshold
threadPinningThreshold := Config.BatchProcessorThreadPinningThreshold
if threadPinningThreshold == 0 {
threadPinningThreshold = cache.MinBatchSizeForThreadPinning // Fallback
threadPinningThreshold = Config.MinBatchSizeForThreadPinning // Fallback
}
// Only pin to OS thread for large batches to reduce thread contention
@ -475,11 +458,9 @@ func (bap *BatchAudioProcessor) processBatchWrite(batch []batchWriteRequest) {
return
}
// Get cached config to avoid GetConfig() calls in hot path
cache := Config
threadPinningThreshold := cache.BatchProcessorThreadPinningThreshold
threadPinningThreshold := Config.BatchProcessorThreadPinningThreshold
if threadPinningThreshold == 0 {
threadPinningThreshold = cache.MinBatchSizeForThreadPinning // Fallback
threadPinningThreshold = Config.MinBatchSizeForThreadPinning // Fallback
}
// Only pin to OS thread for large batches to reduce thread contention
@ -581,10 +562,7 @@ func GetBatchAudioProcessor() *BatchAudioProcessor {
// Initialize on first use
if atomic.CompareAndSwapInt32(&batchProcessorInitialized, 0, 1) {
// Get cached config to avoid GetConfig() calls
cache := Config
processor := NewBatchAudioProcessor(cache.BatchProcessorFramesPerBatch, cache.BatchProcessorTimeout)
processor := NewBatchAudioProcessor(Config.BatchProcessorFramesPerBatch, Config.BatchProcessorTimeout)
atomic.StorePointer(&globalBatchProcessor, unsafe.Pointer(processor))
return processor
}
@ -596,8 +574,7 @@ func GetBatchAudioProcessor() *BatchAudioProcessor {
}
// Fallback: create a new processor (should rarely happen)
config := Config
return NewBatchAudioProcessor(config.BatchProcessorFramesPerBatch, config.BatchProcessorTimeout)
return NewBatchAudioProcessor(Config.BatchProcessorFramesPerBatch, Config.BatchProcessorTimeout)
}
// EnableBatchAudioProcessing enables the global batch processor

View File

@ -30,21 +30,21 @@ static snd_pcm_t *pcm_playback_handle = NULL;
static OpusEncoder *encoder = NULL;
static OpusDecoder *decoder = NULL;
// Opus encoder settings - initialized from Go configuration
static int opus_bitrate = 96000; // Will be set from GetConfig().CGOOpusBitrate
static int opus_complexity = 3; // Will be set from GetConfig().CGOOpusComplexity
static int opus_vbr = 1; // Will be set from GetConfig().CGOOpusVBR
static int opus_vbr_constraint = 1; // Will be set from GetConfig().CGOOpusVBRConstraint
static int opus_signal_type = 3; // Will be set from GetConfig().CGOOpusSignalType
static int opus_bitrate = 96000; // Will be set from Config.CGOOpusBitrate
static int opus_complexity = 3; // Will be set from Config.CGOOpusComplexity
static int opus_vbr = 1; // Will be set from Config.CGOOpusVBR
static int opus_vbr_constraint = 1; // Will be set from Config.CGOOpusVBRConstraint
static int opus_signal_type = 3; // Will be set from Config.CGOOpusSignalType
static int opus_bandwidth = 1105; // OPUS_BANDWIDTH_WIDEBAND for compatibility (was 1101)
static int opus_dtx = 0; // Will be set from GetConfig().CGOOpusDTX
static int opus_dtx = 0; // Will be set from Config.CGOOpusDTX
static int opus_lsb_depth = 16; // LSB depth for improved bit allocation on constrained hardware
static int sample_rate = 48000; // Will be set from GetConfig().CGOSampleRate
static int channels = 2; // Will be set from GetConfig().CGOChannels
static int frame_size = 960; // Will be set from GetConfig().CGOFrameSize
static int max_packet_size = 1500; // Will be set from GetConfig().CGOMaxPacketSize
static int sleep_microseconds = 1000; // Will be set from GetConfig().CGOUsleepMicroseconds
static int max_attempts_global = 5; // Will be set from GetConfig().CGOMaxAttempts
static int max_backoff_us_global = 500000; // Will be set from GetConfig().CGOMaxBackoffMicroseconds
static int sample_rate = 48000; // Will be set from Config.CGOSampleRate
static int channels = 2; // Will be set from Config.CGOChannels
static int frame_size = 960; // Will be set from Config.CGOFrameSize
static int max_packet_size = 1500; // Will be set from Config.CGOMaxPacketSize
static int sleep_microseconds = 1000; // Will be set from Config.CGOUsleepMicroseconds
static int max_attempts_global = 5; // Will be set from Config.CGOMaxAttempts
static int max_backoff_us_global = 500000; // Will be set from Config.CGOMaxBackoffMicroseconds
// Hardware optimization flags for constrained environments
static int use_mmap_access = 0; // Disable MMAP for compatibility (was 1)
static int optimized_buffer_size = 0; // Disable optimized buffer sizing for stability (was 1)
@ -709,9 +709,9 @@ func cgoAudioInit() error {
C.int(cache.channels.Load()),
C.int(cache.frameSize.Load()),
C.int(cache.maxPacketSize.Load()),
C.int(GetConfig().CGOUsleepMicroseconds),
C.int(GetConfig().CGOMaxAttempts),
C.int(GetConfig().CGOMaxBackoffMicroseconds),
C.int(Config.CGOUsleepMicroseconds),
C.int(Config.CGOMaxAttempts),
C.int(Config.CGOMaxBackoffMicroseconds),
)
result := C.jetkvm_audio_init()
@ -726,7 +726,6 @@ func cgoAudioClose() {
}
// AudioConfigCache provides a comprehensive caching system for audio configuration
// to minimize GetConfig() calls in the hot path
type AudioConfigCache struct {
// Atomic int64 fields MUST be first for ARM32 alignment (8-byte alignment required)
minFrameDuration atomic.Int64 // Store as nanoseconds
@ -815,52 +814,50 @@ func (c *AudioConfigCache) Update() {
// Double-check after acquiring lock
if !c.initialized.Load() || time.Since(c.lastUpdate) > c.cacheExpiry {
config := GetConfig() // Call GetConfig() only once
// Update atomic values for lock-free access - CGO values
c.minReadEncodeBuffer.Store(int32(config.MinReadEncodeBuffer))
c.maxDecodeWriteBuffer.Store(int32(config.MaxDecodeWriteBuffer))
c.maxPacketSize.Store(int32(config.CGOMaxPacketSize))
c.maxPCMBufferSize.Store(int32(config.MaxPCMBufferSize))
c.opusBitrate.Store(int32(config.CGOOpusBitrate))
c.opusComplexity.Store(int32(config.CGOOpusComplexity))
c.opusVBR.Store(int32(config.CGOOpusVBR))
c.opusVBRConstraint.Store(int32(config.CGOOpusVBRConstraint))
c.opusSignalType.Store(int32(config.CGOOpusSignalType))
c.opusBandwidth.Store(int32(config.CGOOpusBandwidth))
c.opusDTX.Store(int32(config.CGOOpusDTX))
c.sampleRate.Store(int32(config.CGOSampleRate))
c.channels.Store(int32(config.CGOChannels))
c.frameSize.Store(int32(config.CGOFrameSize))
c.minReadEncodeBuffer.Store(int32(Config.MinReadEncodeBuffer))
c.maxDecodeWriteBuffer.Store(int32(Config.MaxDecodeWriteBuffer))
c.maxPacketSize.Store(int32(Config.CGOMaxPacketSize))
c.maxPCMBufferSize.Store(int32(Config.MaxPCMBufferSize))
c.opusBitrate.Store(int32(Config.CGOOpusBitrate))
c.opusComplexity.Store(int32(Config.CGOOpusComplexity))
c.opusVBR.Store(int32(Config.CGOOpusVBR))
c.opusVBRConstraint.Store(int32(Config.CGOOpusVBRConstraint))
c.opusSignalType.Store(int32(Config.CGOOpusSignalType))
c.opusBandwidth.Store(int32(Config.CGOOpusBandwidth))
c.opusDTX.Store(int32(Config.CGOOpusDTX))
c.sampleRate.Store(int32(Config.CGOSampleRate))
c.channels.Store(int32(Config.CGOChannels))
c.frameSize.Store(int32(Config.CGOFrameSize))
// Update additional validation values
c.maxAudioFrameSize.Store(int32(config.MaxAudioFrameSize))
c.maxChannels.Store(int32(config.MaxChannels))
c.minFrameDuration.Store(int64(config.MinFrameDuration))
c.maxFrameDuration.Store(int64(config.MaxFrameDuration))
c.minOpusBitrate.Store(int32(config.MinOpusBitrate))
c.maxOpusBitrate.Store(int32(config.MaxOpusBitrate))
c.maxAudioFrameSize.Store(int32(Config.MaxAudioFrameSize))
c.maxChannels.Store(int32(Config.MaxChannels))
c.minFrameDuration.Store(int64(Config.MinFrameDuration))
c.maxFrameDuration.Store(int64(Config.MaxFrameDuration))
c.minOpusBitrate.Store(int32(Config.MinOpusBitrate))
c.maxOpusBitrate.Store(int32(Config.MaxOpusBitrate))
// Update batch processing related values
c.BatchProcessingTimeout = 100 * time.Millisecond // Fixed timeout for batch processing
c.BatchProcessorFramesPerBatch = config.BatchProcessorFramesPerBatch
c.BatchProcessorTimeout = config.BatchProcessorTimeout
c.BatchProcessingDelay = config.BatchProcessingDelay
c.MinBatchSizeForThreadPinning = config.MinBatchSizeForThreadPinning
c.BatchProcessorMaxQueueSize = config.BatchProcessorMaxQueueSize
c.BatchProcessorAdaptiveThreshold = config.BatchProcessorAdaptiveThreshold
c.BatchProcessorThreadPinningThreshold = config.BatchProcessorThreadPinningThreshold
c.BatchProcessorFramesPerBatch = Config.BatchProcessorFramesPerBatch
c.BatchProcessorTimeout = Config.BatchProcessorTimeout
c.BatchProcessingDelay = Config.BatchProcessingDelay
c.MinBatchSizeForThreadPinning = Config.MinBatchSizeForThreadPinning
c.BatchProcessorMaxQueueSize = Config.BatchProcessorMaxQueueSize
c.BatchProcessorAdaptiveThreshold = Config.BatchProcessorAdaptiveThreshold
c.BatchProcessorThreadPinningThreshold = Config.BatchProcessorThreadPinningThreshold
// Pre-allocate common errors
c.bufferTooSmallReadEncode = newBufferTooSmallError(0, config.MinReadEncodeBuffer)
c.bufferTooLargeDecodeWrite = newBufferTooLargeError(config.MaxDecodeWriteBuffer+1, config.MaxDecodeWriteBuffer)
c.bufferTooSmallReadEncode = newBufferTooSmallError(0, Config.MinReadEncodeBuffer)
c.bufferTooLargeDecodeWrite = newBufferTooLargeError(Config.MaxDecodeWriteBuffer+1, Config.MaxDecodeWriteBuffer)
c.lastUpdate = time.Now()
c.initialized.Store(true)
// Update the global validation cache as well
if cachedMaxFrameSize != 0 {
cachedMaxFrameSize = config.MaxAudioFrameSize
cachedMaxFrameSize = Config.MaxAudioFrameSize
}
}
}

View File

@ -87,13 +87,11 @@ func ValidateBufferSize(size int) error {
return nil
}
// Slower path: full validation against SocketMaxBuffer
config := Config
// Use SocketMaxBuffer as the upper limit for general buffer validation
// This allows for socket buffers while still preventing extremely large allocations
if size > config.SocketMaxBuffer {
if size > Config.SocketMaxBuffer {
return fmt.Errorf("%w: buffer size %d exceeds maximum %d",
ErrInvalidBufferSize, size, config.SocketMaxBuffer)
ErrInvalidBufferSize, size, Config.SocketMaxBuffer)
}
return nil
}
@ -123,16 +121,14 @@ func ValidateLatency(latency time.Duration) error {
return nil
}
// Slower path: full validation with GetConfig()
config := Config
minLatency := time.Millisecond // Minimum reasonable latency
if latency > 0 && latency < minLatency {
return fmt.Errorf("%w: latency %v below minimum %v",
ErrInvalidLatency, latency, minLatency)
}
if latency > config.MaxLatency {
if latency > Config.MaxLatency {
return fmt.Errorf("%w: latency %v exceeds maximum %v",
ErrInvalidLatency, latency, config.MaxLatency)
ErrInvalidLatency, latency, Config.MaxLatency)
}
return nil
}
@ -158,10 +154,8 @@ func ValidateMetricsInterval(interval time.Duration) error {
return nil
}
// Slower path: full validation with GetConfig()
config := Config
minInterval = config.MinMetricsUpdateInterval
maxInterval = config.MaxMetricsUpdateInterval
minInterval = Config.MinMetricsUpdateInterval
maxInterval = Config.MaxMetricsUpdateInterval
if interval < minInterval {
return ErrInvalidMetricsInterval
}
@ -192,11 +186,9 @@ func ValidateAdaptiveBufferConfig(minSize, maxSize, defaultSize int) error {
// ValidateInputIPCConfig validates input IPC configuration
func ValidateInputIPCConfig(sampleRate, channels, frameSize int) error {
// Use config values
config := Config
minSampleRate := config.MinSampleRate
maxSampleRate := config.MaxSampleRate
maxChannels := config.MaxChannels
minSampleRate := Config.MinSampleRate
maxSampleRate := Config.MaxSampleRate
maxChannels := Config.MaxChannels
if sampleRate < minSampleRate || sampleRate > maxSampleRate {
return ErrInvalidSampleRate
}
@ -211,11 +203,9 @@ func ValidateInputIPCConfig(sampleRate, channels, frameSize int) error {
// ValidateOutputIPCConfig validates output IPC configuration
func ValidateOutputIPCConfig(sampleRate, channels, frameSize int) error {
// Use config values
config := Config
minSampleRate := config.MinSampleRate
maxSampleRate := config.MaxSampleRate
maxChannels := config.MaxChannels
minSampleRate := Config.MinSampleRate
maxSampleRate := Config.MaxSampleRate
maxChannels := Config.MaxChannels
if sampleRate < minSampleRate || sampleRate > maxSampleRate {
return ErrInvalidSampleRate
}
@ -236,7 +226,7 @@ func ValidateLatencyConfig(config LatencyConfig) error {
if err := ValidateLatency(config.MaxLatency); err != nil {
return err
}
if config.TargetLatency >= config.MaxLatency {
if config.TargetLatency >= Config.MaxLatency {
return ErrInvalidLatency
}
if err := ValidateMetricsInterval(config.OptimizationInterval); err != nil {
@ -271,8 +261,7 @@ func ValidateSampleRate(sampleRate int) error {
}
// Slower path: check against all valid rates
config := Config
validRates := config.ValidSampleRates
validRates := Config.ValidSampleRates
for _, rate := range validRates {
if sampleRate == rate {
return nil
@ -340,17 +329,15 @@ func ValidateBitrate(bitrate int) error {
return nil
}
// Slower path: full validation with GetConfig()
config := Config
// Convert kbps to bps for comparison with config limits
bitrateInBps := bitrate * 1000
if bitrateInBps < config.MinOpusBitrate {
if bitrateInBps < Config.MinOpusBitrate {
return fmt.Errorf("%w: bitrate %d kbps (%d bps) below minimum %d bps",
ErrInvalidBitrate, bitrate, bitrateInBps, config.MinOpusBitrate)
ErrInvalidBitrate, bitrate, bitrateInBps, Config.MinOpusBitrate)
}
if bitrateInBps > config.MaxOpusBitrate {
if bitrateInBps > Config.MaxOpusBitrate {
return fmt.Errorf("%w: bitrate %d kbps (%d bps) exceeds maximum %d bps",
ErrInvalidBitrate, bitrate, bitrateInBps, config.MaxOpusBitrate)
ErrInvalidBitrate, bitrate, bitrateInBps, Config.MaxOpusBitrate)
}
return nil
}
@ -462,11 +449,11 @@ func ValidateAudioConfigConstants(config *AudioConfigConstants) error {
}
// Validate configuration values if config is provided
if config != nil {
if config.MaxFrameSize <= 0 {
return fmt.Errorf("invalid MaxFrameSize: %d", config.MaxFrameSize)
if Config.MaxFrameSize <= 0 {
return fmt.Errorf("invalid MaxFrameSize: %d", Config.MaxFrameSize)
}
if config.SampleRate <= 0 {
return fmt.Errorf("invalid SampleRate: %d", config.SampleRate)
if Config.SampleRate <= 0 {
return fmt.Errorf("invalid SampleRate: %d", Config.SampleRate)
}
}
return nil
@ -478,8 +465,7 @@ var cachedMaxFrameSize int
// InitValidationCache initializes cached validation values with actual config
func InitValidationCache() {
// Initialize the global cache variable for backward compatibility
config := Config
cachedMaxFrameSize = config.MaxAudioFrameSize
cachedMaxFrameSize = Config.MaxAudioFrameSize
// Initialize the global audio config cache
cachedMaxFrameSize = Config.MaxAudioFrameSize