mirror of
https://github.com/semihalev/twig.git
synced 2026-03-14 22:05:46 +01:00
- Implemented pooled slices for function arguments - Added specialized pooling for variable node and literal node objects - Modified array and hash node evaluation to reduce allocations - Optimized test and filter evaluation with pooled resources - Added comprehensive benchmarks to validate improvements - Updated node pool implementation to remove duplicate declarations - Fixed memory allocations in merge filter to correctly handle array manipulations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
244 lines
5.7 KiB
Go
244 lines
5.7 KiB
Go
package twig
|
|
|
|
import (
|
|
"sync"
|
|
)
|
|
|
|
// NodePool provides object pooling for all node types
|
|
// This significantly reduces GC pressure by reusing node objects
|
|
|
|
// TextNodePool provides a pool for TextNode objects
|
|
var TextNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &TextNode{}
|
|
},
|
|
}
|
|
|
|
// GetTextNode gets a TextNode from the pool and initializes it
|
|
func GetTextNode(content string, line int) *TextNode {
|
|
node := TextNodePool.Get().(*TextNode)
|
|
node.content = content
|
|
node.line = line
|
|
return node
|
|
}
|
|
|
|
// ReleaseTextNode returns a TextNode to the pool
|
|
func ReleaseTextNode(node *TextNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.content = ""
|
|
TextNodePool.Put(node)
|
|
}
|
|
|
|
// PrintNodePool provides a pool for PrintNode objects
|
|
var PrintNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &PrintNode{}
|
|
},
|
|
}
|
|
|
|
// GetPrintNode gets a PrintNode from the pool and initializes it
|
|
func GetPrintNode(expression Node, line int) *PrintNode {
|
|
node := PrintNodePool.Get().(*PrintNode)
|
|
node.expression = expression
|
|
node.line = line
|
|
return node
|
|
}
|
|
|
|
// ReleasePrintNode returns a PrintNode to the pool
|
|
func ReleasePrintNode(node *PrintNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.expression = nil
|
|
PrintNodePool.Put(node)
|
|
}
|
|
|
|
// RootNodePool provides a pool for RootNode objects
|
|
var RootNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &RootNode{}
|
|
},
|
|
}
|
|
|
|
// GetRootNode gets a RootNode from the pool and initializes it
|
|
func GetRootNode(children []Node, line int) *RootNode {
|
|
node := RootNodePool.Get().(*RootNode)
|
|
node.children = children
|
|
node.line = line
|
|
return node
|
|
}
|
|
|
|
// ReleaseRootNode returns a RootNode to the pool
|
|
func ReleaseRootNode(node *RootNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.children = nil
|
|
RootNodePool.Put(node)
|
|
}
|
|
|
|
// Note: LiteralNodePool, GetLiteralNode, ReleaseLiteralNode moved to expr_pool.go
|
|
// Note: VariableNodePool, GetVariableNode, ReleaseVariableNode moved to expr_pool.go
|
|
|
|
// TokenPool provides a pool for Token objects
|
|
var TokenPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &Token{}
|
|
},
|
|
}
|
|
|
|
// GetToken gets a Token from the pool and initializes it
|
|
func GetToken(tokenType int, value string, line int) *Token {
|
|
token := TokenPool.Get().(*Token)
|
|
token.Type = tokenType
|
|
token.Value = value
|
|
token.Line = line
|
|
return token
|
|
}
|
|
|
|
// ReleaseToken returns a Token to the pool
|
|
func ReleaseToken(token *Token) {
|
|
if token == nil {
|
|
return
|
|
}
|
|
token.Value = ""
|
|
TokenPool.Put(token)
|
|
}
|
|
|
|
// SlicePool provides pools for commonly used slice types
|
|
// to reduce allocations when working with collections
|
|
|
|
// IfNodePool provides a pool for IfNode objects
|
|
var IfNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &IfNode{}
|
|
},
|
|
}
|
|
|
|
// GetIfNode gets an IfNode from the pool and initializes it
|
|
func GetIfNode(conditions []Node, bodies [][]Node, elseBranch []Node, line int) *IfNode {
|
|
node := IfNodePool.Get().(*IfNode)
|
|
node.conditions = conditions
|
|
node.bodies = bodies
|
|
node.elseBranch = elseBranch
|
|
node.line = line
|
|
return node
|
|
}
|
|
|
|
// ReleaseIfNode returns an IfNode to the pool
|
|
func ReleaseIfNode(node *IfNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.conditions = nil
|
|
node.bodies = nil
|
|
node.elseBranch = nil
|
|
IfNodePool.Put(node)
|
|
}
|
|
|
|
// ForNodePool provides a pool for ForNode objects
|
|
var ForNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &ForNode{}
|
|
},
|
|
}
|
|
|
|
// GetForNode gets a ForNode from the pool and initializes it
|
|
func GetForNode(keyVar, valueVar string, sequence Node, body, elseBranch []Node, line int) *ForNode {
|
|
node := ForNodePool.Get().(*ForNode)
|
|
node.keyVar = keyVar
|
|
node.valueVar = valueVar
|
|
node.sequence = sequence
|
|
node.body = body
|
|
node.elseBranch = elseBranch
|
|
node.line = line
|
|
return node
|
|
}
|
|
|
|
// ReleaseForNode returns a ForNode to the pool
|
|
func ReleaseForNode(node *ForNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.keyVar = ""
|
|
node.valueVar = ""
|
|
node.sequence = nil
|
|
node.body = nil
|
|
node.elseBranch = nil
|
|
ForNodePool.Put(node)
|
|
}
|
|
|
|
// NodeSlicePool provides a pool for []Node slices
|
|
var NodeSlicePool = sync.Pool{
|
|
New: func() interface{} {
|
|
slice := make([]Node, 0, 8) // Default capacity of 8 for common cases
|
|
return &slice
|
|
},
|
|
}
|
|
|
|
// GetNodeSlice gets a slice of Node from the pool
|
|
func GetNodeSlice() *[]Node {
|
|
slice := NodeSlicePool.Get().(*[]Node)
|
|
*slice = (*slice)[:0] // Clear slice but keep capacity
|
|
return slice
|
|
}
|
|
|
|
// ReleaseNodeSlice returns a slice of Node to the pool
|
|
func ReleaseNodeSlice(slice *[]Node) {
|
|
if slice == nil {
|
|
return
|
|
}
|
|
// Clear references to help GC
|
|
for i := range *slice {
|
|
(*slice)[i] = nil
|
|
}
|
|
*slice = (*slice)[:0]
|
|
NodeSlicePool.Put(slice)
|
|
}
|
|
|
|
// TokenSlicePool provides a pool for []Token slices
|
|
var TokenSlicePool = sync.Pool{
|
|
New: func() interface{} {
|
|
slice := make([]Token, 0, 32) // Higher default capacity for tokens
|
|
return &slice
|
|
},
|
|
}
|
|
|
|
// GetTokenSlice gets a slice of Token from the pool with optional capacity hint
|
|
func GetTokenSlice(capacityHint int) []Token {
|
|
if capacityHint <= 0 {
|
|
// Use default capacity
|
|
slice := TokenSlicePool.Get().(*[]Token)
|
|
return (*slice)[:0]
|
|
}
|
|
|
|
// For large token slices, allocate directly
|
|
if capacityHint > 1000 {
|
|
return make([]Token, 0, capacityHint)
|
|
}
|
|
|
|
// Get from pool and ensure it has enough capacity
|
|
slice := TokenSlicePool.Get().(*[]Token)
|
|
if cap(*slice) < capacityHint {
|
|
// Current slice is too small, allocate a new one
|
|
*slice = make([]Token, 0, capacityHint)
|
|
} else {
|
|
*slice = (*slice)[:0] // Clear but keep capacity
|
|
}
|
|
return *slice
|
|
}
|
|
|
|
// ReleaseTokenSlice returns a slice of Token to the pool
|
|
func ReleaseTokenSlice(slice []Token) {
|
|
// Only return reasonable sized slices to the pool
|
|
if cap(slice) > 1000 || cap(slice) < 32 {
|
|
return // Don't pool very large or very small slices
|
|
}
|
|
|
|
// Clear the slice to help GC
|
|
slicePtr := &slice
|
|
*slicePtr = (*slicePtr)[:0]
|
|
TokenSlicePool.Put(slicePtr)
|
|
}
|