mirror of
https://github.com/semihalev/twig.git
synced 2026-03-14 22:05:46 +01:00
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>
289 lines
6.6 KiB
Go
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)
|
|
}
|