| name | performance-analyzer |
| description | Analyze and optimize VoiceLite performance including RAM usage, CPU utilization, transcription latency, and memory leaks. Activates when performance targets are violated or optimization is needed. |
Performance Analyzer
Monitor and optimize VoiceLite performance against strict targets.
When This Skill Activates
- Keywords: "slow", "memory leak", "high CPU", "performance", "optimization"
- Metrics mentioned: RAM, CPU, latency, FPS, responsiveness
- Violations: ">100MB idle", ">300MB active", ">5% CPU idle", "slow transcription"
- After changes to: AudioRecorder, PersistentWhisperService, MainWindow
- User reports: "App is slow", "High memory usage", "Laggy UI"
VoiceLite Performance Targets
From CLAUDE.md - these are STRICT requirements:
| Metric | Target | Failure Threshold | How to Measure |
|---|
| Idle RAM | <100MB | >150MB | Task Manager or dotMemory after 30s idle |
| Active RAM | <300MB | >400MB | During transcription (peak usage) |
| Idle CPU | <5% | >10% | Task Manager (30s average) |
| Transcription Latency | <200ms | >500ms | Time from speech stop to text injection |
| Whisper Processing (tiny) | <0.8s | >2s | 5-second audio sample |
| Whisper Processing (small) | <3s | >8s | 5-second audio sample |
| Startup Time | <2s | >5s | From launch to tray icon visible |
All targets are non-negotiable - VoiceLite must be lightweight and responsive.
Quick Performance Check
Manual Task Manager Method
# 1. Launch VoiceLite.exe
# 2. Wait 30 seconds (reach idle state)
# 3. Open Task Manager (Ctrl+Shift+Esc)
# - Details tab → Find VoiceLite.exe
# - Check "Memory (Private Working Set)"
# ✓ Should be <100MB
# ✗ If >150MB → Memory leak investigation needed
# 4. Check CPU usage over 30 seconds
# ✓ Should average <5%
# ✗ If >10% → CPU profiling needed
# 5. Test transcription
# - Record 5 seconds of audio
# - Check memory spike
# ✓ Should stay <300MB
# ✗ If >400MB → Audio buffer leak
# 6. Check whisper process
# - After recording, check whisper.exe appears briefly
# - Should disappear after <2s for tiny model
# ✗ If lingers → Zombie process (disposal issue)
Automated Performance Tests
[Fact]
public async Task TranscriptionLatency_ShouldBeLessThan200ms()
{
var recorder = new AudioRecorder();
var whisper = new PersistentWhisperService();
var stopwatch = Stopwatch.StartNew();
var audio = await recorder.Record5Seconds();
var text = await whisper.TranscribeAsync(audio);
stopwatch.Stop();
stopwatch.ElapsedMilliseconds.Should().BeLessThan(200);
}
[Fact]
public void IdleMemory_ShouldBeLessThan100MB()
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var memoryMB = GC.GetTotalMemory(false) / 1024 / 1024;
memoryMB.Should().BeLessThan(100);
}
[Fact]
public async Task WhisperProcessing_TinyModel_ShouldBeLessThan800ms()
{
var service = new PersistentWhisperService("ggml-tiny.bin");
var test5sAudio = "test_audio_5s.wav";
var stopwatch = Stopwatch.StartNew();
await service.TranscribeAsync(test5sAudio);
stopwatch.Stop();
stopwatch.ElapsedMilliseconds.Should().BeLessThan();
}
Memory Profiling with dotMemory
Installation
Profiling Workflow
Red Flags in dotMemory
Growing byte[] arrays:
Snapshot 1: 1.5 MB byte[]
Snapshot 2: 15.2 MB byte[]
→ Audio buffers not being released
→ Check AudioRecorder.Dispose() called
→ Verify _audioBuffer cleared after use
Growing Process objects:
Snapshot 1: 0 Process instances
Snapshot 2: 10 Process instances
→ Whisper.exe processes not disposed
→ Check PersistentWhisperService.TranscribeAsync finally block
→ Ensure process?.Dispose() called
Growing event subscriptions:
Snapshot 1: 5 event handlers
Snapshot 2: 50 event handlers
→ Events subscribed but never unsubscribed
→ Check -= in Dispose methods
→ Example: _audioRecorder.AudioFileReady -= OnAudioFileReady
CPU Profiling with PerfView
Collect CPU Trace
PerfView.exe collect -ThreadTime
Analyzing CPU Hotspots
Look for:
- High % in specific method → Optimization target
- Tight loops → Potential infinite loop or inefficiency
- Excessive GC → Too many allocations
- Blocking I/O on UI thread → Move to background
Example findings:
Method: AudioRecorder.ProcessBuffer - 45% CPU
→ Buffer processing inefficient
→ Optimization: Use Span<byte> instead of byte[]
Method: GC.Collect - 15% CPU
→ Too many allocations
→ Optimization: Reuse buffers, reduce new object creation
Common Performance Issues
Issue 1: Memory Leak in AudioRecorder
Symptom: RAM grows from 50MB → 200MB+ after 20 recordings
Diagnosis:
public class AudioRecorder : IDisposable
{
public void Dispose()
{
Console.WriteLine("AudioRecorder.Dispose called");
_waveIn?.Dispose();
_waveIn = null;
}
}
using (var recorder = new AudioRecorder())
{
await recorder.RecordAsync();
}
Fix: Ensure using statement or explicit Dispose() call
Issue 2: Whisper Zombie Processes
Symptom: Multiple whisper.exe processes remain after transcription
Diagnosis:
tasklist | findstr whisper.exe
Fix:
public async Task<string> TranscribeAsync(string path)
{
Process? process = null;
try
{
process = new Process { };
process.Start();
}
finally
{
process?.Kill();
process?.Dispose();
}
}
Issue 3: UI Thread Blocking
Symptom: UI freezes during transcription
Diagnosis:
private void OnRecordButtonClick(object sender, RoutedEventArgs e)
{
var result = _whisperService.TranscribeAsync(audio).Result;
var result = await _whisperService.TranscribeAsync(audio);
}
Fix: Use async/await throughout, never .Result or .Wait()
Issue 4: Large Audio Buffer Allocations
Symptom: Memory spikes during recording
Optimization:
private void OnDataAvailable(object sender, WaveInEventArgs e)
{
var buffer = new byte[e.BytesRecorded];
Buffer.BlockCopy(e.Buffer, 0, buffer, 0, e.BytesRecorded);
}
private readonly byte[] _reusableBuffer = new byte[8192];
private void OnDataAvailable(object sender, WaveInEventArgs e)
{
Buffer.BlockCopy(e.Buffer, 0, _reusableBuffer, 0, e.BytesRecorded);
}
Whisper Performance Optimization
Command-Line Flags (v1.0.85-88 Optimizations)
whisper.exe \
-m ggml-tiny.bin \
-f audio.wav \
--no-timestamps \
--language en \
--beam-size 1 \
--best-of 1 \
--entropy-thold 3.0 \
--no-fallback \
--max-context 64 \
--flash-attn
whisper.exe -m ggml-tiny.bin -f audio.wav --no-timestamps --language en
Model Performance Comparison
| Model | Size (Q8_0) | Accuracy | Processing Time (5s audio) |
|---|
| ggml-tiny.bin | 42MB | 80-85% | 0.7s ✅ |
| ggml-base.bin | 78MB | 85-90% | 1.5s ✅ |
| ggml-small.bin | 253MB | 90-93% | 3s ✅ |
| ggml-medium.bin | 1.5GB | 95-97% | 12s ⚠️ |
| ggml-large-v3.bin | 1.6GB | 97-98% | 8s ⚠️ |
Performance Regression Testing
CI/CD Performance Tests
dotnet test --filter "Category=Performance"
PERFORMANCE_STRICT=true dotnet test
Benchmarking Script
# benchmark.ps1
$iterations = 10
$results = @()
for ($i = 0; $i -lt $iterations; $i++) {
$sw = [Diagnostics.Stopwatch]::StartNew()
# Test transcription
./VoiceLite/bin/Release/VoiceLite.exe --test-transcribe test_5s.wav
$sw.Stop()
$results += $sw.ElapsedMilliseconds
}
$avg = ($results | Measure-Object -Average).Average
Write-Host "Average transcription time: $avg ms"
if ($avg -gt 800) {
Write-Error "Performance regression! Target: <800ms, Actual: $avg ms"
exit 1
}
Optimization Checklist
Before releasing new version:
Tools Summary
| Tool | Purpose | When to Use |
|---|
| Task Manager | Quick RAM/CPU check | Every build, quick validation |
| dotMemory | Memory leak detection | After major changes, before release |
| PerfView | CPU profiling | When CPU >10%, optimization needed |
| Performance Tests | Automated regression detection | CI/CD, every commit |
| Benchmark Script | Transcription speed validation | After whisper command changes |
Historical Performance Improvements
v1.0.84 → v1.0.88 (67-73% faster):
- Added
--entropy-thold, --no-fallback, optimal threads (40% faster)
- Upgraded whisper.cpp v1.6.0 → v1.7.6 (20-40% faster)
- Added
--flash-attn (7-12% faster)
- Q8_0 quantization (same speed, 45% smaller)
Target: Maintain sub-800ms transcription for tiny model