slangmath 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.MD +366 -0
- package/LICENSE +21 -0
- package/README.md +953 -0
- package/package.json +23 -0
- package/slang-advanced.js +559 -0
- package/slang-basic.js +766 -0
- package/slang-cache.js +519 -0
- package/slang-convertor.js +652 -0
- package/slang-errors.js +454 -0
- package/slang-extended.js +501 -0
- package/slang-helpers.js +284 -0
- package/slang-math.js +3 -0
package/slang-cache.js
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SLaNg Performance Optimization and Caching System
|
|
3
|
+
* Provides caching, memoization, and performance monitoring
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// CACHE IMPLEMENTATION
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* LRU Cache implementation for expression caching
|
|
12
|
+
*/
|
|
13
|
+
class LRUCache {
|
|
14
|
+
constructor(maxSize = 1000) {
|
|
15
|
+
this.maxSize = maxSize;
|
|
16
|
+
this.cache = new Map();
|
|
17
|
+
this.hits = 0;
|
|
18
|
+
this.misses = 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get(key) {
|
|
22
|
+
if (this.cache.has(key)) {
|
|
23
|
+
// Move to end (most recently used)
|
|
24
|
+
const value = this.cache.get(key);
|
|
25
|
+
this.cache.delete(key);
|
|
26
|
+
this.cache.set(key, value);
|
|
27
|
+
this.hits++;
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
this.misses++;
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
set(key, value) {
|
|
35
|
+
if (this.cache.has(key)) {
|
|
36
|
+
this.cache.delete(key);
|
|
37
|
+
} else if (this.cache.size >= this.maxSize) {
|
|
38
|
+
// Remove least recently used (first item)
|
|
39
|
+
const firstKey = this.cache.keys().next().value;
|
|
40
|
+
this.cache.delete(firstKey);
|
|
41
|
+
}
|
|
42
|
+
this.cache.set(key, value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
has(key) {
|
|
46
|
+
return this.cache.has(key);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
clear() {
|
|
50
|
+
this.cache.clear();
|
|
51
|
+
this.hits = 0;
|
|
52
|
+
this.misses = 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getStats() {
|
|
56
|
+
const total = this.hits + this.misses;
|
|
57
|
+
return {
|
|
58
|
+
size: this.cache.size,
|
|
59
|
+
maxSize: this.maxSize,
|
|
60
|
+
hits: this.hits,
|
|
61
|
+
misses: this.misses,
|
|
62
|
+
hitRate: total > 0 ? (this.hits / total * 100).toFixed(2) + '%' : '0%'
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// PERFORMANCE MONITORING
|
|
69
|
+
// ============================================================================
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Performance monitor for tracking operation metrics
|
|
73
|
+
*/
|
|
74
|
+
class PerformanceMonitor {
|
|
75
|
+
constructor() {
|
|
76
|
+
this.operations = new Map();
|
|
77
|
+
this.globalStats = {
|
|
78
|
+
totalOperations: 0,
|
|
79
|
+
totalTime: 0,
|
|
80
|
+
averageTime: 0
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
startOperation(name) {
|
|
85
|
+
const startTime = performance.now();
|
|
86
|
+
this.operations.set(name, {
|
|
87
|
+
startTime,
|
|
88
|
+
endTime: null,
|
|
89
|
+
duration: null
|
|
90
|
+
});
|
|
91
|
+
return startTime;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
endOperation(name) {
|
|
95
|
+
const operation = this.operations.get(name);
|
|
96
|
+
if (operation) {
|
|
97
|
+
operation.endTime = performance.now();
|
|
98
|
+
operation.duration = operation.endTime - operation.startTime;
|
|
99
|
+
|
|
100
|
+
// Update global stats
|
|
101
|
+
this.globalStats.totalOperations++;
|
|
102
|
+
this.globalStats.totalTime += operation.duration;
|
|
103
|
+
this.globalStats.averageTime = this.globalStats.totalTime / this.globalStats.totalOperations;
|
|
104
|
+
|
|
105
|
+
return operation.duration;
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
getOperationStats(name) {
|
|
111
|
+
return this.operations.get(name);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getGlobalStats() {
|
|
115
|
+
return { ...this.globalStats };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
clear() {
|
|
119
|
+
this.operations.clear();
|
|
120
|
+
this.globalStats = {
|
|
121
|
+
totalOperations: 0,
|
|
122
|
+
totalTime: 0,
|
|
123
|
+
averageTime: 0
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ============================================================================
|
|
129
|
+
// MEMOIZATION
|
|
130
|
+
// ============================================================================
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Memoization decorator for functions
|
|
134
|
+
*/
|
|
135
|
+
export function memoize(fn, options = {}) {
|
|
136
|
+
const {
|
|
137
|
+
cacheSize = 100,
|
|
138
|
+
keyGenerator = (...args) => JSON.stringify(args),
|
|
139
|
+
ttl = null // Time to live in milliseconds
|
|
140
|
+
} = options;
|
|
141
|
+
|
|
142
|
+
const cache = new LRUCache(cacheSize);
|
|
143
|
+
const timestamps = new Map();
|
|
144
|
+
|
|
145
|
+
return function(...args) {
|
|
146
|
+
const key = keyGenerator(...args);
|
|
147
|
+
|
|
148
|
+
// Check TTL if specified
|
|
149
|
+
if (ttl && timestamps.has(key)) {
|
|
150
|
+
const age = Date.now() - timestamps.get(key);
|
|
151
|
+
if (age > ttl) {
|
|
152
|
+
cache.delete(key);
|
|
153
|
+
timestamps.delete(key);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check cache
|
|
158
|
+
const cached = cache.get(key);
|
|
159
|
+
if (cached !== null) {
|
|
160
|
+
return cached;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Compute and cache
|
|
164
|
+
const result = fn.apply(this, args);
|
|
165
|
+
cache.set(key, result);
|
|
166
|
+
timestamps.set(key, Date.now());
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Async memoization for promise-based functions
|
|
174
|
+
*/
|
|
175
|
+
export function memoizeAsync(fn, options = {}) {
|
|
176
|
+
const {
|
|
177
|
+
cacheSize = 100,
|
|
178
|
+
keyGenerator = (...args) => JSON.stringify(args),
|
|
179
|
+
ttl = null
|
|
180
|
+
} = options;
|
|
181
|
+
|
|
182
|
+
const cache = new LRUCache(cacheSize);
|
|
183
|
+
const timestamps = new Map();
|
|
184
|
+
const pending = new Map();
|
|
185
|
+
|
|
186
|
+
return async function(...args) {
|
|
187
|
+
const key = keyGenerator(...args);
|
|
188
|
+
|
|
189
|
+
// Check if already pending
|
|
190
|
+
if (pending.has(key)) {
|
|
191
|
+
return pending.get(key);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Check cache
|
|
195
|
+
const cached = cache.get(key);
|
|
196
|
+
if (cached !== null) {
|
|
197
|
+
return cached;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check TTL
|
|
201
|
+
if (ttl && timestamps.has(key)) {
|
|
202
|
+
const age = Date.now() - timestamps.get(key);
|
|
203
|
+
if (age > ttl) {
|
|
204
|
+
cache.delete(key);
|
|
205
|
+
timestamps.delete(key);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Create and cache promise
|
|
210
|
+
const promise = fn.apply(this, args).then(result => {
|
|
211
|
+
cache.set(key, result);
|
|
212
|
+
timestamps.set(key, Date.now());
|
|
213
|
+
pending.delete(key);
|
|
214
|
+
return result;
|
|
215
|
+
}).catch(error => {
|
|
216
|
+
pending.delete(key);
|
|
217
|
+
throw error;
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
pending.set(key, promise);
|
|
221
|
+
return promise;
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ============================================================================
|
|
226
|
+
// CACHED CONVERTER FUNCTIONS
|
|
227
|
+
// ============================================================================
|
|
228
|
+
|
|
229
|
+
// Global cache instances
|
|
230
|
+
export const latexToSlangCache = new LRUCache(500);
|
|
231
|
+
export const slangToLatexCache = new LRUCache(500);
|
|
232
|
+
export const validationCache = new LRUCache(200);
|
|
233
|
+
export const complexityCache = new LRUCache(200);
|
|
234
|
+
|
|
235
|
+
// Performance monitor
|
|
236
|
+
export const performanceMonitor = new PerformanceMonitor();
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Cached LaTeX to SLaNg conversion
|
|
240
|
+
*/
|
|
241
|
+
export function cachedLatexToSlang(latex, options = {}) {
|
|
242
|
+
const key = `latex2slang:${JSON.stringify(latex)}:${JSON.stringify(options)}`;
|
|
243
|
+
|
|
244
|
+
performanceMonitor.startOperation('latexToSlang');
|
|
245
|
+
|
|
246
|
+
const cached = latexToSlangCache.get(key);
|
|
247
|
+
if (cached !== null) {
|
|
248
|
+
performanceMonitor.endOperation('latexToSlang');
|
|
249
|
+
return cached;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Import here to avoid circular dependencies
|
|
253
|
+
import('./slang-convertor.js').then(({ latexToSlang }) => {
|
|
254
|
+
try {
|
|
255
|
+
const result = latexToSlang(latex, options);
|
|
256
|
+
latexToSlangCache.set(key, result);
|
|
257
|
+
performanceMonitor.endOperation('latexToSlang');
|
|
258
|
+
return result;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
performanceMonitor.endOperation('latexToSlang');
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Cached SLaNg to LaTeX conversion
|
|
268
|
+
*/
|
|
269
|
+
export function cachedSlangToLatex(slang, options = {}) {
|
|
270
|
+
const key = `slang2latex:${JSON.stringify(slang)}:${JSON.stringify(options)}`;
|
|
271
|
+
|
|
272
|
+
performanceMonitor.startOperation('slangToLatex');
|
|
273
|
+
|
|
274
|
+
const cached = slangToLatexCache.get(key);
|
|
275
|
+
if (cached !== null) {
|
|
276
|
+
performanceMonitor.endOperation('slangToLatex');
|
|
277
|
+
return cached;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
import('./slang-convertor.js').then(({ slangToLatex }) => {
|
|
281
|
+
try {
|
|
282
|
+
const result = slangToLatex(slang, options);
|
|
283
|
+
slangToLatexCache.set(key, result);
|
|
284
|
+
performanceMonitor.endOperation('slangToLatex');
|
|
285
|
+
return result;
|
|
286
|
+
} catch (error) {
|
|
287
|
+
performanceMonitor.endOperation('slangToLatex');
|
|
288
|
+
throw error;
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Cached validation
|
|
295
|
+
*/
|
|
296
|
+
export function cachedValidation(latex, options = {}) {
|
|
297
|
+
const key = `validate:${JSON.stringify(latex)}:${JSON.stringify(options)}`;
|
|
298
|
+
|
|
299
|
+
performanceMonitor.startOperation('validateLatex');
|
|
300
|
+
|
|
301
|
+
const cached = validationCache.get(key);
|
|
302
|
+
if (cached !== null) {
|
|
303
|
+
performanceMonitor.endOperation('validateLatex');
|
|
304
|
+
return cached;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
import('./slang-convertor.js').then(({ validateLatex }) => {
|
|
308
|
+
try {
|
|
309
|
+
const result = validateLatex(latex, options);
|
|
310
|
+
validationCache.set(key, result);
|
|
311
|
+
performanceMonitor.endOperation('validateLatex');
|
|
312
|
+
return result;
|
|
313
|
+
} catch (error) {
|
|
314
|
+
performanceMonitor.endOperation('validateLatex');
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Cached complexity calculation
|
|
322
|
+
*/
|
|
323
|
+
export function cachedComplexity(slang) {
|
|
324
|
+
const key = `complexity:${JSON.stringify(slang)}`;
|
|
325
|
+
|
|
326
|
+
performanceMonitor.startOperation('getExpressionComplexity');
|
|
327
|
+
|
|
328
|
+
const cached = complexityCache.get(key);
|
|
329
|
+
if (cached !== null) {
|
|
330
|
+
performanceMonitor.endOperation('getExpressionComplexity');
|
|
331
|
+
return cached;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
import('./slang-convertor.js').then(({ getExpressionComplexity }) => {
|
|
335
|
+
try {
|
|
336
|
+
const result = getExpressionComplexity(slang);
|
|
337
|
+
complexityCache.set(key, result);
|
|
338
|
+
performanceMonitor.endOperation('getExpressionComplexity');
|
|
339
|
+
return result;
|
|
340
|
+
} catch (error) {
|
|
341
|
+
performanceMonitor.endOperation('getExpressionComplexity');
|
|
342
|
+
throw error;
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ============================================================================
|
|
348
|
+
// BATCH PROCESSING OPTIMIZATIONS
|
|
349
|
+
// ============================================================================
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Optimized batch processing with parallel execution
|
|
353
|
+
*/
|
|
354
|
+
export async function optimizedBatchProcess(items, processor, options = {}) {
|
|
355
|
+
const {
|
|
356
|
+
batchSize = 10,
|
|
357
|
+
maxConcurrency = 4,
|
|
358
|
+
useCache = true,
|
|
359
|
+
progressCallback = null
|
|
360
|
+
} = options;
|
|
361
|
+
|
|
362
|
+
const results = [];
|
|
363
|
+
const startTime = performance.now();
|
|
364
|
+
|
|
365
|
+
// Process in batches with controlled concurrency
|
|
366
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
367
|
+
const batch = items.slice(i, i + batchSize);
|
|
368
|
+
|
|
369
|
+
const batchPromises = batch.map(async (item, batchIndex) => {
|
|
370
|
+
const globalIndex = i + batchIndex;
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
const result = await processor(item);
|
|
374
|
+
return { index: globalIndex, success: true, result };
|
|
375
|
+
} catch (error) {
|
|
376
|
+
return { index: globalIndex, success: false, error: error.message };
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Wait for current batch to complete
|
|
381
|
+
const batchResults = await Promise.all(batchPromises);
|
|
382
|
+
results.push(...batchResults);
|
|
383
|
+
|
|
384
|
+
// Progress callback
|
|
385
|
+
if (progressCallback) {
|
|
386
|
+
const progress = ((i + batchSize) / items.length) * 100;
|
|
387
|
+
progressCallback(Math.min(progress, 100), results);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const endTime = performance.now();
|
|
392
|
+
const duration = endTime - startTime;
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
results,
|
|
396
|
+
stats: {
|
|
397
|
+
totalItems: items.length,
|
|
398
|
+
successful: results.filter(r => r.success).length,
|
|
399
|
+
failed: results.filter(r => !r.success).length,
|
|
400
|
+
duration: duration.toFixed(2) + 'ms',
|
|
401
|
+
averageTime: (duration / items.length).toFixed(2) + 'ms'
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ============================================================================
|
|
407
|
+
// PERFORMANCE UTILITIES
|
|
408
|
+
// ============================================================================
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Get comprehensive performance statistics
|
|
412
|
+
*/
|
|
413
|
+
export function getPerformanceStats() {
|
|
414
|
+
return {
|
|
415
|
+
caches: {
|
|
416
|
+
latexToSlang: latexToSlangCache.getStats(),
|
|
417
|
+
slangToLatex: slangToLatexCache.getStats(),
|
|
418
|
+
validation: validationCache.getStats(),
|
|
419
|
+
complexity: complexityCache.getStats()
|
|
420
|
+
},
|
|
421
|
+
operations: performanceMonitor.getGlobalStats(),
|
|
422
|
+
memory: getMemoryUsage()
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Get memory usage statistics
|
|
428
|
+
*/
|
|
429
|
+
function getMemoryUsage() {
|
|
430
|
+
if (typeof performance !== 'undefined' && performance.memory) {
|
|
431
|
+
return {
|
|
432
|
+
used: formatBytes(performance.memory.usedJSHeapSize),
|
|
433
|
+
total: formatBytes(performance.memory.totalJSHeapSize),
|
|
434
|
+
limit: formatBytes(performance.memory.jsHeapSizeLimit)
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Format bytes to human readable format
|
|
442
|
+
*/
|
|
443
|
+
function formatBytes(bytes) {
|
|
444
|
+
if (bytes === 0) return '0 Bytes';
|
|
445
|
+
const k = 1024;
|
|
446
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
447
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
448
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Clear all caches
|
|
453
|
+
*/
|
|
454
|
+
export function clearAllCaches() {
|
|
455
|
+
latexToSlangCache.clear();
|
|
456
|
+
slangToLatexCache.clear();
|
|
457
|
+
validationCache.clear();
|
|
458
|
+
complexityCache.clear();
|
|
459
|
+
performanceMonitor.clear();
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Optimize cache sizes based on usage patterns
|
|
464
|
+
*/
|
|
465
|
+
export function optimizeCacheSizes() {
|
|
466
|
+
const stats = getPerformanceStats();
|
|
467
|
+
|
|
468
|
+
// Adjust cache sizes based on hit rates
|
|
469
|
+
if (parseFloat(stats.caches.latexToSlang.hitRate) > 80) {
|
|
470
|
+
latexToSlangCache.maxSize = Math.min(latexToSlangCache.maxSize * 1.5, 1000);
|
|
471
|
+
} else if (parseFloat(stats.caches.latexToSlang.hitRate) < 30) {
|
|
472
|
+
latexToSlangCache.maxSize = Math.max(latexToSlangCache.maxSize * 0.7, 100);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Similar optimization for other caches
|
|
476
|
+
if (parseFloat(stats.caches.slangToLatex.hitRate) > 80) {
|
|
477
|
+
slangToLatexCache.maxSize = Math.min(slangToLatexCache.maxSize * 1.5, 1000);
|
|
478
|
+
} else if (parseFloat(stats.caches.slangToLatex.hitRate) < 30) {
|
|
479
|
+
slangToLatexCache.maxSize = Math.max(slangToLatexCache.maxSize * 0.7, 100);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ============================================================================
|
|
484
|
+
// PERFORMANCE DECORATOR
|
|
485
|
+
// ============================================================================
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Performance monitoring decorator
|
|
489
|
+
*/
|
|
490
|
+
export function withPerformanceMonitoring(fn, name = fn.name) {
|
|
491
|
+
return function(...args) {
|
|
492
|
+
performanceMonitor.startOperation(name);
|
|
493
|
+
try {
|
|
494
|
+
const result = fn.apply(this, args);
|
|
495
|
+
|
|
496
|
+
// Handle both sync and async results
|
|
497
|
+
if (result && typeof result.then === 'function') {
|
|
498
|
+
return result.finally(() => {
|
|
499
|
+
performanceMonitor.endOperation(name);
|
|
500
|
+
});
|
|
501
|
+
} else {
|
|
502
|
+
performanceMonitor.endOperation(name);
|
|
503
|
+
return result;
|
|
504
|
+
}
|
|
505
|
+
} catch (error) {
|
|
506
|
+
performanceMonitor.endOperation(name);
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// ============================================================================
|
|
513
|
+
// EXPORTS
|
|
514
|
+
// ============================================================================
|
|
515
|
+
|
|
516
|
+
export {
|
|
517
|
+
LRUCache,
|
|
518
|
+
PerformanceMonitor
|
|
519
|
+
};
|