diff options
Diffstat (limited to 'js/baba-yaga/src/core/ast-pool.js')
-rw-r--r-- | js/baba-yaga/src/core/ast-pool.js | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/js/baba-yaga/src/core/ast-pool.js b/js/baba-yaga/src/core/ast-pool.js new file mode 100644 index 0000000..0569c6c --- /dev/null +++ b/js/baba-yaga/src/core/ast-pool.js @@ -0,0 +1,526 @@ +// ast-pool.js - Object pooling for AST nodes to reduce GC pressure + +/** + * Object pool for AST nodes to reduce garbage collection overhead + * Provides significant performance improvement for large programs + */ + +export class ASTNodePool { + constructor() { + // Pools for different node types + this.pools = new Map(); + + // Pool configuration + this.maxPoolSize = 1000; // Maximum objects per pool + this.initialPoolSize = 50; // Initial objects to create + + // Statistics + this.stats = { + created: 0, + reused: 0, + returned: 0, + poolHits: 0, + poolMisses: 0 + }; + + // Initialize common node type pools + this.initializePools(); + } + + /** + * Initialize pools for common AST node types + */ + initializePools() { + const commonTypes = [ + 'BinaryExpression', + 'UnaryExpression', + 'FunctionCall', + 'Identifier', + 'NumberLiteral', + 'StringLiteral', + 'BooleanLiteral', + 'ListLiteral', + 'TableLiteral', + 'MemberExpression', + 'WhenExpression', + 'WhenCase', + 'AnonymousFunction' + ]; + + for (const type of commonTypes) { + this.pools.set(type, []); + + // Pre-populate with initial objects + for (let i = 0; i < this.initialPoolSize; i++) { + this.pools.get(type).push(this.createFreshNode(type)); + } + } + } + + /** + * Create a fresh AST node of the specified type + */ + createFreshNode(type) { + const node = { type }; + + // Initialize common properties based on node type + switch (type) { + case 'BinaryExpression': + node.operator = null; + node.left = null; + node.right = null; + break; + + case 'UnaryExpression': + node.operator = null; + node.operand = null; + break; + + case 'FunctionCall': + node.callee = null; + node.arguments = []; + break; + + case 'Identifier': + node.name = null; + break; + + case 'NumberLiteral': + node.value = 0; + node.isFloat = false; + break; + + case 'StringLiteral': + node.value = ''; + break; + + case 'BooleanLiteral': + node.value = false; + break; + + case 'ListLiteral': + node.elements = []; + break; + + case 'TableLiteral': + node.properties = []; + break; + + case 'MemberExpression': + node.object = null; + node.property = null; + break; + + case 'WhenExpression': + node.discriminants = []; + node.cases = []; + break; + + case 'WhenCase': + node.patterns = []; + node.consequent = null; + break; + + case 'AnonymousFunction': + node.params = []; + node.body = null; + break; + } + + this.stats.created++; + return node; + } + + /** + * Get a node from the pool or create a new one + */ + acquire(type, properties = {}) { + const pool = this.pools.get(type); + let node; + + if (pool && pool.length > 0) { + node = pool.pop(); + this.stats.reused++; + this.stats.poolHits++; + } else { + node = this.createFreshNode(type); + this.stats.poolMisses++; + } + + // Apply provided properties + Object.assign(node, properties); + + return node; + } + + /** + * Return a node to the pool for reuse + */ + release(node) { + if (!node || !node.type) { + return; + } + + const pool = this.pools.get(node.type); + if (!pool) { + // Create pool for unknown type + this.pools.set(node.type, []); + this.pools.get(node.type).push(node); + this.stats.returned++; + return; + } + + // Don't exceed maximum pool size + if (pool.length >= this.maxPoolSize) { + return; + } + + // Reset node properties + this.resetNode(node); + + pool.push(node); + this.stats.returned++; + } + + /** + * Reset a node to its initial state + */ + resetNode(node) { + const type = node.type; + + // Clear all properties except type + for (const key of Object.keys(node)) { + if (key !== 'type') { + delete node[key]; + } + } + + // Reinitialize based on type + switch (type) { + case 'BinaryExpression': + node.operator = null; + node.left = null; + node.right = null; + break; + + case 'UnaryExpression': + node.operator = null; + node.operand = null; + break; + + case 'FunctionCall': + node.callee = null; + node.arguments = []; + break; + + case 'Identifier': + node.name = null; + break; + + case 'NumberLiteral': + node.value = 0; + node.isFloat = false; + break; + + case 'StringLiteral': + node.value = ''; + break; + + case 'BooleanLiteral': + node.value = false; + break; + + case 'ListLiteral': + node.elements = []; + break; + + case 'TableLiteral': + node.properties = []; + break; + + case 'MemberExpression': + node.object = null; + node.property = null; + break; + + case 'WhenExpression': + node.discriminants = []; + node.cases = []; + break; + + case 'WhenCase': + node.patterns = []; + node.consequent = null; + break; + + case 'AnonymousFunction': + node.params = []; + node.body = null; + break; + } + } + + /** + * Recursively release an entire AST tree + */ + releaseTree(node) { + if (!node || typeof node !== 'object') { + return; + } + + // Release child nodes first + this.visitChildren(node, (child) => { + this.releaseTree(child); + }); + + // Release this node + this.release(node); + } + + /** + * Visit all child nodes of an AST node + */ + visitChildren(node, callback) { + if (!node || typeof node !== 'object') { + return; + } + + switch (node.type) { + case 'BinaryExpression': + if (node.left) callback(node.left); + if (node.right) callback(node.right); + break; + + case 'UnaryExpression': + if (node.operand) callback(node.operand); + break; + + case 'FunctionCall': + if (node.callee) callback(node.callee); + if (node.arguments) { + for (const arg of node.arguments) { + callback(arg); + } + } + break; + + case 'ListLiteral': + if (node.elements) { + for (const element of node.elements) { + callback(element); + } + } + break; + + case 'TableLiteral': + if (node.properties) { + for (const prop of node.properties) { + if (prop.value) callback(prop.value); + } + } + break; + + case 'MemberExpression': + if (node.object) callback(node.object); + if (node.property) callback(node.property); + break; + + case 'WhenExpression': + if (node.discriminants) { + for (const discriminant of node.discriminants) { + callback(discriminant); + } + } + if (node.cases) { + for (const whenCase of node.cases) { + callback(whenCase); + } + } + break; + + case 'WhenCase': + if (node.patterns) { + for (const pattern of node.patterns) { + callback(pattern); + } + } + if (node.consequent) callback(node.consequent); + break; + + case 'AnonymousFunction': + if (node.body) callback(node.body); + break; + + case 'Program': + if (node.body) { + for (const statement of node.body) { + callback(statement); + } + } + break; + } + } + + /** + * Get pool statistics + */ + getStats() { + const totalRequests = this.stats.poolHits + this.stats.poolMisses; + const hitRate = totalRequests > 0 ? this.stats.poolHits / totalRequests : 0; + const reuseRate = this.stats.created > 0 ? this.stats.reused / this.stats.created : 0; + + const poolSizes = {}; + for (const [type, pool] of this.pools.entries()) { + poolSizes[type] = pool.length; + } + + return { + ...this.stats, + hitRate, + reuseRate, + poolCount: this.pools.size, + poolSizes, + totalPooledObjects: Array.from(this.pools.values()).reduce((sum, pool) => sum + pool.length, 0) + }; + } + + /** + * Reset all statistics + */ + resetStats() { + this.stats = { + created: 0, + reused: 0, + returned: 0, + poolHits: 0, + poolMisses: 0 + }; + } + + /** + * Clear all pools and reset + */ + clear() { + this.pools.clear(); + this.resetStats(); + this.initializePools(); + } + + /** + * Warm up pools by pre-creating objects + */ + warmUp(type, count = 100) { + if (!this.pools.has(type)) { + this.pools.set(type, []); + } + + const pool = this.pools.get(type); + for (let i = 0; i < count; i++) { + if (pool.length < this.maxPoolSize) { + pool.push(this.createFreshNode(type)); + } + } + } +} + +/** + * Global AST node pool instance + */ +export const globalASTPool = new ASTNodePool(); + +/** + * Convenience functions for common operations + */ +export const pooledNodes = { + binaryExpression: (operator, left, right) => + globalASTPool.acquire('BinaryExpression', { operator, left, right }), + + unaryExpression: (operator, operand) => + globalASTPool.acquire('UnaryExpression', { operator, operand }), + + functionCall: (callee, args) => + globalASTPool.acquire('FunctionCall', { callee, arguments: args }), + + identifier: (name) => + globalASTPool.acquire('Identifier', { name }), + + numberLiteral: (value, isFloat = false) => + globalASTPool.acquire('NumberLiteral', { value, isFloat }), + + stringLiteral: (value) => + globalASTPool.acquire('StringLiteral', { value }), + + booleanLiteral: (value) => + globalASTPool.acquire('BooleanLiteral', { value }), + + listLiteral: (elements) => + globalASTPool.acquire('ListLiteral', { elements }), + + tableLiteral: (properties) => + globalASTPool.acquire('TableLiteral', { properties }), + + memberExpression: (object, property) => + globalASTPool.acquire('MemberExpression', { object, property }), + + whenExpression: (discriminants, cases) => + globalASTPool.acquire('WhenExpression', { discriminants, cases }), + + anonymousFunction: (params, body) => + globalASTPool.acquire('AnonymousFunction', { params, body }) +}; + +/** + * Benchmark AST node pool performance + */ +export async function benchmarkASTPool(iterations = 100000) { + console.log(`Benchmarking AST node pool with ${iterations} iterations...`); + + // Benchmark without pooling + const nPoolStart = performance.now(); + const nodes1 = []; + + for (let i = 0; i < iterations; i++) { + nodes1.push({ + type: 'BinaryExpression', + operator: '+', + left: { type: 'NumberLiteral', value: i }, + right: { type: 'NumberLiteral', value: i + 1 } + }); + } + + const nPoolTime = performance.now() - nPoolStart; + + // Benchmark with pooling + const pool = new ASTNodePool(); + const poolStart = performance.now(); + const nodes2 = []; + + for (let i = 0; i < iterations; i++) { + const left = pool.acquire('NumberLiteral', { value: i }); + const right = pool.acquire('NumberLiteral', { value: i + 1 }); + const expr = pool.acquire('BinaryExpression', { operator: '+', left, right }); + nodes2.push(expr); + } + + // Return nodes to pool + for (const node of nodes2) { + pool.releaseTree(node); + } + + const poolTime = performance.now() - poolStart; + + console.log(`Without pooling: ${nPoolTime.toFixed(2)}ms`); + console.log(`With pooling: ${poolTime.toFixed(2)}ms`); + console.log(`Speedup: ${(nPoolTime / poolTime).toFixed(2)}x`); + + const stats = pool.getStats(); + console.log(`Pool hit rate: ${(stats.hitRate * 100).toFixed(1)}%`); + console.log(`Reuse rate: ${(stats.reuseRate * 100).toFixed(1)}%`); + + return { + nPoolTime, + poolTime, + speedup: nPoolTime / poolTime, + stats + }; +} |