go-twig/node_pool.go
semihalev 0f06dd0fd9 Update benchmark results with latest performance metrics
Latest benchmark runs show dramatic performance improvements:
- Twig is now 57x faster than Go's html/template for complex templates
- Memory usage reduced by 90% compared to standard Go templates
- Performance on medium templates improved to 0.14 µs/op from 0.35 µs/op
- Simple template rendering improved to 0.28 µs/op from 0.47 µs/op

These improvements reflect the optimizations from object pooling and
filter chain handling optimizations in recent commits.

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-03-11 14:54:36 +03:00

289 lines
6.6 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)
}
// LiteralNodePool provides a pool for LiteralNode objects
var LiteralNodePool = sync.Pool{
New: func() interface{} {
return &LiteralNode{}
},
}
// GetLiteralNode gets a LiteralNode from the pool and initializes it
func GetLiteralNode(value interface{}, line int) *LiteralNode {
node := LiteralNodePool.Get().(*LiteralNode)
node.value = value
node.line = line
return node
}
// ReleaseLiteralNode returns a LiteralNode to the pool
func ReleaseLiteralNode(node *LiteralNode) {
if node == nil {
return
}
node.value = nil
LiteralNodePool.Put(node)
}
// VariableNodePool provides a pool for VariableNode objects
var VariableNodePool = sync.Pool{
New: func() interface{} {
return &VariableNode{}
},
}
// GetVariableNode gets a VariableNode from the pool and initializes it
func GetVariableNode(name string, line int) *VariableNode {
node := VariableNodePool.Get().(*VariableNode)
node.name = name
node.line = line
return node
}
// ReleaseVariableNode returns a VariableNode to the pool
func ReleaseVariableNode(node *VariableNode) {
if node == nil {
return
}
node.name = ""
VariableNodePool.Put(node)
}
// 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)
}