Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ include_directories(${MAIN_PROJECT_SOURCE_DIR}/libCacheSim/bin)
set(PYTHON_MODULE_SOURCES
src/export.cpp
src/export_cache.cpp
src/export_admissioner.cpp
src/export_reader.cpp
src/export_analyzer.cpp
src/export_misc.cpp
Expand Down
34 changes: 30 additions & 4 deletions docs/src/en/examples/plugins.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Plugin System

We enable user add any customized cache via libCacheSim's plugin system.
## PluginCache

With user-defined sive python hook functions,
We enable users to add any customized cache via libCacheSim's plugin system.

With user-defined python hook functions,

```c++
py::function cache_init_hook;
Expand All @@ -15,8 +17,7 @@ With user-defined sive python hook functions,

We can simulate and determine the cache eviction behavior from the python side.

Here is the signature requirement for these hook functions.

Here are the signature requirements for these hook functions.
```python
def cache_init_hook(ccparams: CommonCacheParams) -> CustomizedCacheData: ...
def cache_hit_hook(data: CustomizedCacheData, req: Request) -> None: ...
Expand All @@ -25,3 +26,28 @@ def cache_eviction_hook(data: CustomizedCacheData, req: Request) -> int | str: .
def cache_remove_hook(data: CustomizedCacheData, obj_id: int | str) ->: ...
def cache_free_hook(data: CustomizedCacheData) ->: ...
```

## PluginAdmissioner

We enable users to define their own admission policies via libCacheSim's plugin system, which can be used in conjunction with existing cache implementations (e.g., `LRU`, `S3FIFO`).

With user-defined python hook functions:

```c++
py::function admissioner_init_hook;
py::function admissioner_admit_hook;
py::function admissioner_update_hook;
py::function admissioner_clone_hook;
py::function admissioner_free_hook;
```

We have complete control over which objects are admitted into the underlying cache conveniently from Python.

Here are the signature requirements for these hook functions.
```python
def admissioner_init_hook() -> CustomizedAdmissionerData: ...
def admissioner_admit_hook(data: CustomizedAdmissionerData, req: Request) -> bool: ...
def admissioner_update_hook(data: CustomizedAdmissionerData, req: Request, cache_size: int) -> None: ...
def admissioner_clone_hook(data: CustomizedAdmissionerData) -> AdmissionerBase: ...
def admissioner_free_hook(data: CustomizedAdmissionerData) -> None: ...
```
175 changes: 174 additions & 1 deletion docs/src/en/examples/simulation.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,176 @@
# Cache Simulation

[TBD]
## Basic Usage

The cache classes are the core of cache simulation. When an instance of a cache is creates (e.g., `LRU`, `S3FIFO`), we can configure the cache size and any cache-specific parameters such as promotion thresholds.

```py
import libcachesim as lcs

# Initialize cache
cache = lcs.S3FIFO(
cache_size=1024 * 1024,
# Cache specific parameters
small_size_ratio=0.2,
ghost_size_ratio=0.8,
move_to_main_threshold=2,
)
```

Admission policies are optional - if none is provided, the cache will simply admit all objects according to the replacement policy. An admissioner (e.g., `BloomFilterAdmissioner`) can be placed infront of the cache by specifying the `admissioner` argument.

```py
import libcachesim as lcs

# Initialize admissioner
admissioner = lcs.BloomFilterAdmissioner()

# Step 2: Initialize cache
cache = lcs.S3FIFO(
cache_size=1024 * 1024,
# Cache specific parameters
small_size_ratio=0.2,
ghost_size_ratio=0.8,
move_to_main_threshold=2,
# Optionally provide admissioner
admissioner=admissioner,
)
```

Then we can run cache simulations using real world workloads leveraging trace readers (see [Trace Reader](reader.md) for more on using `TraceReader`):

```py
# Process entire trace efficiently (C++ backend)
req_miss_ratio, byte_miss_ratio = cache.process_trace(reader)
print(f"Request miss ratio: {req_miss_ratio:.4f}, Byte miss ratio: {byte_miss_ratio:.4f}")
```

## Caches
The following cache classes all inherit from `CacheBase` and share a common interface, sharing the following arguments in all cache classes unless otherwise specified:

- `cache_size: int`
- `default_ttl: int` (optional)
- `hashpower: int` (optional)
- `consider_obj_metadata: bool` (optional)
- `admissioner: AdmissionerBase` (optional)

### LHD
**Lest Hit Density** evicts objects based on each objects expected hits-per-space-consumed (hit density).

- *No additional parameters beyond the common arguments*

### LRU
**Least Recently Used** evicts the object that has not been accessed for the longest time.

- *No additional parameters beyond the common arguments*

### FIFO
**First-In, First-Out** evicts objects in order regardless of frequency or recency.

- *No additional parameters beyond the common arguments*

### LFU
**Least Frequently Used** evicts the object with the lowest access frequency.

- *No additional parameters beyond the common arguments*

### Arc
**Adaptive Replacement Cache** a hybrid algorithm which balances recency and frequency.

- *No additional parameters beyond the common arguments*

### Clock
**Clock** is an low-complexity approximation of `LRU`.

- `int_freq: int` - Initial frequency counter value which is used for new objects (default: `0`)
- `n_bit_counter: int` - Number of bits used for the frequency counter (default: `1`)

### Random
**Random** evicts objects at random.

- *No additional parameters beyond the common arguments*

### S3FIFO
[TBD]

### Sieve
[TBD]

### LIRS
[TBD]

### TwoQ
[TBD]

### SLRU
[TBD]

### WTinyLFU
[TBD]

### LeCaR
[TBD]

### LFUDA
[TBD]

### ClockPro
[TBD]

### Cacheus
[TBD]

### Belady
[TBD]

### BeladySize
[TBD]

### LRUProb
[TBD]

### FlashProb
[TBD]

### GDSF
[TBD]

### Hyperbolic
[TBD]

### ThreeLCache
[TBD]

### GLCache
[TBD]

### LRB
[TBD]

## Admission Policies

### BloomFilterAdmissioner
Uses a Bloom filter to decide admissions based on how many times an object has been seen.

- *No parameters*

### ProbAdmissioner
Admits objects with a fixed probability.

- `prob: float` (optional) - Probability of admitting an object (default: `0.5`)

### SizeAdmissioner
Admits objects only if they are below a specified size threshold.

- `size_threshold: int` (optional) - Maximum allowed object size (in bytes) for admission (default: `9_223_372_036_854_775_807`, or `INT64_MAX`)

### SizeProbabilisticAdmissioner
Admits objects with a probability that decreases with object size, favoring smaller objects over large.

- `exponent: float` (optional) - Exponent controlling how aggressively larger objects are filtered out (default: `1e-6`)

### AdaptSizeAdmissioner
Implements **AdaptSize**, a feedback-driven policy that periodically adjusts its size threshold.

- `max_iteration: int` (optional) - Maximum number of iterators for parameter tuning (default: `15`)
- `reconf_interval: int` (optional) - Interval (with respect to request count) at which the threshold is re-evaluated (default: `30_000`)
31 changes: 31 additions & 0 deletions examples/admission/bloomfilter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from libcachesim import BloomFilterAdmissioner, SyntheticReader, LRU

BloomFilter = BloomFilterAdmissioner()
lru_without_admission = LRU(
cache_size=1024,
# admissioner=BloomFilter
)
lru_with_admission = LRU(
cache_size=1024,
admissioner=BloomFilter
)

reader = SyntheticReader(
num_of_req=100_000,
num_objects=10_000,
obj_size=100,
alpha=0.8,
dist="zipf",
)

without_admission_hits = 0
with_admission_hits = 0

for req in reader:
if lru_without_admission.get(req):
without_admission_hits += 1
if lru_with_admission.get(req):
with_admission_hits += 1

print(f'Obtained {without_admission_hits} without using cache admission')
print(f'Obtained {with_admission_hits} using cache admission')
65 changes: 65 additions & 0 deletions examples/admission/plugin_admissioner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from libcachesim import PluginAdmissioner, SyntheticReader, LRU
import random

'''
A toy example where we admit ten percent of all requests
at random. The admit rate is tracked and printed in the
free hook to serve as a final sanity check.
'''


class AdmissionerStats:
admitted_requests: int = 0
total_requests: int = 0


def init_hook():
return AdmissionerStats()


def admit_hook(data, request):
admit = random.randint(1, 10) == 5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using random.randint(1, 10) == 5 to achieve a 10% probability is functionally correct but can be a bit obscure. A more idiomatic and clearer way to express this probability is by using random.random() < 0.1.

Suggested change
admit = random.randint(1, 10) == 5
admit = random.random() < 0.1

if admit:
data.admitted_requests += 1
data.total_requests += 1
return admit


def clone_hook():
pass


def update_hook(data, request, cs):
pass


def free_hook(data):
print(f'Admit rate: {100 * data.admitted_requests / data.total_requests}%')


custom_admissioner = PluginAdmissioner(
"AdmitTenPercent",
init_hook,
admit_hook,
clone_hook,
update_hook,
free_hook,
)
lru_cache = LRU(
cache_size=1024,
admissioner=custom_admissioner
)

reader = SyntheticReader(
num_of_req=100_000,
num_objects=10_000,
obj_size=100,
alpha=0.8,
dist="zipf",
)

for req in reader:
lru_cache.get(req)

# Invokes free_hook, percentage should be ~10%
del lru_cache
19 changes: 19 additions & 0 deletions libcachesim/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@
PluginCache,
)

from .admissioner import (
BloomFilterAdmissioner,
ProbAdmissioner,
SizeAdmissioner,
SizeProbabilisticAdmissioner,
AdaptSizeAdmissioner,
PluginAdmissioner,
AdmissionerBase,
)

from .trace_reader import TraceReader
from .trace_analyzer import TraceAnalyzer
from .synthetic_reader import SyntheticReader, create_zipf_requests, create_uniform_requests
Expand Down Expand Up @@ -110,6 +120,15 @@
"LRB",
# Plugin cache
"PluginCache",
# Admission algorithms
"BloomFilterAdmissioner",
"ProbAdmissioner",
"SizeAdmissioner",
"SizeProbabilisticAdmissioner",
"AdaptSizeAdmissioner",
"PluginAdmissioner",
# Admissioner base class
"AdmissionerBase",
# Readers and analyzers
"TraceReader",
"TraceAnalyzer",
Expand Down
Loading
Loading