Skip to content

Commit 2d23a17

Browse files
committed
Feat: support plugin in python
1 parent 7c536db commit 2d23a17

7 files changed

Lines changed: 918 additions & 33 deletions

File tree

libCacheSim-python/README.md

Lines changed: 153 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ python -m pytest .
2222

2323
## Usage
2424

25+
### Basic Cache Usage
26+
2527
```python
2628
import libcachesim as cachesim
2729

@@ -38,7 +40,155 @@ hit = cache.get(req)
3840
print(f"Cache hit: {hit}")
3941
```
4042

41-
## Features
43+
### Custom Cache Policies
44+
45+
The Python binding supports custom cache replacement algorithms using Python function hooks - no C/C++ compilation required:
46+
47+
#### Python Hook Cache
48+
49+
Define custom cache policies using pure Python functions:
50+
51+
```python
52+
import libcachesim as cachesim
53+
from collections import OrderedDict
54+
55+
# Create a Python hook-based cache
56+
cache = cachesim.PythonHookCachePolicy(cache_size=1024*1024, cache_name="MyLRU")
57+
58+
# Define LRU policy hooks
59+
def init_hook(cache_size):
60+
return OrderedDict() # Track access order
61+
62+
def hit_hook(lru_dict, obj_id, obj_size):
63+
lru_dict.move_to_end(obj_id) # Move to end (most recent)
64+
65+
def miss_hook(lru_dict, obj_id, obj_size):
66+
lru_dict[obj_id] = True # Add to end
67+
68+
def eviction_hook(lru_dict, obj_id, obj_size):
69+
return next(iter(lru_dict)) # Return least recent
70+
71+
def remove_hook(lru_dict, obj_id):
72+
lru_dict.pop(obj_id, None)
73+
74+
# Set the hooks
75+
cache.set_hooks(init_hook, hit_hook, miss_hook, eviction_hook, remove_hook)
76+
77+
# Use it like any other cache
78+
req = cachesim.Request()
79+
req.obj_id = 1
80+
req.obj_size = 100
81+
hit = cache.get(req)
82+
```
83+
84+
### Available Cache Algorithms
85+
86+
The following built-in cache algorithms are available:
87+
88+
- **FIFO**: First-In-First-Out
89+
- **LRU**: Least Recently Used
90+
- **ARC**: Adaptive Replacement Cache
91+
- **Clock**: Clock algorithm
92+
- **S3FIFO**: Simple, Fast, Fair FIFO
93+
- **Sieve**: Sieve cache algorithm
94+
- **TinyLFU**: TinyLFU with window
95+
- **TwoQ**: Two-Queue algorithm
96+
- **LRB**: Learning-based cache (if enabled)
97+
- **ThreeLCache**: Three-level cache (if enabled)
98+
99+
Each algorithm can be used similarly:
100+
101+
```python
102+
# Examples of different cache algorithms
103+
lru_cache = cachesim.LRU(cache_size=1024*1024)
104+
arc_cache = cachesim.ARC(cache_size=1024*1024)
105+
s3fifo_cache = cachesim.S3FIFO(cache_size=1024*1024)
106+
```
107+
108+
### Custom Cache Implementation Example
109+
110+
Here's a complete example implementing a custom FIFO cache using Python hooks:
111+
112+
```python
113+
import libcachesim as cachesim
114+
from collections import deque
115+
116+
# Create a custom FIFO cache
117+
cache = cachesim.PythonHookCachePolicy(cache_size=1024, cache_name="CustomFIFO")
118+
119+
def init_hook(cache_size):
120+
return deque() # Use deque for FIFO order
121+
122+
def hit_hook(fifo_queue, obj_id, obj_size):
123+
pass # FIFO doesn't reorder on hit
124+
125+
def miss_hook(fifo_queue, obj_id, obj_size):
126+
fifo_queue.append(obj_id) # Add to end of queue
127+
128+
def eviction_hook(fifo_queue, obj_id, obj_size):
129+
return fifo_queue[0] # Return first item (oldest)
130+
131+
def remove_hook(fifo_queue, obj_id):
132+
if fifo_queue and fifo_queue[0] == obj_id:
133+
fifo_queue.popleft()
134+
135+
# Set the hooks
136+
cache.set_hooks(init_hook, hit_hook, miss_hook, eviction_hook, remove_hook)
137+
138+
# Test the cache
139+
req = cachesim.Request()
140+
req.obj_id = 1
141+
req.obj_size = 100
142+
hit = cache.get(req)
143+
print(f"Cache hit: {hit}") # Should be False (miss)
144+
```
145+
146+
### Testing and Validation
147+
148+
To ensure your custom cache implementation is correct, you can compare it against the built-in implementations:
149+
150+
```python
151+
import libcachesim as cachesim
152+
153+
# Test your custom cache against the built-in LRU
154+
def test_custom_vs_builtin():
155+
cache_size = 1024
156+
157+
# Your custom LRU implementation
158+
custom_cache = cachesim.PythonHookCachePolicy(cache_size, "CustomLRU")
159+
# ... set up your LRU hooks here ...
160+
161+
# Built-in LRU for comparison
162+
builtin_cache = cachesim.LRU(cache_size)
163+
164+
# Test with same request sequence
165+
test_requests = [(1, 100), (2, 100), (3, 100), (1, 100)]
166+
167+
for obj_id, obj_size in test_requests:
168+
req1 = cachesim.Request()
169+
req1.obj_id = obj_id
170+
req1.obj_size = obj_size
171+
172+
req2 = cachesim.Request()
173+
req2.obj_id = obj_id
174+
req2.obj_size = obj_size
175+
176+
custom_result = custom_cache.get(req1)
177+
builtin_result = builtin_cache.get(req2)
178+
179+
assert custom_result == builtin_result, f"Mismatch at obj_id {obj_id}"
180+
print(f"obj_id {obj_id}: {'HIT' if custom_result else 'MISS'}")
181+
```
182+
183+
### Hook Function Reference
184+
185+
When implementing `PythonHookCachePolicy`, you need to provide these hook functions:
186+
187+
- **`init_hook(cache_size: int) -> Any`**: Initialize and return plugin data structure
188+
- **`hit_hook(plugin_data: Any, obj_id: int, obj_size: int) -> None`**: Handle cache hits
189+
- **`miss_hook(plugin_data: Any, obj_id: int, obj_size: int) -> None`**: Handle cache misses
190+
- **`eviction_hook(plugin_data: Any, obj_id: int, obj_size: int) -> int`**: Return object ID to evict
191+
- **`remove_hook(plugin_data: Any, obj_id: int) -> None`**: Clean up when object is removed
192+
- **`free_hook(plugin_data: Any) -> None`**: [Optional] Clean up plugin resources
42193

43-
- [x] Support for multiple eviction policies (FIFO, LRU, ARC, Clock, etc.)
44-
- [ ] trace analysis tools
194+
The `plugin_data` is whatever object you return from `init_hook()` - it can be any Python object like a list, dict, class instance, etc.
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Example demonstrating how to create custom cache policies using Python hooks.
4+
5+
This example shows how to implement LRU and FIFO cache policies using the
6+
PythonHookCachePolicy class, which allows users to define cache behavior using
7+
pure Python functions instead of C/C++ plugins.
8+
"""
9+
10+
import libcachesim as lcs
11+
from collections import OrderedDict, deque
12+
13+
14+
class LRUPolicy:
15+
"""LRU (Least Recently Used) cache policy implementation."""
16+
17+
def __init__(self, cache_size):
18+
self.cache_size = cache_size
19+
self.access_order = OrderedDict() # obj_id -> True (for ordering)
20+
21+
def on_hit(self, obj_id, obj_size):
22+
"""Move accessed object to end (most recent)."""
23+
if obj_id in self.access_order:
24+
# Move to end (most recent)
25+
self.access_order.move_to_end(obj_id)
26+
27+
def on_miss(self, obj_id, obj_size):
28+
"""Add new object to end (most recent)."""
29+
self.access_order[obj_id] = True
30+
31+
def evict(self, obj_id, obj_size):
32+
"""Return the least recently used object ID."""
33+
if self.access_order:
34+
# Return first item (least recent)
35+
victim_id = next(iter(self.access_order))
36+
return victim_id
37+
raise RuntimeError("No objects to evict")
38+
39+
def on_remove(self, obj_id):
40+
"""Remove object from tracking."""
41+
self.access_order.pop(obj_id, None)
42+
43+
44+
class FIFOPolicy:
45+
"""FIFO (First In First Out) cache policy implementation."""
46+
47+
def __init__(self, cache_size):
48+
self.cache_size = cache_size
49+
self.insertion_order = deque() # obj_id queue
50+
51+
def on_hit(self, obj_id, obj_size):
52+
"""FIFO doesn't change order on hits."""
53+
pass
54+
55+
def on_miss(self, obj_id, obj_size):
56+
"""Add new object to end of queue."""
57+
self.insertion_order.append(obj_id)
58+
59+
def evict(self, obj_id, obj_size):
60+
"""Return the first inserted object ID."""
61+
if self.insertion_order:
62+
victim_id = self.insertion_order.popleft()
63+
return victim_id
64+
raise RuntimeError("No objects to evict")
65+
66+
def on_remove(self, obj_id):
67+
"""Remove object from tracking."""
68+
try:
69+
self.insertion_order.remove(obj_id)
70+
except ValueError:
71+
pass # Object not in queue
72+
73+
74+
def create_lru_cache(cache_size):
75+
"""Create an LRU cache using Python hooks."""
76+
cache = lcs.PythonHookCachePolicy(cache_size, "PythonLRU")
77+
78+
def init_hook(cache_size):
79+
return LRUPolicy(cache_size)
80+
81+
def hit_hook(policy, obj_id, obj_size):
82+
policy.on_hit(obj_id, obj_size)
83+
84+
def miss_hook(policy, obj_id, obj_size):
85+
policy.on_miss(obj_id, obj_size)
86+
87+
def eviction_hook(policy, obj_id, obj_size):
88+
return policy.evict(obj_id, obj_size)
89+
90+
def remove_hook(policy, obj_id):
91+
policy.on_remove(obj_id)
92+
93+
def free_hook(policy):
94+
# Python garbage collection handles cleanup
95+
pass
96+
97+
cache.set_hooks(init_hook, hit_hook, miss_hook, eviction_hook, remove_hook, free_hook)
98+
return cache
99+
100+
101+
def create_fifo_cache(cache_size):
102+
"""Create a FIFO cache using Python hooks."""
103+
cache = lcs.PythonHookCachePolicy(cache_size, "PythonFIFO")
104+
105+
def init_hook(cache_size):
106+
return FIFOPolicy(cache_size)
107+
108+
def hit_hook(policy, obj_id, obj_size):
109+
policy.on_hit(obj_id, obj_size)
110+
111+
def miss_hook(policy, obj_id, obj_size):
112+
policy.on_miss(obj_id, obj_size)
113+
114+
def eviction_hook(policy, obj_id, obj_size):
115+
return policy.evict(obj_id, obj_size)
116+
117+
def remove_hook(policy, obj_id):
118+
policy.on_remove(obj_id)
119+
120+
cache.set_hooks(init_hook, hit_hook, miss_hook, eviction_hook, remove_hook)
121+
return cache
122+
123+
124+
def test_cache_policy(cache, name):
125+
"""Test a cache policy with sample requests."""
126+
print(f"\n=== Testing {name} Cache ===")
127+
128+
# Test requests: obj_id, obj_size
129+
test_requests = [
130+
(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), # Fill cache
131+
(1, 100), # Hit
132+
(6, 100), # Miss, should evict something
133+
(2, 100), # Hit or miss depending on policy
134+
(7, 100), # Miss, should evict something
135+
]
136+
137+
hits = 0
138+
misses = 0
139+
140+
for obj_id, obj_size in test_requests:
141+
req = lcs.Request()
142+
req.obj_id = obj_id
143+
req.obj_size = obj_size
144+
145+
hit = cache.get(req)
146+
if hit:
147+
hits += 1
148+
print(f"Request {obj_id}: HIT")
149+
else:
150+
misses += 1
151+
print(f"Request {obj_id}: MISS")
152+
153+
print(f"Total: {hits} hits, {misses} misses")
154+
print(f"Cache stats: {cache.n_obj} objects, {cache.occupied_byte} bytes occupied")
155+
156+
157+
def main():
158+
"""Main example function."""
159+
cache_size = 400 # Bytes (can hold 4 objects of size 100 each)
160+
161+
# Test LRU cache
162+
lru_cache = create_lru_cache(cache_size)
163+
test_cache_policy(lru_cache, "LRU")
164+
165+
# Test FIFO cache
166+
fifo_cache = create_fifo_cache(cache_size)
167+
test_cache_policy(fifo_cache, "FIFO")
168+
169+
print("\n=== Comparison ===")
170+
print("LRU keeps recently accessed items, evicting least recently used")
171+
print("FIFO keeps items in insertion order, evicting oldest inserted")
172+
173+
174+
if __name__ == "__main__":
175+
main()

libCacheSim-python/libcachesim/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
ThreeLCache,
2222
TinyLFU,
2323
TwoQ,
24+
PythonHookCachePolicy,
2425
)
2526

2627
__all__ = [
@@ -38,6 +39,7 @@
3839
"TinyLFU",
3940
"TraceType",
4041
"TwoQ",
42+
"PythonHookCachePolicy",
4143
"__doc__",
4244
"__version__",
4345
"create_cache",

0 commit comments

Comments
 (0)