@@ -46,6 +46,9 @@ type VM struct {
4646 debug bool
4747 step chan struct {}
4848 curr chan int
49+ scopePool []Scope // Pre-allocated pool of Scope values for reuse
50+ scopePoolIdx int // Current index into scopePool for allocation
51+ currScope * Scope // Cached pointer to the current scope (optimization)
4952}
5053
5154func (vm * VM ) Run (program * Program , env any ) (_ any , err error ) {
@@ -76,6 +79,8 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
7679 clearSlice (vm .Scopes )
7780 vm .Scopes = vm .Scopes [0 :0 ]
7881 }
82+ vm .scopePoolIdx = 0 // Reset pool index for reuse
83+ vm .currScope = nil
7984 if len (vm .Variables ) < program .variables {
8085 vm .Variables = make ([]any , program .variables )
8186 }
@@ -221,8 +226,7 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
221226 if arg < 0 {
222227 panic ("negative jump offset is invalid" )
223228 }
224- scope := vm .scope ()
225- if scope .Index >= scope .Len {
229+ if vm .currScope .Index >= vm .currScope .Len {
226230 vm .ip += arg
227231 }
228232
@@ -511,40 +515,45 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
511515 vm .push (deref .Interface (a ))
512516
513517 case OpIncrementIndex :
514- vm .scope () .Index ++
518+ vm .currScope .Index ++
515519
516520 case OpDecrementIndex :
517- scope := vm .scope ()
518- scope .Index --
521+ vm .currScope .Index --
519522
520523 case OpIncrementCount :
521- scope := vm .scope ()
522- scope .Count ++
524+ vm .currScope .Count ++
523525
524526 case OpGetIndex :
525- vm .push (vm .scope () .Index )
527+ vm .push (vm .currScope .Index )
526528
527529 case OpGetCount :
528- scope := vm .scope ()
529- vm .push (scope .Count )
530+ vm .push (vm .currScope .Count )
530531
531532 case OpGetLen :
532- scope := vm .scope ()
533- vm .push (scope .Len )
533+ vm .push (vm .currScope .Len )
534534
535535 case OpGetAcc :
536- vm .push (vm .scope () .Acc )
536+ vm .push (vm .currScope .Acc )
537537
538538 case OpSetAcc :
539- vm .scope () .Acc = vm .pop ()
539+ vm .currScope .Acc = vm .pop ()
540540
541541 case OpSetIndex :
542- scope := vm .scope ()
543- scope .Index = vm .pop ().(int )
542+ vm .currScope .Index = vm .pop ().(int )
544543
545544 case OpPointer :
546- scope := vm .scope ()
547- vm .push (scope .Array .Index (scope .Index ).Interface ())
545+ scope := vm .currScope
546+ if scope .Ints != nil {
547+ vm .push (scope .Ints [scope .Index ])
548+ } else if scope .Floats != nil {
549+ vm .push (scope .Floats [scope .Index ])
550+ } else if scope .Strings != nil {
551+ vm .push (scope .Strings [scope .Index ])
552+ } else if scope .Anys != nil {
553+ vm .push (scope .Anys [scope .Index ])
554+ } else {
555+ vm .push (scope .Array .Index (scope .Index ).Interface ())
556+ }
548557
549558 case OpThrow :
550559 panic (vm .pop ().(error ))
@@ -554,7 +563,7 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
554563 case 1 :
555564 vm .push (make (groupBy ))
556565 case 2 :
557- scope := vm .scope ()
566+ scope := vm .currScope
558567 var desc bool
559568 switch vm .pop ().(string ) {
560569 case "asc" :
@@ -574,21 +583,43 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
574583 }
575584
576585 case OpGroupBy :
577- scope := vm .scope ()
586+ scope := vm .currScope
578587 key := vm .pop ()
579- item := scope .Array .Index (scope .Index ).Interface ()
588+ var item any
589+ if scope .Ints != nil {
590+ item = scope .Ints [scope .Index ]
591+ } else if scope .Floats != nil {
592+ item = scope .Floats [scope .Index ]
593+ } else if scope .Strings != nil {
594+ item = scope .Strings [scope .Index ]
595+ } else if scope .Anys != nil {
596+ item = scope .Anys [scope .Index ]
597+ } else {
598+ item = scope .Array .Index (scope .Index ).Interface ()
599+ }
580600 scope .Acc .(groupBy )[key ] = append (scope .Acc .(groupBy )[key ], item )
581601
582602 case OpSortBy :
583- scope := vm .scope ()
603+ scope := vm .currScope
584604 value := vm .pop ()
585- item := scope .Array .Index (scope .Index ).Interface ()
605+ var item any
606+ if scope .Ints != nil {
607+ item = scope .Ints [scope .Index ]
608+ } else if scope .Floats != nil {
609+ item = scope .Floats [scope .Index ]
610+ } else if scope .Strings != nil {
611+ item = scope .Strings [scope .Index ]
612+ } else if scope .Anys != nil {
613+ item = scope .Anys [scope .Index ]
614+ } else {
615+ item = scope .Array .Index (scope .Index ).Interface ()
616+ }
586617 sortable := scope .Acc .(* runtime.SortBy )
587618 sortable .Array = append (sortable .Array , item )
588619 sortable .Values = append (sortable .Values , value )
589620
590621 case OpSort :
591- scope := vm .scope ()
622+ scope := vm .currScope
592623 sortable := scope .Acc .(* runtime.SortBy )
593624 sort .Sort (sortable )
594625 vm .memGrow (uint (scope .Len ))
@@ -605,10 +636,23 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
605636 case OpBegin :
606637 a := vm .pop ()
607638 array := reflect .ValueOf (a )
608- vm .Scopes = append (vm .Scopes , & Scope {
609- Array : array ,
610- Len : array .Len (),
611- })
639+ s := vm .allocScope (array )
640+ switch v := a .(type ) {
641+ case []int :
642+ s .Ints = v
643+ s .Len = len (v )
644+ case []float64 :
645+ s .Floats = v
646+ s .Len = len (v )
647+ case []string :
648+ s .Strings = v
649+ s .Len = len (v )
650+ case []any :
651+ s .Anys = v
652+ s .Len = len (v )
653+ }
654+ vm .Scopes = append (vm .Scopes , s )
655+ vm .currScope = s
612656
613657 case OpAnd :
614658 a := vm .pop ()
@@ -622,6 +666,11 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
622666
623667 case OpEnd :
624668 vm .Scopes = vm .Scopes [:len (vm .Scopes )- 1 ]
669+ if len (vm .Scopes ) > 0 {
670+ vm .currScope = vm .Scopes [len (vm .Scopes )- 1 ]
671+ } else {
672+ vm .currScope = nil
673+ }
625674
626675 default :
627676 panic (fmt .Sprintf ("unknown bytecode %#x" , op ))
@@ -675,6 +724,26 @@ func (vm *VM) scope() *Scope {
675724 return vm .Scopes [len (vm .Scopes )- 1 ]
676725}
677726
727+ // allocScope returns a pointer to a Scope from the pool, growing the pool if needed.
728+ // The returned Scope has Array and Len set; Index, Count, and Acc are zeroed.
729+ func (vm * VM ) allocScope (array reflect.Value ) * Scope {
730+ if vm .scopePoolIdx >= len (vm .scopePool ) {
731+ vm .scopePool = append (vm .scopePool , Scope {})
732+ }
733+ s := & vm .scopePool [vm .scopePoolIdx ]
734+ vm .scopePoolIdx ++
735+ s .Array = array
736+ s .Len = array .Len ()
737+ s .Index = 0
738+ s .Count = 0
739+ s .Acc = nil
740+ s .Ints = nil
741+ s .Floats = nil
742+ s .Strings = nil
743+ s .Anys = nil
744+ return s
745+ }
746+
678747// getArgsForFunc lazily initializes the buffer the first time it is called for
679748// a given program (thus, it also needs "program" to run). It will
680749// take "needed" elements from the buffer and populate them with vm.pop() in
0 commit comments