@asamuzakjp/generational-cache 0.1.0 → 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/README.md +56 -31
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
|
|
7
7
|
A lightweight, **generational pseudo-LRU (Least Recently Used) cache** with strict maximum size limits.
|
|
8
8
|
|
|
9
|
-
`GenerationalCache` uses a two-generation strategy (Current and Old) to provide a balance between memory efficiency and high-speed access, making it particularly effective for workloads with frequent evictions.
|
|
10
|
-
|
|
11
9
|
## How it Works
|
|
12
10
|
|
|
13
11
|
`GenerationalCache` maintains two internal `Map` objects: `current` and `old`.
|
|
@@ -19,8 +17,9 @@ A lightweight, **generational pseudo-LRU (Least Recently Used) cache** with stri
|
|
|
19
17
|
This "pseudo-LRU" approach avoids the overhead of updating timestamps or complex linked list pointers on every single access.
|
|
20
18
|
|
|
21
19
|
## Installation
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
```bash
|
|
21
|
+
npm i @asamuzakjp/generational-cache
|
|
22
|
+
```
|
|
24
23
|
|
|
25
24
|
## Usage
|
|
26
25
|
```javascript
|
|
@@ -76,45 +75,71 @@ Benchmarks are divided into two states to simulate real-world conditions:
|
|
|
76
75
|
### Benchmark Environment
|
|
77
76
|
- **Engine:** Node.js v24.x (V8)
|
|
78
77
|
- **Measurement:** [mitata](https://github.com/evanwashere/mitata).
|
|
79
|
-
- **Comparison:** [LRUCache](https://www.npmjs.com/package/lru-cache) (v11.x), [QuickLRU](https://www.npmjs.com/package/quick-lru) (v7.x)
|
|
78
|
+
- **Comparison:** [LRUCache](https://www.npmjs.com/package/lru-cache) (v11.x), [QuickLRU](https://www.npmjs.com/package/quick-lru) (v7.x), [Mnemonist](https://www.npmjs.com/package/mnemonist) (v0.40.x)
|
|
80
79
|
|
|
81
80
|
### 1. Small Cache (Max Size = 512)
|
|
82
|
-
| Scenario | State | **GenerationalCache** | LRUCache | QuickLRU |
|
|
83
|
-
| :--- | :--- | :--- | :--- | :--- |
|
|
84
|
-
| **Set** | Cold | **17,
|
|
85
|
-
| | Warm | **
|
|
86
|
-
| **Get** | Cold |
|
|
87
|
-
| | Warm |
|
|
88
|
-
| **Eviction** | Cold | **
|
|
89
|
-
| | Warm | **
|
|
81
|
+
| Scenario | State | **GenerationalCache** | LRUCache | QuickLRU | Mnemonist |
|
|
82
|
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
83
|
+
| **Set** | Cold | **17,733,640 ops/sec** | 4,933,885 ops/sec | 13,506,212 ops/sec | **17,229,496 ops/sec** |
|
|
84
|
+
| | Warm | **23,030,861 ops/sec** | 15,216,068 ops/sec | 18,175,209 ops/sec | 19,409,937 ops/sec |
|
|
85
|
+
| **Get** | Cold | 17,717,930 ops/sec | 7,633,587 ops/sec | 13,734,377 ops/sec | **30,731,407 ops/sec** |
|
|
86
|
+
| | Warm | 21,724,961 ops/sec | 24,148,756 ops/sec | 16,385,384 ops/sec | **35,688,793 ops/sec** |
|
|
87
|
+
| **Eviction** | Cold | **16,700,066 ops/sec** | 6,953,619 ops/sec | 13,285,505 ops/sec | 4,925,865 ops/sec |
|
|
88
|
+
| | Warm | **23,148,148 ops/sec** | 9,040,773 ops/sec | 16,903,313 ops/sec | 8,037,293 ops/sec |
|
|
90
89
|
|
|
91
90
|
### 2. Medium Cache (Max Size = 2,048)
|
|
92
|
-
| Scenario | State | **GenerationalCache** | LRUCache | QuickLRU |
|
|
93
|
-
| :--- | :--- | :--- | :--- | :--- |
|
|
94
|
-
| **Set** | Cold | **
|
|
95
|
-
| | Warm | **19,
|
|
96
|
-
| **Get** | Cold |
|
|
97
|
-
| | Warm | 17,
|
|
98
|
-
| **Eviction** | Cold | **16,
|
|
99
|
-
| | Warm | **
|
|
91
|
+
| Scenario | State | **GenerationalCache** | LRUCache | QuickLRU | Mnemonist |
|
|
92
|
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
93
|
+
| **Set** | Cold | **15,987,210 ops/sec** | 4,874,957 ops/sec | 11,849,745 ops/sec | **15,309,246 ops/sec** |
|
|
94
|
+
| | Warm | **19,716,088 ops/sec** | 13,345,789 ops/sec | 14,755,791 ops/sec | 17,325,017 ops/sec |
|
|
95
|
+
| **Get** | Cold | 14,994,751 ops/sec | 7,950,389 ops/sec | 11,503,508 ops/sec | **23,651,844 ops/sec** |
|
|
96
|
+
| | Warm | 17,825,311 ops/sec | 18,789,928 ops/sec | 13,838,915 ops/sec | **31,289,111 ops/sec** |
|
|
97
|
+
| **Eviction** | Cold | **16,355,904 ops/sec** | 6,757,669 ops/sec | 12,074,378 ops/sec | 5,175,983 ops/sec |
|
|
98
|
+
| | Warm | **21,982,853 ops/sec** | 8,089,305 ops/sec | 15,309,246 ops/sec | 7,132,158 ops/sec |
|
|
100
99
|
|
|
101
100
|
### 3. Large Cache (Max Size = 8,192)
|
|
102
|
-
| Scenario | State | **GenerationalCache** | LRUCache | QuickLRU |
|
|
101
|
+
| Scenario | State | **GenerationalCache** | LRUCache | QuickLRU | Mnemonist |
|
|
102
|
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
103
|
+
| **Set** | Cold | **13,679,890 ops/sec** | 3,954,288 ops/sec | 8,126,777 ops/sec | 10,972,130 ops/sec |
|
|
104
|
+
| | Warm | **20,593,080 ops/sec** | 12,054,001 ops/sec | 12,995,451 ops/sec | 15,600,624 ops/sec |
|
|
105
|
+
| **Get** | Cold | 11,918,951 ops/sec | 5,785,363 ops/sec | 9,067,827 ops/sec | **16,784,155 ops/sec** |
|
|
106
|
+
| | Warm | 16,781,339 ops/sec | 17,247,326 ops/sec | 12,733,987 ops/sec | **31,436,655 ops/sec** |
|
|
107
|
+
| **Eviction** | Cold | **13,561,160 ops/sec** | 5,510,249 ops/sec | 9,642,271 ops/sec | 4,040,404 ops/sec |
|
|
108
|
+
| | Warm | **21,128,248 ops/sec** | 7,082,152 ops/sec | 13,208,294 ops/sec | 6,023,007 ops/sec |
|
|
109
|
+
|
|
110
|
+
### 4. Cyclic Access (Max Size = 8,192 / Working Set = 5,000)
|
|
111
|
+
| Metric | **GenerationalCache** | LRUCache | QuickLRU | Mnemonist |
|
|
103
112
|
| :--- | :--- | :--- | :--- | :--- |
|
|
104
|
-
| **
|
|
105
|
-
| |
|
|
106
|
-
| **Get** | Cold | **11,542,012 ops/sec** | 5,955,216 ops/sec | 8,841,732 ops/sec |
|
|
107
|
-
| | Warm | 17,322,016 ops/sec | **17,818,959 ops/sec** | 13,342,228 ops/sec |
|
|
108
|
-
| **Eviction** | Cold | **13,340,448 ops/sec** | 5,236,973 ops/sec | 9,320,533 ops/sec |
|
|
109
|
-
| | Warm | **19,409,937 ops/sec** | 6,889,424 ops/sec | 12,671,059 ops/sec |
|
|
113
|
+
| **Hit Rate** | 78.30% | **100.00%** | **100.00%** | **100.00%** |
|
|
114
|
+
| **Throughput** | 10,365,916 ops/sec | 40,832,993 ops/sec | 40,950,040 ops/sec | **48,426,150 ops/sec** |
|
|
110
115
|
|
|
111
|
-
|
|
116
|
+
## Key Characteristics
|
|
112
117
|
|
|
113
118
|
* **High Eviction Efficiency**: `GenerationalCache` demonstrates strong throughput during high-turnover workloads, maintaining a performance margin compared to standard LRU designs in large-scale eviction scenarios.
|
|
114
119
|
* **Predictable Scalability**: While other libraries may experience performance degradation as cache size increases, `GenerationalCache` maintains consistent throughput due to its generational swap mechanism.
|
|
115
120
|
* **Balanced Read/Write**: It provides stable and competitive performance across all basic operations (`get`, `set`), making it suitable for both read-heavy and write-heavy environments.
|
|
116
|
-
* **
|
|
117
|
-
|
|
121
|
+
* **Trade-offs**: In cyclic access patterns where the working set is greater than `max / 2` but smaller than `max`, `GenerationalCache` will experience frequent generation swaps and cache misses.
|
|
122
|
+
|
|
123
|
+
## Conclusion: When to Use GenerationalCache
|
|
124
|
+
|
|
125
|
+
`GenerationalCache` is not a one-size-fits-all solution.
|
|
126
|
+
Its effectiveness depends on the ratio between your **Working Set Size ($W$)** and the **Cache Capacity ($S$)**.
|
|
127
|
+
|
|
128
|
+
### 1. High-Turnover Environments ($W \ge S$)
|
|
129
|
+
This is the primary use case for `GenerationalCache`.
|
|
130
|
+
When your working set is larger than the cache size, evictions occur constantly.
|
|
131
|
+
* **Use case**: High-traffic APIs, memory-constrained containers, or streaming data where maintaining high system throughput is more critical than a perfect hit rate.
|
|
132
|
+
|
|
133
|
+
### 2. Over-Provisioned Environments ($S \ge 2W$)
|
|
134
|
+
If you have abundant memory, `GenerationalCache` operates as an ultra-low-overhead "passive" cache.
|
|
135
|
+
* **Use case**: Static master data or "hot" lookups where you want to minimize CPU cycles and maintain consistent sub-microsecond latency without any internal pointer updates.
|
|
136
|
+
|
|
137
|
+
### Avoid ($S/2 < W < S$)
|
|
138
|
+
Be cautious when your working set is larger than half the cache but smaller than the total capacity.
|
|
139
|
+
In this specific range, a standard LRU might achieve a 100% hit rate, while `GenerationalCache` will experience periodic misses due to generation swaps.
|
|
140
|
+
|
|
141
|
+
* **For Stability**: Set **$S \ge 2W$** (Eliminate swap misses)
|
|
142
|
+
* **For Throughput**: Set **$S < W$** (Maximize eviction speed)
|
|
118
143
|
|
|
119
144
|
## License
|
|
120
145
|
|
package/package.json
CHANGED
|
@@ -31,9 +31,10 @@
|
|
|
31
31
|
"eslint-plugin-prettier": "^5.5.5",
|
|
32
32
|
"eslint-plugin-regexp": "^3.1.0",
|
|
33
33
|
"eslint-plugin-unicorn": "^64.0.0",
|
|
34
|
-
"globals": "^17.
|
|
35
|
-
"lru-cache": "^11.3.
|
|
34
|
+
"globals": "^17.5.0",
|
|
35
|
+
"lru-cache": "^11.3.5",
|
|
36
36
|
"mitata": "^1.0.34",
|
|
37
|
+
"mnemonist": "^0.40.3",
|
|
37
38
|
"mocha": "^11.7.5",
|
|
38
39
|
"neostandard": "^0.13.0",
|
|
39
40
|
"prettier": "^3.8.2",
|
|
@@ -51,6 +52,7 @@
|
|
|
51
52
|
},
|
|
52
53
|
"scripts": {
|
|
53
54
|
"bench": "node --expose-gc ./benchmark/benchmark.js",
|
|
55
|
+
"bench:worst": "node --expose-gc ./benchmark/worst-case-benchmark.js",
|
|
54
56
|
"build": "npm run tsc && npm run lint && npm test",
|
|
55
57
|
"lint": "eslint --fix .",
|
|
56
58
|
"test": "c8 --reporter=text mocha --exit test/*.test.js",
|
|
@@ -59,5 +61,5 @@
|
|
|
59
61
|
"engines": {
|
|
60
62
|
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
|
61
63
|
},
|
|
62
|
-
"version": "
|
|
64
|
+
"version": "1.0.0"
|
|
63
65
|
}
|