On Fri, May 2, 2025, at 10:14, Edmond Dantes wrote: > Good day, everyone. > > In the neighboring thread Concept: Lightweight error channels, the > topic of exception performance was discussed. > > The main issue is the immediate backtrace generation in the > constructor, which can cause up to a 50% performance loss compared to > return (as I understood from the discussion — I may be mistaken). > > I have a small idea on how to improve the situation in cases where > exceptions are caught and a backtrace isn't needed. > > Let’s assume we delay backtrace generation. In PHP, you can’t just > keep a reference to a stack frame, since it may be destroyed. You > could copy it, of course (which is relatively inexpensive compared to > generating the full backtrace). > > Based on that, there are two possible implementations: > > * Generate the backtrace at the moment the stack is freed > > * Clone the stack frame when the stack is freed (this is roughly what > happens in Python) > > This would require changes to the functions > `zend_vm_stack_free_extra_args_ex` and > `zend_vm_stack_free_extra_args`. > > These functions could be used to either clone the stack frame or > generate the full/partial backtrace at the right moment. > > The information about whether something is referencing a stack frame > could be stored in a separate structure in `EG`, so there would be no > need to modify `zend_execute_data`. > Any code referencing the frame would, of course, need to correctly > decrement the reference counter. > > For example, when an exception is created, it increments the reference > count on the current call frame, which guarantees that the frame stays > available until either the exception is destroyed or the backtrace is > generated. > > Nuances: > > * It’s more efficient to copy only the parts of the stack frame needed > for the backtrace, not the entire frame. This also applies to > parameter slots — they should be converted immediately if the > DEBUG_BACKTRACE_IGNORE_ARGS flag is not set. > > * When cloning the stack frame, the reference to the previous frame > should automatically be incremented. > > At first glance, this algorithm doesn’t break backward compatibility > in any way and spreads out memory/CPU costs. > > Even if multiple exceptions are created within a function but no > backtrace is generated, overall memory usage decreases, since Zend > only retains the data structures currently needed, rather than > duplicating them in each backtrace. > > Cloned call frames exist only once and can be shared across multiple > exceptions. > > **What are the benefits?** > > * The cost of throwing an exception that is caught and not used at the > top level is nearly equivalent to a return operation. > > * Lower memory usage when no backtrace is generated. > > * It becomes possible to reuse a single backtrace generation for > multiple exceptions that share common frames (needs further > consideration). > > **Drawbacks:** > > * Exiting a PHP function consumes CPU to copy ~100 bytes of memory. > > * Increased code complexity in the exception/stack frame modules. > > These conclusions were made mentally without writing actual code. > But perhaps someone will have something to add — either in favor or against. > > --- > Ed. >
Hey Ed, > The main issue is the immediate backtrace generation in the > constructor, which can cause up to a 50% performance loss compared to > return (as I understood from the discussion — I may be mistaken). For what it is worth, the stack trace is not generated in the constructor (at least from the perspective of the developer). Using reflection to create the exception without calling the constructor still results in the stack trace being generated in the exception object. If it were created only in the constructor, then I suspect this could all be resolved in user-land libraries. — Rob