スキル一覧に戻る
YosrBennagra

performance

by YosrBennagra

1🍴 0📅 2026年1月21日
GitHubで見るManusで実行

SKILL.md


name: performance description: Performance optimization patterns for the 3SC widget host. Covers startup time, memory management, UI responsiveness, profiling, and optimization strategies.

Performance

Overview

A performant desktop application starts quickly, responds immediately to user input, and uses resources efficiently. This skill covers performance patterns for WPF applications.

Definition of Done (DoD)

  • Application starts in under 3 seconds on typical hardware
  • UI remains responsive during background operations
  • Memory usage stays bounded during normal use
  • No memory leaks after extended use
  • Large operations show progress feedback
  • Performance-critical paths are profiled and optimized

Performance Targets

MetricTargetMeasurement
Cold startup< 3sTime to usable shell
Warm startup< 1sSubsequent launches
UI response< 100msInput to visual feedback
Frame rate60 FPSDuring animations
Memory (idle)< 150MBAfter startup
Memory (active)< 500MBWith 10+ widgets

Startup Optimization

Lazy Initialization

public sealed class ServiceLocator
{
    // ✅ GOOD - Defer expensive initialization
    private readonly Lazy<AppDbContext> _dbContext;
    private readonly Lazy<IWorkshopSyncService> _syncService;
    
    private ServiceLocator()
    {
        // Database context created on first use
        _dbContext = new Lazy<AppDbContext>(() =>
        {
            var options = CreateDbOptions();
            var context = new AppDbContext(options);
            context.Database.EnsureCreated();
            return context;
        });
        
        // Sync service not needed until user requests
        _syncService = new Lazy<IWorkshopSyncService>(() =>
            new WorkshopSyncService(/* deps */));
    }
}

Startup Timing

public class StartupTimer
{
    public static StartupTimer Instance { get; } = new();
    
    private readonly Stopwatch _stopwatch = new();
    private readonly List<(string Name, long ElapsedMs)> _checkpoints = new();
    private string _currentPhase = "Init";
    
    public void Start() => _stopwatch.Start();
    
    public void BeginPhase(string phase)
    {
        Checkpoint($"{_currentPhase}Complete");
        _currentPhase = phase;
    }
    
    public void Checkpoint(string name)
    {
        _checkpoints.Add((name, _stopwatch.ElapsedMilliseconds));
    }
    
    public void Complete()
    {
        _stopwatch.Stop();
        Checkpoint("StartupComplete");
        
        Log.Information("Startup completed in {TotalMs}ms", _stopwatch.ElapsedMilliseconds);
        
        foreach (var (name, elapsed) in _checkpoints)
        {
            Log.Debug("Startup checkpoint: {Name} at {Elapsed}ms", name, elapsed);
        }
        
        TelemetryEventSource.Log.StartupTiming(_stopwatch.ElapsedMilliseconds);
    }
}

// Usage in App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
    StartupTimer.Instance.Start();
    
    StartupTimer.Instance.BeginPhase("Logging");
    LogBootstrapper.Initialize();
    
    StartupTimer.Instance.BeginPhase("UI");
    InitializeTrayIcon();
    InitializeShell();
    
    StartupTimer.Instance.BeginPhase("Data");
    // Async, don't block
    _ = LoadWidgetsAsync();
    
    StartupTimer.Instance.Complete();
}

Defer Non-Critical Work

protected override async void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);
    
    // Critical path - show UI immediately
    _shellWindow = new ShellWindow();
    _shellWindow.Show();
    
    // Defer non-critical initialization
    await Dispatcher.InvokeAsync(async () =>
    {
        await Task.Delay(500);  // Let UI render first
        
        // Now load widgets, sync, etc.
        await LoadWidgetsAsync();
        await CheckForUpdatesAsync();
    }, DispatcherPriority.Background);
}

UI Responsiveness

Async Commands

[RelayCommand]
private async Task LoadWidgetsAsync(CancellationToken ct)
{
    IsLoading = true;
    
    try
    {
        // Run on background thread
        var widgets = await Task.Run(
            () => _repository.GetAllAsync(ct), ct);
        
        // Update UI on dispatcher
        Widgets = new ObservableCollection<WidgetViewModel>(
            widgets.Select(w => new WidgetViewModel(w)));
    }
    finally
    {
        IsLoading = false;
    }
}

Progress Reporting

[RelayCommand]
private async Task InstallPackagesAsync(
    IEnumerable<WidgetPackage> packages, 
    CancellationToken ct)
{
    var packageList = packages.ToList();
    var total = packageList.Count;
    var current = 0;
    
    Progress = 0;
    StatusMessage = "Installing widgets...";
    
    foreach (var package in packageList)
    {
        ct.ThrowIfCancellationRequested();
        
        StatusMessage = $"Installing {package.DisplayName}...";
        await _installer.InstallAsync(package, ct);
        
        current++;
        Progress = (double)current / total * 100;
    }
    
    StatusMessage = $"Installed {total} widgets";
}

UI Thread Monitor

public class UiResponsivenessMonitor : IDisposable
{
    private readonly DispatcherTimer _timer;
    private DateTimeOffset _lastTick;
    private const int WarningThresholdMs = 200;
    
    public UiResponsivenessMonitor()
    {
        _timer = new DispatcherTimer
        {
            Interval = TimeSpan.FromMilliseconds(100)
        };
        _timer.Tick += OnTick;
    }
    
    public void Start()
    {
        _lastTick = DateTimeOffset.UtcNow;
        _timer.Start();
    }
    
    private void OnTick(object? sender, EventArgs e)
    {
        var now = DateTimeOffset.UtcNow;
        var elapsed = (now - _lastTick).TotalMilliseconds;
        
        if (elapsed > WarningThresholdMs)
        {
            Log.Warning(
                "UI thread blocked for {Elapsed}ms (expected ~100ms)",
                elapsed);
            
            TelemetryEventSource.Log.UiBlocked((long)elapsed);
        }
        
        _lastTick = now;
    }
    
    public void Stop() => _timer.Stop();
    public void Dispose() => _timer.Stop();
}

Memory Management

Memory Monitoring

public class MemoryMonitor
{
    public static MemoryMonitor Instance { get; } = new();
    
    private readonly Dictionary<string, long> _snapshots = new();
    
    public void TakeSnapshot(string name)
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        
        var memory = GC.GetTotalMemory(forceFullCollection: false);
        _snapshots[name] = memory;
        
        Log.Debug("Memory snapshot {Name}: {MemoryMB:F2}MB", 
            name, memory / 1024.0 / 1024.0);
    }
    
    public void CheckForLeaks()
    {
        if (_snapshots.TryGetValue("AppStarted", out var startMemory) &&
            _snapshots.TryGetValue("AppShutdown", out var endMemory))
        {
            var growthMb = (endMemory - startMemory) / 1024.0 / 1024.0;
            
            if (growthMb > 100)  // More than 100MB growth
            {
                Log.Warning(
                    "Potential memory leak: Memory grew by {GrowthMB:F2}MB during session",
                    growthMb);
            }
        }
    }
    
    public MemoryInfo GetCurrentInfo()
    {
        var process = Process.GetCurrentProcess();
        
        return new MemoryInfo
        {
            ManagedMemoryMB = GC.GetTotalMemory(false) / 1024.0 / 1024.0,
            WorkingSetMB = process.WorkingSet64 / 1024.0 / 1024.0,
            PrivateMemoryMB = process.PrivateMemorySize64 / 1024.0 / 1024.0,
            Gen0Collections = GC.CollectionCount(0),
            Gen1Collections = GC.CollectionCount(1),
            Gen2Collections = GC.CollectionCount(2)
        };
    }
}

public record MemoryInfo
{
    public double ManagedMemoryMB { get; init; }
    public double WorkingSetMB { get; init; }
    public double PrivateMemoryMB { get; init; }
    public int Gen0Collections { get; init; }
    public int Gen1Collections { get; init; }
    public int Gen2Collections { get; init; }
}

Avoiding Memory Leaks

// ❌ BAD - Event handler leak
public class WidgetViewModel
{
    public WidgetViewModel(IEventAggregator events)
    {
        events.ThemeChanged += OnThemeChanged;  // Never unsubscribed!
    }
}

// ✅ GOOD - Weak event or explicit unsubscription
public class WidgetViewModel : IDisposable
{
    private readonly IEventAggregator _events;
    
    public WidgetViewModel(IEventAggregator events)
    {
        _events = events;
        _events.ThemeChanged += OnThemeChanged;
    }
    
    public void Dispose()
    {
        _events.ThemeChanged -= OnThemeChanged;
    }
}

// ✅ BETTER - Use WeakEventManager
public class WidgetViewModel
{
    public WidgetViewModel(IEventAggregator events)
    {
        WeakEventManager<IEventAggregator, EventArgs>.AddHandler(
            events, nameof(events.ThemeChanged), OnThemeChanged);
    }
}

Dispose Pattern

public class WidgetWindow : Window, IDisposable
{
    private readonly DispatcherTimer _timer;
    private readonly HttpClient _httpClient;
    private bool _disposed;
    
    protected virtual void Dispose(bool disposing)
    {
        if (_disposed) return;
        
        if (disposing)
        {
            _timer.Stop();
            _httpClient.Dispose();
        }
        
        _disposed = true;
    }
    
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    
    protected override void OnClosed(EventArgs e)
    {
        Dispose();
        base.OnClosed(e);
    }
}

Database Performance

Query Optimization

// ❌ BAD - N+1 queries
foreach (var widget in widgets)
{
    var instances = await _instanceRepo.GetByWidgetKeyAsync(widget.WidgetKey);
}

// ✅ GOOD - Single query with includes
var widgetsWithInstances = await _context.Widgets
    .Include(w => w.Instances)
    .AsNoTracking()
    .ToListAsync(ct);

// ✅ GOOD - Projection to DTO
var widgetSummaries = await _context.Widgets
    .Select(w => new WidgetSummary
    {
        Key = w.WidgetKey,
        Name = w.DisplayName,
        InstanceCount = w.Instances.Count
    })
    .ToListAsync(ct);

Pagination

public async Task<PagedResult<Widget>> GetPagedAsync(
    int page, 
    int pageSize, 
    CancellationToken ct)
{
    var query = _context.Widgets.AsNoTracking();
    
    var totalCount = await query.CountAsync(ct);
    
    var items = await query
        .OrderBy(w => w.DisplayName)
        .Skip((page - 1) * pageSize)
        .Take(pageSize)
        .ToListAsync(ct);
    
    return new PagedResult<Widget>
    {
        Items = items,
        TotalCount = totalCount,
        Page = page,
        PageSize = pageSize
    };
}

Virtualization

UI Virtualization

<!-- ✅ GOOD - Virtualized list for large collections -->
<ListBox ItemsSource="{Binding Widgets}"
         VirtualizingStackPanel.IsVirtualizing="True"
         VirtualizingStackPanel.VirtualizationMode="Recycling"
         ScrollViewer.IsDeferredScrollingEnabled="True">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

<!-- For grid layouts -->
<ItemsControl ItemsSource="{Binding Widgets}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingWrapPanel />  <!-- Custom panel -->
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

Profiling Checklist

Before Optimization

  1. Measure first - Don't guess, profile
  2. Set targets - Define acceptable performance
  3. Identify bottlenecks - Focus on hot paths
  4. Test on target hardware - Not just dev machines

Tools

ToolPurpose
Visual Studio ProfilerCPU, memory, UI analysis
PerfViewETW traces, GC analysis
dotMemoryMemory snapshots, leak detection
BenchmarkDotNetMicro-benchmarks

Key Metrics to Track

public static class PerformanceCounters
{
    private static readonly Stopwatch AppUptime = Stopwatch.StartNew();
    private static long _operationCount;
    
    public static void RecordOperation() => 
        Interlocked.Increment(ref _operationCount);
    
    public static void LogPerformanceSummary()
    {
        var memory = MemoryMonitor.Instance.GetCurrentInfo();
        
        Log.Information(
            "Performance summary - Uptime: {Uptime}, Operations: {OpCount}, " +
            "Memory: {MemoryMB:F1}MB, GC Gen2: {Gen2}",
            AppUptime.Elapsed,
            _operationCount,
            memory.WorkingSetMB,
            memory.Gen2Collections);
    }
}

Anti-Patterns

Anti-PatternImpactSolution
Sync over asyncUI freezeUse async/await properly
Large object allocationsGC pressurePool or reuse objects
Binding to complex objectsSlow renderingUse lightweight ViewModels
Missing virtualizationMemory bloatEnable UI virtualization
Unbounded collectionsMemory growthUse pagination/windowing

References

スコア

総合スコア

40/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です