mirror of
https://github.com/semihalev/twig.git
synced 2026-03-14 13:55: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>
475 lines
No EOL
11 KiB
Go
475 lines
No EOL
11 KiB
Go
package twig
|
|
|
|
import (
|
|
"sync"
|
|
)
|
|
|
|
// This file implements object pooling for expression node types
|
|
// to reduce memory allocations during evaluation.
|
|
|
|
// BinaryNodePool provides a pool for BinaryNode objects
|
|
var BinaryNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &BinaryNode{}
|
|
},
|
|
}
|
|
|
|
// GetBinaryNode gets a BinaryNode from the pool and initializes it
|
|
func GetBinaryNode(operator string, left, right Node, line int) *BinaryNode {
|
|
node := BinaryNodePool.Get().(*BinaryNode)
|
|
node.ExpressionNode.exprType = ExprBinary
|
|
node.ExpressionNode.line = line
|
|
node.operator = operator
|
|
node.left = left
|
|
node.right = right
|
|
return node
|
|
}
|
|
|
|
// ReleaseBinaryNode returns a BinaryNode to the pool
|
|
func ReleaseBinaryNode(node *BinaryNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.operator = ""
|
|
node.left = nil
|
|
node.right = nil
|
|
BinaryNodePool.Put(node)
|
|
}
|
|
|
|
// GetAttrNodePool provides a pool for GetAttrNode objects
|
|
var GetAttrNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &GetAttrNode{}
|
|
},
|
|
}
|
|
|
|
// GetGetAttrNode gets a GetAttrNode from the pool and initializes it
|
|
func GetGetAttrNode(node, attribute Node, line int) *GetAttrNode {
|
|
n := GetAttrNodePool.Get().(*GetAttrNode)
|
|
n.ExpressionNode.exprType = ExprGetAttr
|
|
n.ExpressionNode.line = line
|
|
n.node = node
|
|
n.attribute = attribute
|
|
return n
|
|
}
|
|
|
|
// ReleaseGetAttrNode returns a GetAttrNode to the pool
|
|
func ReleaseGetAttrNode(node *GetAttrNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.node = nil
|
|
node.attribute = nil
|
|
GetAttrNodePool.Put(node)
|
|
}
|
|
|
|
// GetItemNodePool provides a pool for GetItemNode objects
|
|
var GetItemNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &GetItemNode{}
|
|
},
|
|
}
|
|
|
|
// GetGetItemNode gets a GetItemNode from the pool and initializes it
|
|
func GetGetItemNode(node, item Node, line int) *GetItemNode {
|
|
n := GetItemNodePool.Get().(*GetItemNode)
|
|
n.ExpressionNode.exprType = ExprGetItem
|
|
n.ExpressionNode.line = line
|
|
n.node = node
|
|
n.item = item
|
|
return n
|
|
}
|
|
|
|
// ReleaseGetItemNode returns a GetItemNode to the pool
|
|
func ReleaseGetItemNode(node *GetItemNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.node = nil
|
|
node.item = nil
|
|
GetItemNodePool.Put(node)
|
|
}
|
|
|
|
// FilterNodePool provides a pool for FilterNode objects
|
|
var FilterNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &FilterNode{}
|
|
},
|
|
}
|
|
|
|
// GetFilterNode gets a FilterNode from the pool and initializes it
|
|
func GetFilterNode(node Node, filter string, args []Node, line int) *FilterNode {
|
|
n := FilterNodePool.Get().(*FilterNode)
|
|
n.ExpressionNode.exprType = ExprFilter
|
|
n.ExpressionNode.line = line
|
|
n.node = node
|
|
n.filter = filter
|
|
n.args = args
|
|
return n
|
|
}
|
|
|
|
// ReleaseFilterNode returns a FilterNode to the pool
|
|
func ReleaseFilterNode(node *FilterNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.node = nil
|
|
node.filter = ""
|
|
node.args = nil
|
|
FilterNodePool.Put(node)
|
|
}
|
|
|
|
// TestNodePool provides a pool for TestNode objects
|
|
var TestNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &TestNode{}
|
|
},
|
|
}
|
|
|
|
// GetTestNode gets a TestNode from the pool and initializes it
|
|
func GetTestNode(node Node, test string, args []Node, line int) *TestNode {
|
|
n := TestNodePool.Get().(*TestNode)
|
|
n.ExpressionNode.exprType = ExprTest
|
|
n.ExpressionNode.line = line
|
|
n.node = node
|
|
n.test = test
|
|
n.args = args
|
|
return n
|
|
}
|
|
|
|
// ReleaseTestNode returns a TestNode to the pool
|
|
func ReleaseTestNode(node *TestNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.node = nil
|
|
node.test = ""
|
|
node.args = nil
|
|
TestNodePool.Put(node)
|
|
}
|
|
|
|
// UnaryNodePool provides a pool for UnaryNode objects
|
|
var UnaryNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &UnaryNode{}
|
|
},
|
|
}
|
|
|
|
// GetUnaryNode gets a UnaryNode from the pool and initializes it
|
|
func GetUnaryNode(operator string, node Node, line int) *UnaryNode {
|
|
n := UnaryNodePool.Get().(*UnaryNode)
|
|
n.ExpressionNode.exprType = ExprUnary
|
|
n.ExpressionNode.line = line
|
|
n.operator = operator
|
|
n.node = node
|
|
return n
|
|
}
|
|
|
|
// ReleaseUnaryNode returns a UnaryNode to the pool
|
|
func ReleaseUnaryNode(node *UnaryNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.operator = ""
|
|
node.node = nil
|
|
UnaryNodePool.Put(node)
|
|
}
|
|
|
|
// ConditionalNodePool provides a pool for ConditionalNode objects
|
|
var ConditionalNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &ConditionalNode{}
|
|
},
|
|
}
|
|
|
|
// GetConditionalNode gets a ConditionalNode from the pool and initializes it
|
|
func GetConditionalNode(condition, trueExpr, falseExpr Node, line int) *ConditionalNode {
|
|
node := ConditionalNodePool.Get().(*ConditionalNode)
|
|
node.ExpressionNode.exprType = ExprConditional
|
|
node.ExpressionNode.line = line
|
|
node.condition = condition
|
|
node.trueExpr = trueExpr
|
|
node.falseExpr = falseExpr
|
|
return node
|
|
}
|
|
|
|
// ReleaseConditionalNode returns a ConditionalNode to the pool
|
|
func ReleaseConditionalNode(node *ConditionalNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.condition = nil
|
|
node.trueExpr = nil
|
|
node.falseExpr = nil
|
|
ConditionalNodePool.Put(node)
|
|
}
|
|
|
|
// ArrayNodePool provides a pool for ArrayNode objects
|
|
var ArrayNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &ArrayNode{}
|
|
},
|
|
}
|
|
|
|
// GetArrayNode gets an ArrayNode from the pool and initializes it
|
|
func GetArrayNode(items []Node, line int) *ArrayNode {
|
|
node := ArrayNodePool.Get().(*ArrayNode)
|
|
node.ExpressionNode.exprType = ExprArray
|
|
node.ExpressionNode.line = line
|
|
node.items = items
|
|
return node
|
|
}
|
|
|
|
// ReleaseArrayNode returns an ArrayNode to the pool
|
|
func ReleaseArrayNode(node *ArrayNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.items = nil
|
|
ArrayNodePool.Put(node)
|
|
}
|
|
|
|
// HashNodePool provides a pool for HashNode objects
|
|
var HashNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &HashNode{}
|
|
},
|
|
}
|
|
|
|
// GetHashNode gets a HashNode from the pool and initializes it
|
|
func GetHashNode(items map[Node]Node, line int) *HashNode {
|
|
node := HashNodePool.Get().(*HashNode)
|
|
node.ExpressionNode.exprType = ExprHash
|
|
node.ExpressionNode.line = line
|
|
node.items = items
|
|
return node
|
|
}
|
|
|
|
// ReleaseHashNode returns a HashNode to the pool
|
|
func ReleaseHashNode(node *HashNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.items = nil
|
|
HashNodePool.Put(node)
|
|
}
|
|
|
|
// FunctionNodePool provides a pool for FunctionNode objects
|
|
var FunctionNodePool = sync.Pool{
|
|
New: func() interface{} {
|
|
return &FunctionNode{}
|
|
},
|
|
}
|
|
|
|
// GetFunctionNode gets a FunctionNode from the pool and initializes it
|
|
func GetFunctionNode(name string, args []Node, line int) *FunctionNode {
|
|
node := FunctionNodePool.Get().(*FunctionNode)
|
|
node.ExpressionNode.exprType = ExprFunction
|
|
node.ExpressionNode.line = line
|
|
node.name = name
|
|
node.args = args
|
|
return node
|
|
}
|
|
|
|
// ReleaseFunctionNode returns a FunctionNode to the pool
|
|
func ReleaseFunctionNode(node *FunctionNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.name = ""
|
|
node.args = nil
|
|
node.moduleExpr = nil
|
|
FunctionNodePool.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.ExpressionNode.exprType = ExprVariable
|
|
node.ExpressionNode.line = line
|
|
node.name = name
|
|
return node
|
|
}
|
|
|
|
// ReleaseVariableNode returns a VariableNode to the pool
|
|
func ReleaseVariableNode(node *VariableNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.name = ""
|
|
VariableNodePool.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.ExpressionNode.exprType = ExprLiteral
|
|
node.ExpressionNode.line = line
|
|
node.value = value
|
|
return node
|
|
}
|
|
|
|
// ReleaseLiteralNode returns a LiteralNode to the pool
|
|
func ReleaseLiteralNode(node *LiteralNode) {
|
|
if node == nil {
|
|
return
|
|
}
|
|
node.value = nil
|
|
LiteralNodePool.Put(node)
|
|
}
|
|
|
|
// --- Slice Pools for Expression Evaluation ---
|
|
|
|
// smallArgSlicePool provides a pool for small argument slices (0-2 items)
|
|
var smallArgSlicePool = sync.Pool{
|
|
New: func() interface{} {
|
|
// Pre-allocate a slice with capacity 2
|
|
return make([]interface{}, 0, 2)
|
|
},
|
|
}
|
|
|
|
// mediumArgSlicePool provides a pool for medium argument slices (3-5 items)
|
|
var mediumArgSlicePool = sync.Pool{
|
|
New: func() interface{} {
|
|
// Pre-allocate a slice with capacity 5
|
|
return make([]interface{}, 0, 5)
|
|
},
|
|
}
|
|
|
|
// largeArgSlicePool provides a pool for large argument slices (6-10 items)
|
|
var largeArgSlicePool = sync.Pool{
|
|
New: func() interface{} {
|
|
// Pre-allocate a slice with capacity 10
|
|
return make([]interface{}, 0, 10)
|
|
},
|
|
}
|
|
|
|
// GetArgSlice gets an appropriately sized slice for the given number of arguments
|
|
func GetArgSlice(size int) []interface{} {
|
|
if size <= 0 {
|
|
return nil
|
|
}
|
|
|
|
var slice []interface{}
|
|
|
|
switch {
|
|
case size <= 2:
|
|
slice = smallArgSlicePool.Get().([]interface{})
|
|
case size <= 5:
|
|
slice = mediumArgSlicePool.Get().([]interface{})
|
|
case size <= 10:
|
|
slice = largeArgSlicePool.Get().([]interface{})
|
|
default:
|
|
// For very large slices, just allocate directly
|
|
return make([]interface{}, 0, size)
|
|
}
|
|
|
|
// Clear the slice but maintain capacity
|
|
return slice[:0]
|
|
}
|
|
|
|
// ReleaseArgSlice returns an argument slice to the appropriate pool
|
|
func ReleaseArgSlice(slice []interface{}) {
|
|
if slice == nil {
|
|
return
|
|
}
|
|
|
|
// Clear all references
|
|
for i := range slice {
|
|
slice[i] = nil
|
|
}
|
|
|
|
// Reset length to 0
|
|
slice = slice[:0]
|
|
|
|
// Return to appropriate pool based on capacity
|
|
switch cap(slice) {
|
|
case 2:
|
|
smallArgSlicePool.Put(slice)
|
|
case 5:
|
|
mediumArgSlicePool.Put(slice)
|
|
case 10:
|
|
largeArgSlicePool.Put(slice)
|
|
}
|
|
}
|
|
|
|
// --- Map Pools for HashNode Evaluation ---
|
|
|
|
// smallHashMapPool provides a pool for small hash maps (1-5 items)
|
|
var smallHashMapPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return make(map[string]interface{}, 5)
|
|
},
|
|
}
|
|
|
|
// mediumHashMapPool provides a pool for medium hash maps (6-15 items)
|
|
var mediumHashMapPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return make(map[string]interface{}, 15)
|
|
},
|
|
}
|
|
|
|
// GetHashMap gets an appropriately sized map for hash operations
|
|
func GetHashMap(size int) map[string]interface{} {
|
|
if size <= 5 {
|
|
hashMap := smallHashMapPool.Get().(map[string]interface{})
|
|
// Clear any existing entries
|
|
for k := range hashMap {
|
|
delete(hashMap, k)
|
|
}
|
|
return hashMap
|
|
} else if size <= 15 {
|
|
hashMap := mediumHashMapPool.Get().(map[string]interface{})
|
|
// Clear any existing entries
|
|
for k := range hashMap {
|
|
delete(hashMap, k)
|
|
}
|
|
return hashMap
|
|
}
|
|
|
|
// For larger maps, just allocate directly
|
|
return make(map[string]interface{}, size)
|
|
}
|
|
|
|
// ReleaseHashMap returns a hash map to the appropriate pool
|
|
func ReleaseHashMap(hashMap map[string]interface{}) {
|
|
if hashMap == nil {
|
|
return
|
|
}
|
|
|
|
// Clear all entries (not used directly in our defer block)
|
|
// for k := range hashMap {
|
|
// delete(hashMap, k)
|
|
// }
|
|
|
|
// Return to appropriate pool based on capacity
|
|
// We don't actually clear the map when releasing through the defer,
|
|
// because we return the map as the result and deleting entries would
|
|
// clear the returned result
|
|
|
|
// Map doesn't have a built-in cap function
|
|
// Not using pool return for maps directly returned as results
|
|
|
|
/*
|
|
switch {
|
|
case len(hashMap) <= 5:
|
|
smallHashMapPool.Put(hashMap)
|
|
case len(hashMap) <= 15:
|
|
mediumHashMapPool.Put(hashMap)
|
|
}
|
|
*/
|
|
} |