This document outlines a comprehensive performance optimization plan for the VS Code StatusBar Quick Actions Extension, targeting sub-100ms execution times for core operations and eliminating memory leaks and resource consumption issues.
- Problem: Synchronous manager initialization blocks extension activation
- Impact: 200-500ms startup time for complex configurations
- Root Cause: Sequential initialization and redundant configuration reads
- Problem:
getConfig()called 15-20 times per operation - Impact: 50-100ms overhead per button operation
- Root Cause: No configuration caching and frequent validation
- Problem:
updateConfiguration()recreates all buttons on any change - Impact: 100-300ms for 10+ buttons
- Root Cause: Full recreation vs incremental updates
- Problem: Processes all buttons regardless of visibility conditions
- Impact: 20-50ms per editor change
- Root Cause: Inefficient filtering and redundant checks
- Problem: Cache entries never expire, intervals accumulate
- Impact: Memory growth of 2-5MB per hour
- Root Cause: Inadequate cleanup and infinite growth patterns
- Problem: Package manager detection runs every time
- Impact: 30-80ms per command execution
- Root Cause: No command result or package manager caching
| Operation | Current Time | Target Time | Performance Gap |
|---|---|---|---|
| Extension Activation | 300-800ms | <100ms | 3-8x slower |
| Button Creation | 50-150ms | <20ms | 2.5-7.5x slower |
| Configuration Update | 100-300ms | <30ms | 3-10x slower |
| Command Execution | 200-500ms | <100ms | 2-5x slower |
| Editor Change Processing | 20-50ms | <5ms | 4-10x slower |
| Dynamic Label Update | 100-300ms | <50ms | 2-6x slower |
// Current: Synchronous initialization
await this.initializeManagers();
// Optimized: Staged async initialization
await this.initializeCriticalManagers(); // Fast (<50ms)
setImmediate(() => this.initializeNonCriticalManagers()); // Deferred// Current: Immediate git extension access
const git = gitExtension.exports.getAPI(1);
// Optimized: Lazy access with caching
private async getGitAPI(): Promise<any> {
if (this.gitApiCache) return this.gitApiCache;
const gitExt = vscode.extensions.getExtension("vscode.git");
if (!gitExt) throw new Error("Git extension not available");
this.gitApiCache = await gitExt.activate();
return this.gitApiCache;
}// Cache configuration with TTL
private configCache: Map<string, { data: ExtensionConfig; timestamp: number }> = new Map();
private readonly CACHE_TTL = 30000; // 30 seconds
public getConfig(): ExtensionConfig {
const cached = this.configCache.get('main');
if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
return cached.data;
}
const config = this.loadConfig();
this.configCache.set('main', { data: config, timestamp: Date.now() });
return config;
}// Cache successful command results
private commandCache: Map<string, ExecutionResult> = new Map();
private readonly CACHE_TTL = 60000; // 1 minute
public async execute(command: ButtonCommand, options: ExecutionOptions): Promise<ExecutionResult> {
const cacheKey = this.getCacheKey(command, options);
if (!options.force && this.commandCache.has(cacheKey)) {
const cached = this.commandCache.get(cacheKey)!;
if (Date.now() - cached.timestamp.getTime() < this.CACHE_TTL) {
return cached;
}
}
const result = await this.executeCommand(command, options);
if (result.code === 0) {
this.commandCache.set(cacheKey, result);
}
return result;
}// Current: Recreate all buttons
private async updateConfiguration(config: ExtensionConfig): Promise<void> {
// Remove all existing buttons
this.buttonStates.forEach((state) => state.item.dispose());
this.buttonStates.clear();
// Create all new buttons
for (const buttonConfig of config.buttons) {
await this.createStatusBarItem(buttonConfig);
}
}
// Optimized: Incremental updates
private async updateConfiguration(config: ExtensionConfig): Promise<void> {
const currentIds = new Set(this.buttonStates.keys());
const newIds = new Set(config.buttons.map(b => b.id));
// Remove deleted buttons
for (const buttonId of currentIds) {
if (!newIds.has(buttonId)) {
this.removeButton(buttonId);
}
}
// Add new buttons
for (const buttonConfig of config.buttons) {
if (!currentIds.has(buttonConfig.id)) {
await this.createStatusBarItem(buttonConfig);
} else {
await this.updateButton(buttonConfig);
}
}
}// Current: Process all buttons
this.buttonStates.forEach((buttonState, buttonId) => {
if (!buttonState.config.visibility) return; // Skip visibility check
// ... process button
});
// Optimized: Pre-filtered processing
private getButtonsWithVisibilityConditions(): Map<string, ButtonState> {
const result = new Map<string, ButtonState>();
this.buttonStates.forEach((buttonState, buttonId) => {
if (buttonState.config.visibility) {
result.set(buttonId, buttonState);
}
});
return result;
}// Optimized editor change handler
private setupOptimizedEditorChangeListener(): void {
const processEditorChange = debounce(() => {
const buttonsWithVisibility = this.getButtonsWithVisibilityConditions();
const context = this.visibilityManager.getCurrentContext();
// Batch process visibility checks
buttonsWithVisibility.forEach((buttonState, buttonId) => {
const cached = this.visibilityManager.getCachedVisibility(buttonId);
if (cached !== undefined) {
this.updateButtonVisibility(buttonId, cached);
}
});
}, 100);
this.editorChangeListener = vscode.window.onDidChangeActiveTextEditor(() => {
processEditorChange();
});
}// Automatic cleanup with weak references
private cleanupManager: CacheCleanupManager;
public setupAutomaticCleanup(): void {
// Clean up every 30 seconds instead of 5 minutes
this.cleanupManager = new CacheCleanupManager([
{ cache: this.commandCache, maxAge: 300000 }, // 5 minutes
{ cache: this.visibilityCheckCache, maxAge: 60000 }, // 1 minute
{ cache: this.configCache, maxAge: 30000 }, // 30 seconds
]);
setInterval(() => this.cleanupManager.cleanup(), 30000);
}// Resource pooling for frequent operations
export class ResourcePool<T> {
private pool: T[] = [];
private active: Set<T> = new Set();
constructor(
private factory: () => T,
private reset: (item: T) => void,
private maxSize: number = 10,
) {}
public acquire(): T {
let item = this.pool.pop();
if (!item) {
item = this.factory();
}
this.active.add(item);
return item;
}
public release(item: T): void {
this.active.delete(item);
this.reset(item);
if (this.pool.length < this.maxSize) {
this.pool.push(item);
}
}
}export class PerformanceMonitor {
private metrics: Map<string, number[]> = new Map();
private readonly MAX_SAMPLES = 100;
public startTimer(operation: string): () => void {
const start = Date.now();
return () => {
const duration = Date.now() - start;
this.recordMetric(operation, duration);
};
}
public recordMetric(operation: string, duration: number): void {
if (!this.metrics.has(operation)) {
this.metrics.set(operation, []);
}
const samples = this.metrics.get(operation)!;
samples.push(duration);
if (samples.length > this.MAX_SAMPLES) {
samples.shift();
}
// Log performance warnings
if (duration > this.getThreshold(operation)) {
console.warn(`Performance warning: ${operation} took ${duration}ms`);
}
}
public getMetrics(operation: string): {
average: number;
median: number;
p95: number;
p99: number;
} {
const samples = this.metrics.get(operation) || [];
if (samples.length === 0) {
return { average: 0, median: 0, p95: 0, p99: 0 };
}
const sorted = [...samples].sort((a, b) => a - b);
const avg = samples.reduce((a, b) => a + b, 0) / samples.length;
return {
average: Math.round(avg),
median: sorted[Math.floor(sorted.length / 2)],
p95: sorted[Math.floor(sorted.length * 0.95)],
p99: sorted[Math.floor(sorted.length * 0.99)],
};
}
private getThreshold(operation: string): number {
const thresholds: Record<string, number> = {
extension_activation: 100,
button_creation: 20,
configuration_update: 30,
command_execution: 100,
editor_change: 5,
dynamic_label: 50,
};
return thresholds[operation] || 100;
}
}- Configuration Caching: Implement TTL-based configuration caching
- Async Manager Initialization: Split critical/non-critical initialization
- Incremental Button Updates: Implement diff-based button updates
- Memory Management: Add automatic cleanup and resource pooling
- Filtered Event Processing: Optimize editor change handling
- Batch Processing: Implement debounced batch operations
- Command Result Caching: Add intelligent command caching
- Visibility Optimization: Optimize visibility checking algorithms
- Performance Monitoring: Implement comprehensive metrics collection
- Benchmark Suite: Create performance regression tests
- Memory Profiling: Implement memory leak detection
- Documentation: Update performance documentation
- Performance Tuning: Fine-tune based on metrics
- Backward Compatibility: Ensure no breaking changes
- User Testing: Validate performance improvements
- Final Documentation: Complete performance guide
| Optimization Area | Current Performance | Target Performance | Improvement Factor |
|---|---|---|---|
| Extension Activation | 300-800ms | 50-100ms | 6-8x faster |
| Button Creation | 50-150ms | 5-20ms | 3-30x faster |
| Configuration Updates | 100-300ms | 10-30ms | 3-30x faster |
| Command Execution | 200-500ms | 50-100ms | 2-10x faster |
| Editor Change Processing | 20-50ms | 1-5ms | 4-50x faster |
| Memory Usage | 2-5MB/hour growth | <1MB/hour growth | 4-5x improvement |
- Performance Targets: All core operations complete in <100ms
- Memory Stability: No memory leaks, stable memory usage over time
- User Experience: Noticeable improvement in responsiveness
- Resource Efficiency: Reduced CPU and memory consumption
- Backward Compatibility: No breaking changes for existing users
- Monitoring: Real-time performance metrics and alerting
- Configuration caching (well-established pattern)
- Async initialization (VS Code best practice)
- Memory cleanup (standard patterns)
- Incremental button updates (requires careful testing)
- Command result caching (complex invalidation logic)
- Comprehensive test suite for all optimizations
- Feature flags for gradual rollout
- Performance monitoring to catch regressions
- User feedback collection for validation
This optimization plan addresses the major performance bottlenecks in the VS Code StatusBar Quick Actions Extension. The combination of caching, async patterns, incremental updates, and intelligent resource management should achieve the target performance of sub-100ms execution times for all core operations while maintaining backward compatibility and providing robust performance monitoring.
The phased implementation approach ensures stable delivery and allows for continuous monitoring and adjustment based on real-world performance data.