Skip to content

[Feature Request] Built-in performance profiling for formula evaluation #13

@SINOVACO

Description

@SINOVACO

Complex games can have thousands of formula evaluations per frame. Currently, there's no way to identify performance bottlenecks in formula calculations. Developers need profiling tools to optimize their game balance formulas.

Use Cases

  1. Identify slow formulas: Find which formulas take the most time to evaluate
  2. Cache hit analysis: Understand cache efficiency for repeated calculations
  3. Hotspot detection: Locate expressions that run too frequently
  4. Memory tracking: Monitor formula-related memory allocation
  5. Comparison benchmarks: Compare formula implementations

Proposed API

Profiler Class

// src/utils/Profiler.ts
export interface ProfileResult {
  formulaId: string
  executionCount: number
  totalTimeMs: number
  averageTimeMs: number
  minTimeMs: number
  maxTimeMs: number
  cacheHits: number
  cacheMisses: number
}

export interface ProfileSession {
  id: string
  startTime: Date
  endTime?: Date
  results: Map<string, ProfileResult>
}

export class Profiler {
  static startSession(name?: string): ProfileSession
  static endSession(session: ProfileSession): ProfileReport
  static getActiveSession(): ProfileSession | null

  static profile<T>(name: string, fn: () => T): T
  static profileAsync<T>(name: string, fn: () => Promise<T>): Promise<T>

  static mark(name: string): void
  static measure(name: string, startMark: string, endMark?: string): number
}

Integration with fx singleton

// Profile a specific formula
const damage = fx.profileEvaluate('damage-calc', 'ATK * (1 - DEF/100)')

// Start profiling session
fx.startProfiling()
// ... game loop with formula evaluations ...
const report = fx.stopProfiling()

// Get formula statistics
const stats = fx.getFormulaStats('damage-calc')
console.log(`Average: ${stats.averageTimeMs}ms, Calls: ${stats.executionCount}`)

Profile Report Format

interface ProfileReport {
  sessionDuration: number
  totalEvaluations: number
  totalTimeMs: number

  // Sorted by total time (desc)
  slowestFormulas: ProfileResult[]

  // Sorted by call count (desc)
  mostFrequent: ProfileResult[]

  // Formulas with poor cache utilization
  cacheMissers: ProfileResult[]

  // Export formats
  toJSON(): string
  toCSV(): string
  toMarkdown(): string
}

Example Output

# Formula Performance Report
Session: combat-loop-analysis
Duration: 5000ms | Total Evaluations: 15,234

## Slowest Formulas
| Formula | Calls | Total (ms) | Avg (ms) | Cache Hit % |
|---------|-------|------------|----------|-------------|
| enemy-ai-decision | 1,200 | 450 | 0.375 | 12% |
| damage-calc | 8,500 | 320 | 0.038 | 89% |
| crit-chance | 4,000 | 85 | 0.021 | 95% |

## Recommendations
- `enemy-ai-decision`: Low cache hit rate. Consider memoization.
- `damage-calc`: High call volume. Review call sites for redundancy.

Implementation Details

Minimal Overhead Mode

// Profiling disabled in production (zero overhead)
if (process.env.NODE_ENV === 'production') {
  Profiler.disable()
}

// Selective profiling
Profiler.enable({
  formulas: ['damage-calc', 'heal-calc'],
  threshold: 1 // Only track if > 1ms
})

Memory Profiling (Optional)

// Track allocations during formula evaluation
fx.profileMemory('complex-formula', () => {
  return fx.evaluate(complexExpression)
})
// Returns: { result, allocations: 1240, peakMemory: '2.4KB' }

Visual Integration

The profiler should integrate with the visual editor to:

  • Highlight slow formulas in red
  • Show execution counts on formula nodes
  • Provide real-time performance overlay

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions