@@ -159,20 +159,28 @@ public function iterate(array $items): string
159159 $ ret = '' ;
160160 $ i = 0 ;
161161 $ outerFrame = $ cx ->frame ;
162- // Fast path: when only root is in the frame, skip array_replace.
163- $ simpleFrame = count ($ outerFrame ) === 1 ;
164162 // Pre-allocate bpStack once; mutate [0][0] and [0][1] per iteration.
165163 // PHP COW ensures the inner array's refcount returns to 1 after $cb() returns,
166164 // so the next iteration's assignment is an in-place mutation, not a copy.
167165 $ bpStack = [[null , null ], ...$ this ->outerBlockParams ];
168166
167+ // Pre-allocate the iteration frame once; only the 4 per-iteration keys mutate each loop.
168+ // Reduces per-iteration allocations from 2 (iterData + COW-on-root-assign) to 1 (COW copy
169+ // on the first mutation after $cx->frame = $newFrame shares the array). For non-simple
170+ // frames, also eliminates the per-iteration array_replace call.
171+ $ iterKeys = ['key ' => null , 'index ' => null , 'first ' => null , 'last ' => null , '_parent ' => $ outerFrame ];
172+ $ newFrame = count ($ outerFrame ) === 1
173+ ? $ iterKeys
174+ : array_replace ($ outerFrame , $ iterKeys );
175+ // Reference so @root lookups work and root mutations from inside the block are visible
176+ // outside it — matches HBS.js shallow-copy semantics for object properties.
177+ $ newFrame ['root ' ] = &$ cx ->data ['root ' ];
178+
169179 foreach ($ items as $ index => $ value ) {
170- $ iterData = ['key ' => $ index , 'index ' => $ i , 'first ' => $ i === 0 , 'last ' => $ i === $ last ];
171- $ newFrame = $ simpleFrame ? $ iterData : array_replace ($ outerFrame , $ iterData );
172- // Reference so @root lookups work and root mutations from inside the block are visible
173- // outside it — matches HBS.js shallow-copy semantics for object properties.
174- $ newFrame ['root ' ] = &$ cx ->data ['root ' ];
175- $ newFrame ['_parent ' ] = $ outerFrame ;
180+ $ newFrame ['key ' ] = $ index ;
181+ $ newFrame ['index ' ] = $ i ;
182+ $ newFrame ['first ' ] = $ i === 0 ;
183+ $ newFrame ['last ' ] = $ i === $ last ;
176184 $ cx ->frame = $ newFrame ;
177185
178186 $ bpStack [0 ][0 ] = $ value ;
0 commit comments