Skip to content

Implement Reflection Caching for __call() Performance Optimization #16

Description

@MarcinOrlowski

Currently, the Lombok::call() method performs reflection-based lookups on every invocation of getter/setter methods through the __call() magic method. While the initial configuration is cached per object instance, the method resolution still requires traversing the accessor maps on each call. This could potentially impact performance in high-throughput scenarios.

Current Behavior

public static function call(object $targetObj, string $methodName, array $args)
{
    $objectConfig = static::construct($targetObj);  // This is cached
    
    // But these lookups happen on every call:
    $getter = $objectConfig->getGetter($methodName);
    if ($getter \!== null) {
        return $getter->getValue($targetObj);
    }
    
    $item = $objectConfig->getSetter($methodName);
    if ($item \!== null) {
        $item->setValue($targetObj, $args[0]);
        return $targetObj;
    }
    
    throw new \BadMethodCallException(...);
}

Proposed Solution

Implement a multi-level caching strategy:

  1. Method Resolution Cache: Cache the mapping of method names to their accessor type (getter/setter) and associated ReflectionProperty
  2. Per-Class Cache: Since all instances of a class share the same method definitions, cache at the class level
  3. Benchmark-Driven Decision: Create benchmarks to measure if caching provides real benefits or just adds complexity

Testing Requirements

1. Performance Benchmarks

Create benchmarks to measure:

  • Baseline performance without caching
  • Performance with different caching strategies
  • Memory overhead of caching
  • Cache hit/miss ratios in typical usage

2. Correctness Tests

  • Verify cache invalidation when classes are modified (dev environment)
  • Test memory cleanup for short-lived objects
  • Ensure thread safety if applicable
  • Test behavior with class inheritance

3. Memory Usage Tests

  • Monitor memory usage with large numbers of different classes
  • Verify no memory leaks with long-running processes
  • Test cache size limits if implemented

Success Criteria

  1. Performance: At least 20% improvement in getter/setter call performance for repeated calls
  2. Memory: Cache overhead should not exceed 1MB for typical applications (1000 entities)
  3. Complexity: Implementation should not significantly complicate the codebase
  4. BC: Must maintain backward compatibility

Risks and Considerations

  1. Over-optimization: Caching might not provide significant benefits if reflection is already fast enough
  2. Memory overhead: Cache could consume more memory than it saves in CPU time
  3. Cache invalidation: Handling dynamic class modifications (though rare in production)
  4. Complexity: Added complexity might make the code harder to maintain

Decision Points

Before implementation, benchmarks should answer:

  1. Is the current performance actually a bottleneck?
  2. What's the typical method call pattern in real applications?
  3. Does caching provide measurable benefits?
  4. What's the optimal cache strategy for typical use cases?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions