Performance
Canvas is designed for performance through intelligent caching, lazy instantiation, and aggressive pre-filtering. This page explains how Canvas achieves efficiency internally.
Route Resolution Performance
Canvas uses a two-phase route matching strategy that reduces resolution from O(n) to O(1) + O(k), where n is total routes and k is filtered candidates (typically 3-5 routes).
Pre-filtering Pipeline
Before expensive pattern matching occurs, Canvas eliminates incompatible routes through multiple fast filters:
- HTTP Method Filtering - Instant elimination of routes with incompatible methods (GET vs POST)
- Segment Count Filtering - Removes routes with wrong number of path segments
- Multi-level Static Filtering - Uses static segments at all positions for compound elimination
- Prefix Trie Lookup - O(k) exact matching for fully static routes
This pipeline typically eliminates 95%+ of routes before pattern matching, dramatically reducing computational overhead.
Route Cache Storage
In production mode (debug_mode: false), discovered routes are cached:
- Cache Location:
{project_root}/storage/cache/routes - Cache Format: Serialized PHP arrays via FileCache
- Invalidation: Automatic when controller files are modified
- Development Mode: Cache disabled, routes discovered on every request
Annotation Caching
Route annotations are cached separately from routes themselves:
- Cache Location:
{project_root}/storage/annotations - When Active: Only in production mode (debug_mode: false)
- What's Cached: Parsed annotation data from reflection
- Rebuilds: Automatic on file modification
// Annotation caching configured in Kernel
if (!$this->configuration->getAs('debug_mode', 'bool', false)) {
$config->setUseAnnotationCache(true);
$config->setAnnotationCachePath($rootPath . "/storage/annotations");
}
Dependency Injection Container
Service Discovery and Registration
Canvas uses a lazy discovery pattern for service providers:
- Discovery Source: Scans composer.json for provider definitions
- Cache Format: Exported as PHP arrays (timestamp + providers grouped by family)
- Cache Update: Regenerated on
composer update - Memory Efficiency: Provider definitions stored separately from instances
Lazy Instantiation
Services are created only when requested, not at container initialization:
// From Discover.php - providers instantiated on-demand
protected function getOrInstantiateProvider(string $definitionKey, ProviderDefinition $definition): ?ProviderInterface {
// Return cached instance if exists
if (isset($this->instantiatedProviders[$definitionKey])) {
return $this->instantiatedProviders[$definitionKey];
}
// Create and cache new instance
$provider = $this->instantiateProvider($definition);
return $this->instantiatedProviders[$definitionKey] = $provider;
}
Autowiring Performance
Constructor and method parameter resolution uses reflection:
- Reflection Usage: Per-request for dependency resolution
- Circular Dependency Detection: Resolution stack tracks dependency chain
- Contextual Containers: Cloning creates independent contexts without shared state
- Instance Caching: Service providers determine singleton vs transient behavior
Request-Scoped Services
Certain services are registered and unregistered per-request to prevent memory leaks:
// From RequestHandler.php - cleanup happens in finally block
private function prepareRequest(Request $request): array {
$requestProvider = new RequestProvider($request);
$sessionProvider = new SessionInterfaceProvider($request->getSession());
$this->kernel->getDependencyInjector()->register($requestProvider);
$this->kernel->getDependencyInjector()->register($sessionProvider);
return ['request' => $requestProvider, 'session' => $sessionProvider];
}
private function cleanupRequest(array $providers): void {
$this->kernel->getDependencyInjector()->unregister($providers['session']);
$this->kernel->getDependencyInjector()->unregister($providers['request']);
}
Aspect-Oriented Programming (AOP)
Aspect Resolution
Aspects are resolved once per request using annotation scanning:
- Discovery: Annotation reader scans controller and method for
@InterceptWithannotations - Instantiation: DI container creates aspect instances with dependency injection
- No Global Overhead: Aspects only instantiated when annotated methods are called
- Annotation Caching: Aspect annotations cached in production mode
Execution Pipeline
Aspects execute in a defined order with minimal overhead:
- Request Aspects - Transform incoming request
- Before Aspects - Pre-execution logic (can short-circuit)
- Around Aspects - Nested chain wrapping method execution
- Controller Method - Actual business logic
- After Aspects - Post-processing on response
Around Aspect Chain
Around aspects create a nested execution chain without proxy objects:
// From AspectDispatcher.php - nested chain construction
$proceed = fn() => $this->di->invoke($controller, $method, $context->getArguments());
foreach (array_reverse($aroundAspects) as $aspect) {
$currentProceed = $proceed;
$proceed = fn() => $aspect->around($context, $currentProceed);
}
return $proceed(); // Execute complete chain
This approach means:
- No runtime proxy generation
- No method signature analysis overhead
- Direct function calls with closure wrapping
- Each aspect adds minimal overhead (single closure allocation)
Memory Characteristics
Container Memory
- Provider Definitions: Lightweight arrays stored per discovered provider
- Instance Cache: Only instantiated providers kept in memory
- Generator Pattern:
getProviders()uses generators for memory-efficient iteration - Contextual Isolation: Cloned containers don't share resolution stacks
Route Index Memory
- Index Structure: Built once per request, reused for all route lookups
- Segment Categorization: Routes grouped by segment count and static segments
- Trie Structure: Prefix tree for static route optimization
- Cache Persistence: Full index stored in file cache between requests
Request Processing
Each request follows this memory pattern:
- Route index loaded from cache or built (one-time cost)
- Request-scoped providers registered
- Route resolution (minimal allocations via pre-filtering)
- Controller instantiated via DI
- Aspects instantiated only if needed
- Request-scoped providers unregistered
Production vs Development Mode
Debug Mode Disabled (Production)
- Annotation caching enabled at
{project_root}/storage/annotations - Route caching enabled at
{project_root}/storage/cache/routes - Service provider discovery cached
- Error pages use custom 404.php/500.php if available
Debug Mode Enabled (Development)
- All caches disabled - routes discovered every request
- Reflection happens on every request
- Debug panel tracks performance metrics
- Detailed error pages with stack traces
// Configuration in config/app.php
return [
'debug_mode' => false, // Enable all production caching
];
ObjectQuel Query Optimization
ObjectQuel optimizes database queries through multi-stage processing:
- AST Parsing - Query syntax parsed into abstract syntax tree
- Optimization Pass - AST transformed to eliminate inefficiencies
- Plan Manager - Creates optimal SQL queries from optimized AST
- Range Queries - Uses abstract syntax for efficient range operations
- JSON Loading - Handles JSON data efficiently when needed
- Entity Hydration - Combines results and hydrates objects
The optimization advantage comes from ObjectQuel's ability to analyze the entire query structure before generating SQL, rather than building queries incrementally.
Performance Monitoring
Canvas tracks performance metrics per request:
// From Kernel.php - built-in metrics
public function handle(Request $request): Response {
$start = microtime(true);
$memoryStart = memory_get_usage(true);
// ... request processing ...
$duration = microtime(true) - $start;
$memoryUsed = memory_get_usage(true) - $memoryStart;
}
Metrics available:
- Request execution time (microsecond precision)
- Memory delta (bytes allocated during request)
- Route resolution statistics
- Query execution tracking via SignalHub
Cold Start vs Warm Cache
Cold Start (Cache Miss or Development)
- Scan all controller files for annotations
- Parse and compile route patterns
- Build route index with all filtering structures
- Discover and register service providers
- Process request and generate response
Warm Cache (Production)
- Load pre-compiled route index from cache
- Load service provider definitions from cache
- Load cached annotations for resolved route
- Process request and generate response
The warm cache path eliminates all file scanning, annotation parsing, and route compilation overhead.
Zero Configuration Overhead
Canvas uses sensible defaults to avoid configuration parsing overhead:
- Configuration files loaded once during kernel initialization
- Cached in memory for request lifetime
- No runtime configuration merging or processing
- Type-safe configuration retrieval with defaults
// Configuration loaded once in constructor
$this->configuration = new Configuration(
array_merge($this->getConfigFile("app.php"), $configuration)
);
// Cached retrieval with type safety
$debugMode = $config->getAs('debug_mode', 'bool', false);
Summary
Canvas achieves performance through:
- Aggressive Caching - Routes, annotations, and service definitions cached in production
- Lazy Everything - Services, providers, and aspects instantiated only when needed
- Smart Pre-filtering - 95%+ route elimination before pattern matching
- Minimal Reflection - Reflection results cached, used only when necessary
- Memory Efficiency - Generator patterns, request-scoped cleanup, definition/instance separation
- Direct Execution - No proxy generation, minimal wrapper overhead for AOP
The framework prioritizes real-world performance over theoretical purity, using caching and pre-filtering to minimize expensive operations while maintaining clean, maintainable code.