diff --git a/.gitignore b/.gitignore index 0816e08..0388f44 100644 --- a/.gitignore +++ b/.gitignore @@ -153,6 +153,7 @@ venv.bak/ # mkdocs documentation /site +docs/site/ # mypy .mypy_cache/ diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e7ee002..8c306aa 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -9,9 +9,9 @@ docs_dir: src nav: - Home: - libCacheSim Python: index.md - - Getting Started: - - getting_started/quickstart.md + - Getting Started: - getting_started/installation.md + - getting_started/quickstart.md - Examples: - examples/reader.md - examples/simulation.md @@ -85,11 +85,14 @@ plugins: build: true nav_translations: Home: 首页 - Getting Started: 快速开始 + Getting Started: 入门指南 User Guide: 用户指南 Developer Guide: 开发者指南 API Reference: API参考 Examples: 使用示例 + FAQ: 常见问题 + General: 总览 + API Documentation: API 文档 markdown_extensions: - attr_list diff --git a/docs/src/en/api.md b/docs/src/en/api.md index 8c3fc1b..6c5e97a 100644 --- a/docs/src/en/api.md +++ b/docs/src/en/api.md @@ -1,3 +1,374 @@ # API Reference -[TBD] \ No newline at end of file +This page documents everything exported from the `libcachesim` package. For task-oriented +guides, see [Cache Simulation](examples/simulation.md), [Trace Reader](examples/reader.md), +[Trace Analysis](examples/analysis.md), and [Plugin System](examples/plugins.md). + +```python +import libcachesim as lcs +``` + +## Requests and objects + +### `Request` + +A single access in a trace. Readers fill and return `Request` objects; caches consume them. + +```python +Request( + obj_size: int = 1, + op: ReqOp = ReqOp.OP_NOP, + valid: bool = True, + obj_id: int = 0, + clock_time: int = 0, + hv: int = 0, + next_access_vtime: int = -2, + ttl: int = 0, +) +``` + +| Attribute | Type | Description | +|---|---|---| +| `obj_id` | `int` | Object identifier | +| `obj_size` | `int` | Object size in bytes | +| `clock_time` | `int` | Wall-clock timestamp of the request | +| `next_access_vtime` | `int` | Logical time of this object's next access; only present in oracle traces, and required by `Belady` / `BeladySize` | +| `op` | `ReqOp` | Operation type | +| `ttl` | `int` | Time-to-live in seconds | +| `hv` | `int` | Hash value | +| `valid` | `bool` | `False` marks the end of a trace; iteration stops on it | + +### `CacheObject` + +Returned by `Cache.find`, `insert`, `evict`, and `to_evict`. Exposes read-only `obj_id` and +`obj_size`. + +## Enumerations + +### `ReqOp` + +``` +OP_NOP OP_GET OP_GETS OP_SET OP_ADD +OP_CAS OP_REPLACE OP_APPEND OP_PREPEND OP_DELETE +OP_INCR OP_DECR OP_READ OP_WRITE OP_UPDATE +OP_INVALID +``` + +### `TraceType` + +``` +CSV_TRACE BIN_TRACE PLAIN_TXT_TRACE +ORACLE_GENERAL_TRACE LCS_TRACE VSCSI_TRACE +TWR_TRACE TWRNS_TRACE ORACLE_SIM_TWR_TRACE +ORACLE_SYS_TWR_TRACE ORACLE_SIM_TWRNS_TRACE ORACLE_SYS_TWRNS_TRACE +VALPIN_TRACE UNKNOWN_TRACE +``` + +`UNKNOWN_TRACE` is the default and asks `TraceReader` to infer the format from the file name. + +### `SamplerType` + +``` +SPATIAL_SAMPLER TEMPORAL_SAMPLER SHARDS_SAMPLER INVALID_SAMPLER +``` + +## Configuration objects + +### `ReaderInitParam` + +Controls how a trace file is parsed. Passed to `TraceReader` as `reader_init_params`. + +```python +ReaderInitParam( + binary_fmt_str: str = "", + ignore_obj_size: bool = False, + ignore_size_zero_req: bool = True, + obj_id_is_num: bool = True, + obj_id_is_num_set: bool = False, + cap_at_n_req: int = -1, + block_size: int = -1, + has_header: bool = False, + has_header_set: bool = False, + delimiter: str = ",", + trace_start_offset: int = 0, + sampler: Optional[Sampler] = None, +) +``` + +| Attribute | Description | +|---|---| +| `ignore_obj_size` | Treat every object as size 1, so the byte miss ratio equals the request miss ratio | +| `ignore_size_zero_req` | Skip requests whose object size is zero | +| `obj_id_is_num` | Parse object IDs as integers rather than strings | +| `cap_at_n_req` | Stop after this many requests; `-1` for no cap | +| `block_size` | Block size for block-level traces; `-1` to disable | +| `has_header` | Whether a CSV trace has a header row | +| `delimiter` | Field separator for CSV traces | +| `trace_start_offset` | Byte offset at which to start reading | +| `binary_fmt_str` | Struct format string for `BIN_TRACE` | +| `sampler` | Optional `Sampler` applied while reading | + +CSV field positions are set as attributes after construction, and are **1-indexed**: +`time_field`, `obj_id_field`, `obj_size_field`, `op_field`, `ttl_field`, `cnt_field`, +`tenant_field`, `next_access_vtime_field`, `n_feature_fields`. + +### `CommonCacheParams` + +The parameter bundle every cache is built from. Constructed for you by the cache classes; you +only encounter it directly as the argument to a `PluginCache` init hook. + +| Attribute | Type | +|---|---| +| `cache_size` | `int` | +| `default_ttl` | `int` | +| `hashpower` | `int` | +| `consider_obj_metadata` | `bool` | + +### `AnalysisOption` and `AnalysisParam` + +Configuration for `TraceAnalyzer`. Fields and defaults are documented in +[Trace Analysis](examples/analysis.md#selecting-analyses). + +## Caches + +### `CacheBase` + +Base class of every cache. See +[Working with individual requests](examples/simulation.md#working-with-individual-requests) for +the full method table. + +```python +process_trace(reader: ReaderProtocol, start_req: int = 0, max_req: int = -1) -> tuple[float, float] +``` + +Replays the trace and returns `(request_miss_ratio, byte_miss_ratio)`. With a C-backed reader +the whole loop runs in C++ with the GIL released; with a Python reader it falls back to a +Python loop. + +Other methods: `get`, `find`, `can_insert`, `insert`, `need_eviction`, `evict`, `remove`, +`to_evict`, `get_occupied_byte`, `get_n_obj`, `set_cache_size`, `print_cache`. Read-only +properties: `cache_size`, `cache_name`. + +### Cache algorithms + +All take the common arguments `cache_size`, `default_ttl=25920000`, `hashpower=24`, +`consider_obj_metadata=False`, `admissioner=None`, `reader=None`, plus the extras below. See +[Cache Simulation](examples/simulation.md#caches) for what each algorithm does. + +| Class | Algorithm-specific parameters | +|---|---| +| `LHD` | — | +| `LRU` | — | +| `FIFO` | — | +| `LFU` | — | +| `ARC` | — | +| `Clock` | `init_freq=0`, `n_bit_counter=1` | +| `Random` | — | +| `S3FIFO` | `small_size_ratio=0.1`, `ghost_size_ratio=0.9`, `move_to_main_threshold=2` | +| `Sieve` | — | +| `LIRS` | — | +| `TwoQ` | `a_in_size_ratio=0.25`, `a_out_size_ratio=0.5` | +| `SLRU` | — | +| `WTinyLFU` | `main_cache="SLRU"`, `window_size=0.01` | +| `LeCaR` | `update_weight=True`, `lru_weight=0.5` | +| `LFUDA` | — | +| `ClockPro` | `init_ref=0`, `init_ratio_cold=0.5` | +| `Cacheus` | — | +| `Belady` | — | +| `BeladySize` | `n_samples=128` | +| `LRUProb` | `prob=0.5` | +| `FlashProb` | `ram_size_ratio=0.05`, `disk_admit_prob=0.2`, `ram_cache="LRU"`, `disk_cache="FIFO"` | +| `Size` | — | +| `GDSF` | — | +| `Hyperbolic` | — | +| `ThreeLCache` | `objective="byte-miss-ratio"` — requires `-DENABLE_3L_CACHE=ON` | +| `GLCache` | `segment_size=100`, `n_merge=2`, `type="learned"`, `rank_intvl=0.02`, `merge_consecutive_segs=True`, `train_source_y="online"`, `retrain_intvl=86400` — requires `-DENABLE_GLCACHE=ON` | +| `LRB` | `objective="byte-miss-ratio"` — requires `-DENABLE_LRB=ON` | + +`cache_size` may be an `int` (bytes) or a `float` in `(0, 1]` (a fraction of the working set, +which requires `reader`). See +[Cache size as a ratio](examples/simulation.md#cache-size-as-a-ratio). + +### `PluginCache` + +```python +PluginCache( + cache_size: int | float, + cache_init_hook: Callable, + cache_hit_hook: Callable, + cache_miss_hook: Callable, + cache_eviction_hook: Callable, + cache_remove_hook: Callable, + cache_free_hook: Optional[Callable] = None, + cache_name: str = "PythonHookCache", + default_ttl: int = 25920000, + hashpower: int = 24, + consider_obj_metadata: bool = False, + admissioner: Optional[AdmissionerBase] = None, + reader: Optional[ReaderProtocol] = None, +) +``` + +Hook signatures are documented in [Plugin System](examples/plugins.md#plugincache). +`set_hooks(...)` replaces the hooks on an existing instance. + +## Admission policies + +Every admissioner derives from `AdmissionerBase`, which exposes `admit(req)`, `update(req, +cache_size)`, `clone()`, and `free()`. Pass an instance as the `admissioner` argument of any +cache. + +| Class | Parameters | +|---|---| +| `BloomFilterAdmissioner` | — | +| `ProbAdmissioner` | `prob: float = None` | +| `SizeAdmissioner` | `size_threshold: int = None` | +| `SizeProbabilisticAdmissioner` | `exponent: float = None` | +| `AdaptSizeAdmissioner` | `max_iteration: int = None`, `reconf_interval: int = None` | +| `PluginAdmissioner` | `admissioner_name` plus five hooks | + +Leaving a parameter as `None` uses the C library's own default; those defaults are listed in +[Admission Policies](examples/simulation.md#admission-policies). + +```python +PluginAdmissioner( + admissioner_name: str, + admissioner_init_hook: Callable, + admissioner_admit_hook: Callable, + admissioner_clone_hook: Callable, + admissioner_update_hook: Callable, + admissioner_free_hook: Callable, +) +``` + +## Readers + +### `ReaderProtocol` + +A `runtime_checkable` protocol describing what any reader must provide, so custom readers can be +used wherever `TraceReader` is accepted: + +```python +get_num_of_req() -> int +read_one_req() -> Request +skip_n_req(n: int) -> int +reset() -> None +close() -> None +clone() -> ReaderProtocol +get_working_set_size() -> tuple[int, int] +__iter__() / __next__() / __len__() +``` + +### `TraceReader` + +```python +TraceReader( + trace: str | Reader, + trace_type: TraceType = TraceType.UNKNOWN_TRACE, + reader_init_params: Optional[ReaderInitParam] = None, +) +``` + +`trace` is a local path or an `s3://bucket/key` URI; S3 objects are downloaded and cached +locally on first use. See [Trace Reader](examples/reader.md). + +- Iteration and `len()` work directly on the reader. +- Indexing and slicing are supported: `reader[0]`, `reader[:100]`, `reader[-100:]`. A slice + returns an iterator over a cloned reader, leaving the original position untouched. +- Navigation: `read_one_req()`, `read_first_req(req)`, `read_last_req(req)`, `skip_n_req(n)`, + `go_back_one_req()`, `read_one_req_above()`, `set_read_pos(pos)`, `reset()`, `close()`, + `clone()`. +- `get_working_set_size()` returns `(n_object, n_byte)`. +- Read-only properties include `n_read_req`, `n_total_req`, `n_req_left`, `trace_path`, + `file_size`, `trace_type`, `trace_format`, `is_zstd_file`, `cloned`, `sampler`, + `read_direction`, `lcs_ver`, `init_params`. `ignore_obj_size`, `ignore_size_zero_req`, and + `block_size` are writable. + +`read_one_req()` raises `RuntimeError` at end of trace, whereas iteration stops cleanly. + +### `SyntheticReader` + +Generates requests in memory — no trace file needed. + +```python +SyntheticReader( + num_of_req: int, + obj_size: int = 4000, + time_span: int = 604800, + start_obj_id: int = 0, + seed: Optional[int] = None, + alpha: float = 1.0, + dist: str = "zipf", + num_objects: Optional[int] = None, +) +``` + +`dist` is `"zipf"` or `"uniform"`; `alpha` only applies to Zipf. `num_objects` defaults to +`num_of_req`. Invalid arguments raise `ValueError`. + +!!! note + `SyntheticReader` is a pure-Python reader (`c_reader = False`). `process_trace` still works, + but falls back to a Python loop, and `TraceAnalyzer` rejects it outright. + +### Trace generators + +```python +create_zipf_requests(num_objects, num_requests, alpha=1.0, obj_size=4000, + time_span=604800, start_obj_id=0, seed=None) -> Iterator[Request] + +create_uniform_requests(num_objects, num_requests, obj_size=4000, + time_span=604800, start_obj_id=0, seed=None) -> Iterator[Request] +``` + +Both return an **iterator**, not a list; wrap in `list(...)` if you need to replay them twice. + +## `TraceAnalyzer` + +```python +TraceAnalyzer( + reader: ReaderProtocol, + output_path: str, + analysis_param: Optional[AnalysisParam] = None, + analysis_option: Optional[AnalysisOption] = None, +) +``` + +Methods: `run()`, `cleanup()`. Requires a C-backed reader; anything else raises +`ReaderException`. See [Trace Analysis](examples/analysis.md). + +## `Util` + +Static helpers for trace conversion and simulation. + +```python +Util.convert_to_oracleGeneral(reader, ofilepath, output_txt=False, remove_size_change=False) +Util.convert_to_lcs(reader, ofilepath, output_txt=False, remove_size_change=False, lcs_ver=1) +Util.process_trace(cache, reader, start_req=0, max_req=-1) -> tuple[float, float] +``` + +- `convert_to_oracleGeneral` rewrites a trace into the oracleGeneral format, computing the + next-access field needed by `Belady`. +- `convert_to_lcs` writes the LCS format; `lcs_ver` selects the version (1–8). +- `Util.process_trace` is equivalent to `cache.process_trace(...)` but requires a C-backed + reader, raising `ValueError` otherwise. + +## Metadata + +`libcachesim.__version__` is the installed package version; `libcachesim.__doc__` is the +extension module's docstring. + +## Exceptions + +The bindings use standard Python exceptions: + +| Exception | Raised when | +|---|---| +| `ValueError` | Invalid arguments — a malformed S3 URI, a `cache_size` float outside `(0, 1]` or without a `reader`, an unsupported `dist`, or a non-C reader passed to `Util.process_trace` | +| `TypeError` | `reader_init_params` is not a `ReaderInitParam`; a reader is indexed with something other than an `int` or `slice` | +| `IndexError` | Reader index out of range, or end of trace reached while seeking | +| `RuntimeError` | `read_one_req()` called past the end of a trace | +| `ImportError` | Constructing `LRB`, `ThreeLCache`, or `GLCache` in a build compiled without the corresponding flag | +| `ReaderException` | A non-C reader was passed to `TraceAnalyzer` | + +`ReaderException` is not re-exported at package level; import it from +`libcachesim.trace_analyzer` if you need to catch it by type. diff --git a/docs/src/en/developer.md b/docs/src/en/developer.md index 8fcc019..e494d07 100644 --- a/docs/src/en/developer.md +++ b/docs/src/en/developer.md @@ -1,3 +1,216 @@ # Developer Guide -[TBD] \ No newline at end of file +This page is for people working *on* libCacheSim Python, rather than with it. If you only want +to use the library, start with [Installation](getting_started/installation.md). + +## Repository layout + +``` +libCacheSim-python/ +├── libcachesim/ # The Python package +│ ├── __init__.py # Public API surface (__all__) +│ ├── __init__.pyi # Type stubs for the compiled extension +│ ├── cache.py # Cache wrapper classes +│ ├── admissioner.py # Admission policy wrappers +│ ├── trace_reader.py # TraceReader, with S3 support +│ ├── synthetic_reader.py +│ ├── trace_analyzer.py +│ ├── protocols.py # ReaderProtocol +│ └── util.py # Trace conversion helpers +├── src/ # pybind11 bindings (C++) +│ ├── export_cache.cpp +│ ├── export_reader.cpp +│ ├── export_analyzer.cpp +│ ├── export_admissioner.cpp +│ └── libCacheSim/ # git submodule: the C library +├── tests/ +├── examples/ +├── scripts/ +└── docs/ +``` + +The general shape is: the C library does the work, `src/*.cpp` exposes it through pybind11, and +`libcachesim/*.py` wraps that in an ergonomic Python API. + +## Getting set up + +The C library is a git submodule, so the checkout must be recursive: + +```bash +git clone https://github.com/cacheMon/libCacheSim-python.git +cd libCacheSim-python +git submodule update --init --recursive +pip install -e ".[dev]" +``` + +The `dev` extra brings in `pytest`, `ruff`, `mypy`, and `pre-commit`. + +`scripts/install.sh` does all of the above and then runs the tests. Pass `--all` to enable the +optional learned algorithms: + +```bash +bash scripts/install.sh --all +``` + +### Build system + +Builds go through [scikit-build-core](https://scikit-build-core.readthedocs.io/), configured in +`pyproject.toml`. It configures and builds the bundled C library first, then compiles the +bindings with Ninja. Optional features are toggled with `CMAKE_ARGS`: + +```bash +CMAKE_ARGS="-DENABLE_LRB=ON -DENABLE_3L_CACHE=ON -DENABLE_GLCACHE=ON" pip install -e . +``` + +Note that `pyproject.toml` sets `build-dir = "build"`, so incremental rebuilds reuse previous +output. If a rebuild picks up stale artefacts, remove `build/` and `src/libCacheSim/build/`. + +## Testing + +```bash +python -m pytest tests/ +``` + +!!! important + `pyproject.toml` sets `addopts = [..., "-m", "not optional"]`, so a plain `pytest` run + **skips** the tests for the optional learned algorithms. To run those, you need a build with + the corresponding CMake flags and an explicit marker selection: + + ```bash + python -m pytest tests/ -m optional + ``` + +The suite also sets `filterwarnings = ["error", ...]`, so a new warning will fail the build. + +Several tests download traces from the public S3 bucket, so they need network access on first +run; subsequent runs use the local cache. + +## Code style + +There is no `pre-commit` configuration checked in, so run the tools directly: + +```bash +# Lint and auto-fix Python +ruff check libcachesim/ tests/ examples/ +ruff format libcachesim/ + +# Type-check +mypy libcachesim/ + +# Format C++ (uses the checked-in .clang-format) +clang-format -i src/*.cpp src/*.h +``` + +`ruff` is configured in `pyproject.toml` with a 120-character line length and the `E`, `F`, +`UP`, `B`, `SIM`, and `G` rule sets. + +## Adding a cache algorithm + +Adding an algorithm that already exists in the C library takes five steps: + +1. **Bind it.** In `src/export_cache.cpp`, expose the algorithm's `*_init` function following + the pattern used by the existing ones. +2. **Wrap it.** In `libcachesim/cache.py`, add a class deriving from `CacheBase`. Build the + common parameters with the existing `_create_common_params(...)` helper — this is also what + gives every cache the fractional `cache_size` behaviour for free — and pass any + algorithm-specific settings as a `cache_specific_params` string: + + ```python + class MyAlgo(CacheBase): + """My algorithm + + Special parameters: + my_param: what it controls (default: 0.5) + """ + + def __init__( + self, + cache_size: int | float, + default_ttl: int = 86400 * 300, + hashpower: int = 24, + consider_obj_metadata: bool = False, + my_param: float = 0.5, + admissioner: AdmissionerBase = None, + reader: ReaderProtocol = None, + ): + cache_specific_params = f"my-param={my_param}" + super().__init__( + _cache=MyAlgo_init( + _create_common_params( + cache_size, default_ttl, hashpower, consider_obj_metadata, reader + ), + cache_specific_params, + ), + admissioner=admissioner, + ) + ``` + + Note that the C library's parameter names are hyphenated (`my-param`) even though the Python + keyword is underscored. +3. **Export it.** Add the class to both the import block and `__all__` in + `libcachesim/__init__.py`, and add a stub to `libcachesim/__init__.pyi`. +4. **Test it.** Add a case to `tests/test_cache.py`. If the algorithm depends on an optional + build flag, mark it `@pytest.mark.optional`. +5. **Document it.** Add a section to `docs/src/en/examples/simulation.md` and a row to the table + in `docs/src/en/api.md`. + +If the algorithm needs a third-party library, guard the import as `ThreeLCache` and `GLCache` +do — a `try`/`except ImportError` that re-raises with the `CMAKE_ARGS` incantation the user +needs. + +## Documentation + +The site is built with [MkDocs Material](https://squidfunk.github.io/mkdocs-material/) plus +`mkdocs-static-i18n`. Sources live in `docs/src//`, and the locale folders must mirror +each other: a page at `en/examples/reader.md` is translated by a file at +`zh/examples/reader.md`. A file at any other path is simply never rendered. Missing +translations fall back to English, so partial translation is fine. + +To build and preview locally: + +```bash +bash scripts/build_docs.sh --serve # http://127.0.0.1:8000 +``` + +Or directly: + +```bash +pip install -r docs/requirements.txt +cd docs && mkdocs build --clean --strict +``` + +Always build with `--strict` before opening a PR — that is what CI runs, and it turns broken +internal links into build failures. + +Prefer relative links between pages (`../faq.md`) over absolute URLs to the published site, so +that links keep working in local builds and under the locale fallback. + +## Continuous integration + +| Workflow | Trigger | What it does | +|---|---|---| +| `.github/workflows/build.yml` | changes under `src/`, `libcachesim/`, `tests/` | Builds and tests on Ubuntu and macOS (Intel and Apple Silicon) across Python 3.10–3.13, and separately builds the docs | +| `.github/workflows/docs.yml` | changes under `docs/` | Builds with `--strict` and deploys to GitHub Pages on `main` | +| `.github/workflows/pypi-release.yml` | published release, or manual dispatch | Builds wheels with cibuildwheel and publishes to PyPI | + +Note that `build.yml` only triggers on changes to code paths, and `docs.yml` only on `docs/`, so +a docs-only PR will not run the test suite and vice versa. + +## Releasing + +Releases are cut by publishing a GitHub release, which triggers `pypi-release.yml`. + +Wheels are built by [cibuildwheel](https://cibuildwheel.pypa.io/) using the configuration in +`pyproject.toml`, which builds manylinux and macOS wheels for every supported CPython version +with **all three** optional algorithms enabled, and verifies each wheel by importing it and +running both the default and `optional` test selections. + +`scripts/sync_version.py` keeps the version in `pyproject.toml` in step with +`src/libCacheSim/version.txt` from the submodule. + +## Contributing + +Bug reports and feature requests belong in +[GitHub issues](https://github.com/cacheMon/libCacheSim-python/issues/new/choose). For changes +to the simulation core itself rather than the bindings, the right repository is +[1a1a11a/libCacheSim](https://github.com/1a1a11a/libCacheSim). diff --git a/docs/src/en/examples/analysis.md b/docs/src/en/examples/analysis.md index ccdcb6f..18563f9 100644 --- a/docs/src/en/examples/analysis.md +++ b/docs/src/en/examples/analysis.md @@ -1,3 +1,154 @@ # Trace Analysis -[TBD] \ No newline at end of file +Beyond simulating caches, libCacheSim can characterise a workload directly: request rate, object +size distribution, reuse distance, popularity, and more. This is done with `TraceAnalyzer`, a +thin wrapper over the analyzer in the underlying libCacheSim lib. + +## Basic usage + +`TraceAnalyzer` takes a reader, an output path prefix, and two optional configuration objects: + +```python +import libcachesim as lcs + +# Step 1: Open a trace (see the Trace Reader page for details) +URI = "s3://cache-datasets/cache_dataset_oracleGeneral/2007_msr/msr_hm_0.oracleGeneral.zst" +reader = lcs.TraceReader( + trace=URI, + trace_type=lcs.TraceType.ORACLE_GENERAL_TRACE, + reader_init_params=lcs.ReaderInitParam(ignore_obj_size=False), +) + +# Step 2: Run the analysis +analyzer = lcs.TraceAnalyzer(reader, "example_analysis") +analyzer.run() +``` + +The constructor arguments are: + +- `reader: ReaderProtocol` — the trace to analyse. +- `output_path: str` — prefix for the generated result files. +- `analysis_option: AnalysisOption` (optional) — which analyses to run. Defaults to + `AnalysisOption()`. +- `analysis_param: AnalysisParam` (optional) — tuning knobs for those analyses. Defaults to + `AnalysisParam()`. + +!!! important + The analyzer runs entirely in the C++ backend, so it only accepts a C-backed reader — in + practice, [`TraceReader`](reader.md). Passing a `SyntheticReader` raises + `ReaderException: Only C/C++ reader is supported`. To analyse a synthetic workload, write it + out first with `Util.convert_to_oracleGeneral` and reopen it with `TraceReader`. + +## Selecting analyses + +Each field of `AnalysisOption` toggles one analysis. Five are on by default: + +| Option | Default | What it measures | +|---|---|---| +| `req_rate` | `True` | Request and object rate over time | +| `access_pattern` | `True` | Access pattern of individual objects over time | +| `size` | `True` | Object size distribution, by request and by object | +| `reuse` | `True` | Reuse time / reuse distance distribution | +| `popularity` | `True` | Object popularity distribution (Zipf fit) | +| `ttl` | `False` | TTL distribution (only meaningful for traces with TTLs) | +| `popularity_decay` | `False` | How object popularity decays with age | +| `lifetime` | `False` | Object lifetime distribution | +| `create_future_reuse_ccdf` | `False` | Experimental — CCDF of future reuse | +| `prob_at_age` | `False` | Experimental — access probability as a function of age | +| `size_change` | `False` | How object sizes change across accesses | + +Analyses are independent, so disabling the ones you do not need makes the run considerably +faster on large traces: + +```python +analysis_option = lcs.AnalysisOption( + req_rate=True, # Keep basic request rate analysis + access_pattern=False, + size=True, # Keep size analysis + reuse=False, + popularity=False, + ttl=False, + popularity_decay=False, + lifetime=False, + create_future_reuse_ccdf=False, + prob_at_age=False, + size_change=False, +) + +analyzer = lcs.TraceAnalyzer(reader, "example_analysis", analysis_option=analysis_option) +analyzer.run() +``` + +## Tuning the analyses + +`AnalysisParam` controls how the enabled analyses behave: + +| Parameter | Default | Meaning | +|---|---|---| +| `access_pattern_sample_ratio_inv` | `10` | Inverse sampling ratio for the access-pattern analysis — a value of `n` keeps roughly `1/n` of the data | +| `track_n_popular` | `10` | How many of the most popular objects to report request counts for | +| `track_n_hit` | `5` | How many "X-hit wonder" buckets to track, i.e. the number of objects accessed exactly once, twice, ... `track_n_hit` times | +| `time_window` | `60` | Width, in seconds, of the buckets used for time-series output | +| `warmup_time` | `0` | Seconds of trace to skip before collecting statistics | + +```python +analysis_param = lcs.AnalysisParam( + track_n_popular=4, + track_n_hit=4, + time_window=300, +) + +analyzer = lcs.TraceAnalyzer( + reader, "example_analysis", + analysis_option=analysis_option, + analysis_param=analysis_param, +) +analyzer.run() +``` + +!!! warning + Two constraints are easy to trip over: + + - `warmup_time` must be an exact multiple of `time_window`; the analyzer errors out + otherwise, because the popularity-decay computation depends on that relationship. + - The popularity and reuse analyses need a reasonably large working set to produce + meaningful output. On a tiny trace — a handful of distinct objects — set + `track_n_popular` and `track_n_hit` no higher than the number of objects, or disable + `popularity` and `reuse` altogether. + +## Results + +`run()` writes plain-text result files, all sharing the `output_path` prefix. Each enabled +analysis contributes at least one file — the size analysis writes `example_analysis.size`, and +some analyses additionally emit time-windowed variants such as +`example_analysis.sizeWindow_w60_req`. + +```python +with open("example_analysis.size") as f: + print(f.read()) +``` + +A summary of the run — trace path, request and object counts, compulsory miss ratio, mean object +size, mean frequency, time span, and the X-hit-wonder and popularity histograms — is written to +a file named `stat` in the **current working directory**. Note that this path is fixed rather +than derived from `output_path`, and the analyzer *appends* to it, so results from successive +runs accumulate in the same file. + +When you are finished, `cleanup()` releases the analyzer's internal state: + +```python +analyzer.cleanup() +``` + +## Working set size + +For the single most common statistic — how much data the trace touches — you do not need the +analyzer at all. `TraceReader` exposes it directly: + +```python +n_obj, n_byte = reader.get_working_set_size() +print(f"{n_obj} unique objects, {n_byte} bytes") +``` + +This is also what a fractional `cache_size` is measured against; see +[Cache Simulation](simulation.md#cache-size-as-a-ratio). diff --git a/docs/src/en/examples/reader.md b/docs/src/en/examples/reader.md index fd8e84d..ae2d348 100644 --- a/docs/src/en/examples/reader.md +++ b/docs/src/en/examples/reader.md @@ -7,6 +7,7 @@ We support a unified trace reader to open trace files in different format and re `TraceReader` class is the core of this functionality. When we create an instance of `TraceReader`, we open a trace file for read requests. `TraceReader` accepts three arguments: + - `trace: str | TraceReader`: A trace path or other trace instance. The trace path can be a file path on your local machine (e.g., ~/data/trace.oracleGeneral.zst) or an S3 URI (e.g., s3://cache-datasets/cache_dataset_oracleGeneral/2007_msr/msr_hm_0.oracleGeneral.zst). - `trace_type: TraceType` (optional): If not given, it will be infered according to the file name. - `reader_init_params: ReaderInitParam` (optional): If not given, will use default params for reader initialization. diff --git a/docs/src/en/examples/simulation.md b/docs/src/en/examples/simulation.md index 1378339..dc36112 100644 --- a/docs/src/en/examples/simulation.md +++ b/docs/src/en/examples/simulation.md @@ -2,7 +2,7 @@ ## 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. +The cache classes are the core of cache simulation. When an instance of a cache is created (e.g., `LRU`, `S3FIFO`), we can configure the cache size and any cache-specific parameters such as promotion thresholds. ```py import libcachesim as lcs @@ -45,17 +45,81 @@ 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}") ``` +`process_trace` accepts two further arguments for restricting the replay to part of the trace: + +```py +# Skip the first 10,000 requests, then process the next 1,000 +req_miss_ratio, byte_miss_ratio = cache.process_trace(reader, start_req=10_000, max_req=1_000) +``` + +- `start_req: int` - Index of the first request to process (default: `0`) +- `max_req: int` - Maximum number of requests to process; `-1` means the whole trace (default: `-1`) + +!!! note + `process_trace` rewinds the reader before replaying, so you do not need to call `reset()` + yourself. The *cache*, however, keeps its state across calls — create a fresh cache for each + configuration you measure, rather than reusing one already warmed by a previous run. + +## Cache size as a ratio + +`cache_size` accepts either an absolute byte count (`int`) or a fraction of the trace's working +set (`float`). A float must be in `(0, 1]` and requires the `reader` argument, which is used to +call `reader.get_working_set_size()`: + +```py +# 10% of the trace's total working set size in bytes +cache = lcs.S3FIFO( + cache_size=0.1, + reader=reader, # Required when cache_size is a float +) +``` + +Passing a float without a `reader`, or a float outside `(0, 1]`, raises `ValueError`. Note that +`1024` and `1024.0` therefore mean very different things - the former is 1 KiB, the latter is +rejected. + +## Working with individual requests + +`process_trace` runs the whole replay in the C++ backend and is by far the fastest option. When +you need to observe or intervene per request, `CacheBase` exposes the underlying operations: + +```py +for req in reader: + hit = cache.get(req) # Look up, and insert on miss (evicting as needed) + if not hit: + print(f"miss on {req.obj_id}, cache now holds {cache.get_n_obj()} objects") +``` + +| Method | Description | +|---|---| +| `get(req)` | Full request path: look up `req`, and on a miss insert it, evicting if necessary. Returns `True` on a hit. | +| `find(req, update_cache=True)` | Look up an object without inserting on miss. Set `update_cache=False` for a side-effect-free probe. | +| `can_insert(req)` | Whether the object would be admitted. | +| `insert(req)` | Insert an object without checking for space. | +| `need_eviction(req)` | Whether inserting `req` would require an eviction. | +| `to_evict(req)` | The object that would be evicted next, without evicting it. | +| `evict(req)` | Evict one object according to the policy. | +| `remove(obj_id)` | Remove a specific object. Returns `False` if it was not cached. | +| `get_occupied_byte()` | Bytes currently occupied. | +| `get_n_obj()` | Number of objects currently cached. | +| `set_cache_size(new_size)` | Resize the cache in place. | +| `print_cache()` | A string describing the current cache state, useful when debugging. | + +The `cache_size` and `cache_name` properties are read-only. + ## 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) +- `cache_size: int | float` - Cache size in bytes, or a fraction of the working set (see [above](#cache-size-as-a-ratio)) +- `default_ttl: int` (optional) - Default TTL in seconds (default: `25920000`, i.e. 300 days) +- `hashpower: int` (optional) - Log2 of the initial hash table size (default: `24`) +- `consider_obj_metadata: bool` (optional) - Whether per-object cache metadata counts against the cache size (default: `False`) +- `admissioner: AdmissionerBase` (optional) - Admission policy placed in front of the cache (default: `None`) +- `reader: ReaderProtocol` (optional) - Only needed when `cache_size` is a fraction (default: `None`) ### LHD -**Lest Hit Density** evicts objects based on each objects expected hits-per-space-consumed (hit density). +**Least Hit Density** evicts objects based on each objects expected hits-per-space-consumed (hit density). - *No additional parameters beyond the common arguments* @@ -74,7 +138,7 @@ The following cache classes all inherit from `CacheBase` and share a common inte - *No additional parameters beyond the common arguments* -### Arc +### ARC **Adaptive Replacement Cache** a hybrid algorithm which balances recency and frequency. - *No additional parameters beyond the common arguments* @@ -82,7 +146,7 @@ The following cache classes all inherit from `CacheBase` and share a common inte ### Clock **Clock** is an low-complexity approximation of `LRU`. -- `int_freq: int` - Initial frequency counter value which is used for new objects (default: `0`) +- `init_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 @@ -91,61 +155,140 @@ The following cache classes all inherit from `CacheBase` and share a common inte - *No additional parameters beyond the common arguments* ### S3FIFO -[TBD] +**Simple, Scalable FIFO** splits the cache into a small FIFO queue for newly admitted objects and a main FIFO queue for objects that prove popular, backed by a ghost queue of recently evicted identifiers. One-hit wonders are demoted quickly out of the small queue instead of polluting the main one. + +- `small_size_ratio: float` - Fraction of the cache given to the small queue (default: `0.1`) +- `ghost_size_ratio: float` - Size of the ghost queue as a fraction of the cache (default: `0.9`) +- `move_to_main_threshold: int` - Number of accesses in the small queue before an object is promoted to the main queue (default: `2`) ### Sieve -[TBD] +**Sieve** sweeps a hand over a FIFO queue and evicts the first object whose visited bit is unset, clearing the bits it passes. It achieves LRU-like miss ratios while keeping FIFO's simplicity, with no promotion on hit. + +- *No additional parameters beyond the common arguments* ### LIRS -[TBD] +**Low Inter-reference Recency Set** ranks objects by the recency of their second-to-last access rather than their last, which lets it distinguish genuinely hot objects from ones touched a single time during a scan. + +- *No additional parameters beyond the common arguments* ### TwoQ -[TBD] +**2Q** admits new objects to a FIFO queue (`Ain`), promoting them into an LRU main queue only if they are accessed again while their identifier is still in the ghost queue (`Aout`). + +- `a_in_size_ratio: float` - Size of the `Ain` queue as a fraction of the cache (default: `0.25`) +- `a_out_size_ratio: float` - Size of the `Aout` ghost queue as a fraction of the cache (default: `0.5`) ### SLRU -[TBD] +**Segmented LRU** partitions the cache into ordered LRU segments; an object is promoted one segment on each hit and demoted towards eviction as newer objects arrive. + +- *No additional parameters beyond the common arguments* ### WTinyLFU -[TBD] +**Window TinyLFU** places a small LRU window in front of a larger main cache, and uses a frequency sketch to decide whether an object leaving the window deserves to displace the main cache's eviction candidate. + +- `main_cache: str` - Eviction algorithm used for the main cache (default: `"SLRU"`) +- `window_size: float` - Size of the LRU window as a fraction of the main cache (default: `0.01`) ### LeCaR -[TBD] +**Learning Cache Replacement** maintains both an LRU and an LFU candidate and picks between them using weights updated by regret minimisation, so it adapts as the workload shifts between recency- and frequency-friendly. + +- `update_weight: bool` - Whether to keep learning the weights during the replay (default: `True`) +- `lru_weight: float` - Initial probability of choosing the LRU candidate; the LFU weight is `1 - lru_weight` (default: `0.5`) ### LFUDA -[TBD] +**LFU with Dynamic Aging** is `LFU` plus a global age value added to each object's priority on access, so objects that were popular long ago eventually age out instead of pinning the cache. + +- *No additional parameters beyond the common arguments* ### ClockPro -[TBD] +**CLOCK-Pro** approximates `LIRS` using CLOCK hands, tracking hot and cold pages plus a test period for recently evicted cold pages. + +- `init_ref: int` - Initial reference count given to newly admitted objects (default: `0`) +- `init_ratio_cold: float` - Initial fraction of the cache designated as cold (default: `0.5`) ### Cacheus -[TBD] +**Cacheus** builds on `LeCaR`, adding lightweight adaptation of the learning rate and scan/churn detection so that it degrades gracefully on the workloads where `LeCaR` struggles. + +- *No additional parameters beyond the common arguments* ### Belady -[TBD] +**Belady's MIN** is the optimal offline policy: it evicts the object whose next access is furthest in the future. It is not implementable online and exists as a lower bound on the achievable miss ratio. + +- *No additional parameters beyond the common arguments* + +!!! important + `Belady` and `BeladySize` read `req.next_access_vtime`, which only oracle traces carry. Use a + trace in `ORACLE_GENERAL_TRACE` format (as in the examples on this page); on an ordinary trace + the future-access field is absent and the results are meaningless. ### BeladySize -[TBD] +**Size-aware Belady** extends `Belady` to variable object sizes, choosing among a sample of candidates by both next access time and size. + +- `n_samples: int` - Number of objects sampled when picking a victim (default: `128`) ### LRUProb -[TBD] +**LRU with Probabilistic Promotion** behaves like `LRU`, except an object is only moved to the head of the queue with probability `prob`. Lower values make it behave more like `FIFO` at lower promotion cost. + +- `prob: float` - Probability of promoting an object on a hit (default: `0.5`) ### FlashProb -[TBD] +**FlashProb** models a two-tier RAM-plus-flash cache, admitting objects evicted from RAM to the flash tier only probabilistically so as to limit write amplification on the flash device. + +- `ram_size_ratio: float` - Size of the RAM tier as a fraction of the total cache (default: `0.05`) +- `disk_admit_prob: float` - Probability of admitting an object to the disk tier (default: `0.2`) +- `ram_cache: str` - Eviction algorithm used for the RAM tier (default: `"LRU"`) +- `disk_cache: str` - Eviction algorithm used for the disk tier (default: `"FIFO"`) + +### Size +**Size** evicts the largest object first, maximising the number of objects retained. Useful as a baseline on workloads with highly variable object sizes. + +- *No additional parameters beyond the common arguments* ### GDSF -[TBD] +**GreedyDual-Size with Frequency** ranks objects by frequency divided by size, offset by a global aging factor, favouring small and frequently accessed objects. + +- *No additional parameters beyond the common arguments* ### Hyperbolic -[TBD] +**Hyperbolic** samples a few objects on each eviction and evicts the one with the lowest access count divided by time resident in the cache, approximating a priority ordering without maintaining a global structure. + +- *No additional parameters beyond the common arguments* ### ThreeLCache -[TBD] +**3LCache** is a learned policy that predicts, for each object, how valuable it is to retain, and organises objects across three levels accordingly. + +- `objective: str` - Metric the learned model optimises for (default: `"byte-miss-ratio"`) + +!!! warning + Requires a build with `-DENABLE_3L_CACHE=ON`. See [Installation](../getting_started/installation.md#optional-eviction-algorithms). + Constructing it in a build without the flag raises `ImportError`. ### GLCache -[TBD] +**Group-Learned Cache** groups objects into segments, learns to predict each segment's future utility, and evicts by merging the least useful segments rather than making per-object decisions. + +- `segment_size: int` - Number of objects per segment (default: `100`) +- `n_merge: int` - Number of segments merged in one eviction (default: `2`) +- `type: str` - Cache type, e.g. the learned variant or a baseline (default: `"learned"`) +- `rank_intvl: float` - How often segments are re-ranked, as a fraction of the cache (default: `0.02`) +- `merge_consecutive_segs: bool` - Whether merges are restricted to consecutive segments (default: `True`) +- `train_source_y: str` - Source of the training labels (default: `"online"`) +- `retrain_intvl: int` - Seconds between model retraining (default: `86400`) + +!!! warning + Requires a build with `-DENABLE_GLCACHE=ON`. See [Installation](../getting_started/installation.md#optional-eviction-algorithms). + Constructing it in a build without the flag raises `ImportError`. ### LRB -[TBD] +**Learning Relaxed Belady** trains a model to approximate Belady's decision online, evicting objects predicted to have a distant next access. + +- `objective: str` - Metric the learned model optimises for (default: `"byte-miss-ratio"`) + +!!! warning + Requires a build with `-DENABLE_LRB=ON`. See [Installation](../getting_started/installation.md#optional-eviction-algorithms). + Constructing it in a build without the flag raises `ImportError`. + +### PluginCache +**PluginCache** lets you implement an eviction policy in pure Python via hook functions, with no +compilation. It is documented separately in [Plugin System](plugins.md). ## Admission Policies @@ -174,3 +317,26 @@ Implements **AdaptSize**, a feedback-driven policy that periodically adjusts its - `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`) + +### PluginAdmissioner +Lets you implement an admission policy in Python via hook functions. See +[Plugin System](plugins.md#pluginadmissioner). + +## Comparing algorithms + +Because every cache exposes the same interface, sweeping over algorithms is straightforward. +`process_trace` rewinds the reader before it starts, so the same reader can be handed to each +run without an explicit `reset()`: + +```py +import libcachesim as lcs + +URI = "s3://cache-datasets/cache_dataset_oracleGeneral/2007_msr/msr_hm_0.oracleGeneral.zst" +reader = lcs.TraceReader(trace=URI, trace_type=lcs.TraceType.ORACLE_GENERAL_TRACE) + +CACHE_SIZE = 1024 * 1024 +for cls in (lcs.LRU, lcs.FIFO, lcs.ARC, lcs.S3FIFO, lcs.Sieve): + cache = cls(cache_size=CACHE_SIZE) + req_miss_ratio, byte_miss_ratio = cache.process_trace(reader) + print(f"{cache.cache_name:>10}: req {req_miss_ratio:.4f} byte {byte_miss_ratio:.4f}") +``` diff --git a/docs/src/en/faq.md b/docs/src/en/faq.md index 7f23120..d0f0efe 100644 --- a/docs/src/en/faq.md +++ b/docs/src/en/faq.md @@ -2,13 +2,14 @@ 1. How to resolve when pip install fails? - See [installation](https://cachemon.github.io/libCacheSim-python/getting_started/installation/). + See [Installation](getting_started/installation.md). 2. Get an error message like "cannot find Python package" when building. The reason is that building Python bindings requires Python's development headers and libraries. If you have administrative privileges, you can use your system's package manager to install the required package. For example: + * **Debian/Ubuntu**: `sudo apt install python3-dev` * **RHEL/CentOS/Fedora**: `sudo yum install python3-devel` * **macOS**: Installing Python with Homebrew (`brew install python`) is usually sufficient. diff --git a/docs/src/en/getting_started/installation.md b/docs/src/en/getting_started/installation.md index 7e0f4ef..e8a7280 100644 --- a/docs/src/en/getting_started/installation.md +++ b/docs/src/en/getting_started/installation.md @@ -1,3 +1,119 @@ # Installation -[TBD] \ No newline at end of file +## Requirements + +| | | +|---|---| +| **OS** | Linux / macOS | +| **Python** | 3.10 -- 3.13 | +| **Architecture** | x86_64 / aarch64 | + +Windows is not supported. + +## Install from PyPI + +Pre-built wheels are published to [PyPI](https://pypi.org/project/libcachesim/), so in most +cases no compiler is needed: + +```bash +pip install libcachesim +``` + +We recommend [uv](https://docs.astral.sh/uv/) to create and manage the environment: + +```bash +uv venv --python 3.12 --seed +source .venv/bin/activate +uv pip install libcachesim +``` + +Verify the installation: + +```bash +python -c "import libcachesim; print(libcachesim.__version__)" +``` + +## Optional eviction algorithms + +Three algorithms depend on third-party machine-learning libraries and are therefore guarded by +CMake options, all of which default to `OFF`: + +| Algorithm | CMake option | Depends on | +|---|---|---| +| [`LRB`](../examples/simulation.md#lrb) | `ENABLE_LRB` | LightGBM | +| [`ThreeLCache`](../examples/simulation.md#threelcache) | `ENABLE_3L_CACHE` | LightGBM | +| [`GLCache`](../examples/simulation.md#glcache) | `ENABLE_GLCACHE` | XGBoost | + +!!! note + The wheels released on PyPI are built with all three enabled. You only need the steps below + if no wheel matches your platform and pip falls back to building from source, or if you are + building from a source checkout yourself. + +Install the third-party dependencies first: + +```bash +git clone https://github.com/cacheMon/libCacheSim-python.git +cd libCacheSim-python +bash scripts/install_deps.sh + +# If you cannot install system packages (e.g., no sudo access) +bash scripts/install_deps_user.sh +``` + +Then reinstall, passing the options through `CMAKE_ARGS`. Add `--no-cache-dir` to force a +rebuild rather than reusing a cached wheel: + +```bash +# Enable one algorithm +CMAKE_ARGS="-DENABLE_LRB=ON" pip install libcachesim --no-cache-dir + +# Or enable all three +CMAKE_ARGS="-DENABLE_LRB=ON -DENABLE_3L_CACHE=ON -DENABLE_GLCACHE=ON" \ + pip install libcachesim --no-cache-dir +``` + +!!! important + Because the options default to `OFF`, a plain source build silently **omits** these three + algorithms — constructing `LRB`, `ThreeLCache`, or `GLCache` then fails at runtime. + Conversely, turning an option `ON` without its dependency installed makes CMake fail during + configuration (`LIGHTGBM_PATH not found`, or a missing `xgboost` package). Run the + dependency script first. + +## Install from source + +The C library [libCacheSim](https://github.com/1a1a11a/libCacheSim) is vendored as a git +submodule, so the checkout must be recursive: + +```bash +git clone https://github.com/cacheMon/libCacheSim-python.git +cd libCacheSim-python +git submodule update --init --recursive +pip install . +``` + +`scripts/install.sh` wraps the whole flow — it updates the submodule, installs the package in +editable mode, checks that the import works, and runs the test suite: + +```bash +bash scripts/install.sh + +# Same, but with all optional algorithms enabled +bash scripts/install.sh --all +``` + +Building the extension requires a C++17 compiler, CMake ≥ 3.15, and Ninja. The build is driven +by [scikit-build-core](https://scikit-build-core.readthedocs.io/), which configures and builds +the bundled C library before compiling the [pybind11](https://pybind11.readthedocs.io/) +bindings. + +## Troubleshooting + +Two failures are common enough to have their own entries in the +[FAQ](../faq.md): + +- `pip install` fails to find a suitable wheel and the source build errors out. +- The build reports `cannot find Python package` — Python's development headers are missing, or + Python lives in a non-standard location. + +For anything else, please +[open an issue](https://github.com/cacheMon/libCacheSim-python/issues/new/choose). diff --git a/docs/src/en/getting_started/quickstart.md b/docs/src/en/getting_started/quickstart.md index c3f9f63..02cd549 100644 --- a/docs/src/en/getting_started/quickstart.md +++ b/docs/src/en/getting_started/quickstart.md @@ -5,7 +5,7 @@ This guide will help you get started with libCacheSim. ## Prerequisites - OS: Linux / macOS -- Python: 3.9 -- 3.13 +- Python: 3.10 -- 3.13 ## Installation @@ -19,34 +19,8 @@ source .venv/bin/activate uv pip install libcachesim ``` -For users who want to run LRB, ThreeLCache, and GLCache eviction algorithms: - -!!! important - if `uv` cannot find built wheels for your machine, the building system will skip these algorithms by default. - -To enable them, you need to install all third-party dependencies first. - -!!! note - To install all dependencies, you can use these scripts provided. - ```bash - git clone https://github.com/cacheMon/libCacheSim-python.git - cd libCacheSim-python - bash scripts/install_deps.sh - - # If you cannot install software directly (e.g., no sudo access) - bash scripts/install_deps_user.sh - ``` - -Then, you can reinstall libcachesim using the following commands (may need to add `--no-cache-dir` to force it to build from scratch): - -```bash -# Enable LRB -CMAKE_ARGS="-DENABLE_LRB=ON" uv pip install libcachesim -# Enable ThreeLCache -CMAKE_ARGS="-DENABLE_3L_CACHE=ON" uv pip install libcachesim -# Enable GLCache -CMAKE_ARGS="-DENABLE_GLCACHE=ON" uv pip install libcachesim -``` +See [Installation](installation.md) for building from source, and for enabling the LRB, +ThreeLCache, and GLCache eviction algorithms, which are excluded from source builds by default. ## Cache Simulation @@ -98,6 +72,22 @@ The above example demonstrates the basic workflow of using `libcachesim` for cac This workflow applies to most cache algorithms and trace types, making it easy to get started and customize your experiments. +### Sizing the cache relative to the trace + +Absolute byte counts are awkward when comparing traces of very different sizes. Passing +`cache_size` as a `float` in `(0, 1]` instead interprets it as a fraction of the trace's working +set, which requires handing the cache a `reader`: + +```python +cache = lcs.S3FIFO( + cache_size=0.1, # 10% of the trace's working set size in bytes + reader=reader, # Required whenever cache_size is a float +) +``` + +An `int` is always an absolute byte count, so `1024` means 1 KiB while `1024.0` is out of range +and raises `ValueError`. + ## Trace Analysis Here is an example demonstrating how to use `TraceAnalyzer`. @@ -158,7 +148,7 @@ Here is an example of implement `LRU` via the plugin system. from collections import OrderedDict from typing import Any - from libcachesim import PluginCache, LRU, CommonCacheParams, Request + from libcachesim import PluginCache, LRU, CommonCacheParams, Request, SyntheticReader def init_hook(_: CommonCacheParams) -> Any: return OrderedDict() @@ -190,7 +180,7 @@ Here is an example of implement `LRU` via the plugin system. cache_name="Plugin_LRU", ) - reader = lcs.SyntheticReader(num_objects=1000, num_of_req=10000, obj_size=1) + reader = SyntheticReader(num_objects=1000, num_of_req=10000, obj_size=1) req_miss_ratio, byte_miss_ratio = plugin_lru_cache.process_trace(reader) ref_req_miss_ratio, ref_byte_miss_ratio = LRU(128).process_trace(reader) print(f"plugin req miss ratio {req_miss_ratio}, ref req miss ratio {ref_req_miss_ratio}") @@ -199,6 +189,11 @@ Here is an example of implement `LRU` via the plugin system. By defining custom hook functions for cache initialization, hit, miss, eviction, removal, and cleanup, users can easily prototype and test their own cache eviction algorithms. +## Next steps - - +- [Trace Reader](../examples/reader.md) — opening local and S3 traces, slicing, and iteration +- [Cache Simulation](../examples/simulation.md) — every eviction algorithm and admission policy, + with their parameters +- [Trace Analysis](../examples/analysis.md) — workload characterisation with `TraceAnalyzer` +- [Plugin System](../examples/plugins.md) — hook signatures for custom caches and admissioners +- [API Reference](../api.md) — the complete exported surface diff --git a/docs/src/zh/api.md b/docs/src/zh/api.md index 5bb9814..30dc559 100644 --- a/docs/src/zh/api.md +++ b/docs/src/zh/api.md @@ -1,385 +1,338 @@ # API 参考 -本页面提供 libCacheSim Python 绑定的详细 API 文档。 +本页记录 `libcachesim` 包对外导出的全部内容。若需面向任务的教程,请参阅[缓存模拟](examples/simulation.md)、[Trace Reader](examples/reader.md)、[Trace 分析](examples/analysis.md)和[插件系统](examples/plugins.md)。 -## 核心类 +```python +import libcachesim as lcs +``` + +## 请求与对象 -### 缓存类 +### `Request` -所有缓存类都继承自基础缓存接口,并提供以下方法: +trace 中的一次访问。reader 负责填充并返回 `Request` 对象,缓存则消费它们。 ```python -class Cache: - """基础缓存接口。""" - - def get(self, obj_id: int, obj_size: int = 1) -> bool: - """从缓存请求对象。 - - 参数: - obj_id: 对象标识符 - obj_size: 对象大小(字节) - - 返回: - 如果缓存命中返回 True,缓存缺失返回 False - """ - - def get_hit_ratio(self) -> float: - """获取当前缓存命中率。""" - - def get_miss_ratio(self) -> float: - """获取当前缓存缺失率。""" - - def get_num_hits(self) -> int: - """获取缓存命中总数。""" - - def get_num_misses(self) -> int: - """获取缓存缺失总数。""" +Request( + obj_size: int = 1, + op: ReqOp = ReqOp.OP_NOP, + valid: bool = True, + obj_id: int = 0, + clock_time: int = 0, + hv: int = 0, + next_access_vtime: int = -2, + ttl: int = 0, +) ``` -### 可用的缓存算法 +| 属性 | 类型 | 说明 | +|---|---|---| +| `obj_id` | `int` | 对象标识 | +| `obj_size` | `int` | 对象大小(字节) | +| `clock_time` | `int` | 该请求的墙钟时间戳 | +| `next_access_vtime` | `int` | 该对象下一次访问的逻辑时间;仅 oracle trace 中存在,`Belady` / `BeladySize` 需要它 | +| `op` | `ReqOp` | 操作类型 | +| `ttl` | `int` | 存活时间(秒) | +| `hv` | `int` | 哈希值 | +| `valid` | `bool` | `False` 表示 trace 结束,遍历会随之停止 | -```python -# 基础算法 -def LRU(cache_size: int) -> Cache: ... -def LFU(cache_size: int) -> Cache: ... -def FIFO(cache_size: int) -> Cache: ... -def Clock(cache_size: int) -> Cache: ... -def Random(cache_size: int) -> Cache: ... - -# 高级算法 -def ARC(cache_size: int) -> Cache: ... -def S3FIFO(cache_size: int) -> Cache: ... -def Sieve(cache_size: int) -> Cache: ... -def TinyLFU(cache_size: int) -> Cache: ... -def TwoQ(cache_size: int) -> Cache: ... -``` +### `CacheObject` -### TraceReader +由 `Cache.find`、`insert`、`evict` 和 `to_evict` 返回,暴露只读的 `obj_id` 和 `obj_size`。 -```python -class TraceReader: - """读取各种格式的跟踪文件。""" - - def __init__(self, trace_path: str, trace_type: TraceType, - reader_params: ReaderInitParam = None): - """初始化跟踪读取器。 - - 参数: - trace_path: 跟踪文件路径 - trace_type: 跟踪格式类型 - reader_params: 可选的读取器配置 - """ - - def __iter__(self): - """迭代跟踪中的请求。""" - - def reset(self): - """重置读取器到跟踪开始。""" - - def skip(self, n: int): - """跳过 n 个请求。""" - - def clone(self): - """创建读取器的副本。""" -``` +## 枚举类型 -### SyntheticReader +### `ReqOp` -```python -class SyntheticReader: - """生成合成工作负载。""" - - def __init__(self, num_objects: int, num_requests: int, - distribution: str = "zipf", alpha: float = 1.0, - obj_size: int = 1, seed: int = None): - """初始化合成读取器。 - - 参数: - num_objects: 唯一对象数量 - num_requests: 要生成的总请求数 - distribution: 分布类型("zipf","uniform") - alpha: Zipf 偏斜参数 - obj_size: 对象大小(字节) - seed: 用于可重现性的随机种子 - """ +``` +OP_NOP OP_GET OP_GETS OP_SET OP_ADD +OP_CAS OP_REPLACE OP_APPEND OP_PREPEND OP_DELETE +OP_INCR OP_DECR OP_READ OP_WRITE OP_UPDATE +OP_INVALID ``` -### TraceAnalyzer +### `TraceType` -```python -class TraceAnalyzer: - """分析跟踪特征。""" - - def __init__(self, trace_path: str, trace_type: TraceType, - reader_params: ReaderInitParam = None): - """初始化跟踪分析器。""" - - def get_num_requests(self) -> int: - """获取总请求数。""" - - def get_num_objects(self) -> int: - """获取唯一对象数。""" - - def get_working_set_size(self) -> int: - """获取工作集大小。""" +``` +CSV_TRACE BIN_TRACE PLAIN_TXT_TRACE +ORACLE_GENERAL_TRACE LCS_TRACE VSCSI_TRACE +TWR_TRACE TWRNS_TRACE ORACLE_SIM_TWR_TRACE +ORACLE_SYS_TWR_TRACE ORACLE_SIM_TWRNS_TRACE ORACLE_SYS_TWRNS_TRACE +VALPIN_TRACE UNKNOWN_TRACE ``` -## 枚举和常量 +`UNKNOWN_TRACE` 是默认值,它要求 `TraceReader` 根据文件名推断格式。 -### TraceType +### `SamplerType` -```python -class TraceType: - """支持的跟踪文件格式。""" - CSV_TRACE = "csv" - BINARY_TRACE = "binary" - ORACLE_GENERAL_TRACE = "oracle" - PLAIN_TXT_TRACE = "txt" +``` +SPATIAL_SAMPLER TEMPORAL_SAMPLER SHARDS_SAMPLER INVALID_SAMPLER ``` -### SamplerType +## 配置对象 -```python -class SamplerType: - """采样策略。""" - SPATIAL_SAMPLER = "spatial" - TEMPORAL_SAMPLER = "temporal" -``` +### `ReaderInitParam` -### ReqOp +控制 trace 文件的解析方式,通过 `reader_init_params` 传给 `TraceReader`。 ```python -class ReqOp: - """请求操作类型。""" - READ = "read" - WRITE = "write" - DELETE = "delete" +ReaderInitParam( + binary_fmt_str: str = "", + ignore_obj_size: bool = False, + ignore_size_zero_req: bool = True, + obj_id_is_num: bool = True, + obj_id_is_num_set: bool = False, + cap_at_n_req: int = -1, + block_size: int = -1, + has_header: bool = False, + has_header_set: bool = False, + delimiter: str = ",", + trace_start_offset: int = 0, + sampler: Optional[Sampler] = None, +) ``` -## 数据结构 +| 属性 | 说明 | +|---|---| +| `ignore_obj_size` | 把每个对象都当作大小为 1,于是字节缺失率等于请求缺失率 | +| `ignore_size_zero_req` | 跳过对象大小为零的请求 | +| `obj_id_is_num` | 把对象 ID 解析为整数而非字符串 | +| `cap_at_n_req` | 读到该请求数后停止;`-1` 表示不限制 | +| `block_size` | 块级 trace 的块大小;`-1` 表示禁用 | +| `has_header` | CSV trace 是否带表头行 | +| `delimiter` | CSV trace 的字段分隔符 | +| `trace_start_offset` | 开始读取的字节偏移 | +| `binary_fmt_str` | `BIN_TRACE` 的 struct 格式串 | +| `sampler` | 读取时应用的可选 `Sampler` | -### Request +CSV 的字段位置在构造之后以属性方式设置,且**从 1 开始计数**:`time_field`、`obj_id_field`、`obj_size_field`、`op_field`、`ttl_field`、`cnt_field`、`tenant_field`、`next_access_vtime_field`、`n_feature_fields`。 -```python -class Request: - """表示缓存请求。""" - - def __init__(self): - self.obj_id: int = 0 - self.obj_size: int = 1 - self.timestamp: int = 0 - self.op: str = "read" -``` +### `CommonCacheParams` + +每个缓存都由这组参数构建而成。缓存类会替你构造它;你唯一会直接接触它的场合,是把它作为参数传给 `PluginCache` 的 init hook。 + +| 属性 | 类型 | +|---|---| +| `cache_size` | `int` | +| `default_ttl` | `int` | +| `hashpower` | `int` | +| `consider_obj_metadata` | `bool` | + +### `AnalysisOption` 与 `AnalysisParam` -### ReaderInitParam +`TraceAnalyzer` 的配置。各字段及其默认值见 [Trace 分析](examples/analysis.md#selecting-analyses)。 + +## 缓存 + +### `CacheBase` + +所有缓存的基类。完整的方法列表见[逐请求操作](examples/simulation.md#working-with-individual-requests)。 ```python -class ReaderInitParam: - """跟踪读取器的配置参数。""" - - def __init__(self): - self.has_header: bool = False - self.delimiter: str = "," - self.obj_id_is_num: bool = True - self.ignore_obj_size: bool = False - self.ignore_size_zero_req: bool = True - self.cap_at_n_req: int = -1 - self.block_size: int = 4096 - self.trace_start_offset: int = 0 - - # 字段映射(从1开始索引) - self.time_field: int = 1 - self.obj_id_field: int = 2 - self.obj_size_field: int = 3 - self.op_field: int = 4 - - self.sampler: Sampler = None +process_trace(reader: ReaderProtocol, start_req: int = 0, max_req: int = -1) -> tuple[float, float] ``` -### Sampler +回放 trace 并返回 `(request_miss_ratio, byte_miss_ratio)`。使用 C 实现的 reader 时,整个循环在 C++ 中运行并释放 GIL;使用 Python reader 时,则回退为 Python 循环。 + +其他方法:`get`、`find`、`can_insert`、`insert`、`need_eviction`、`evict`、`remove`、`to_evict`、`get_occupied_byte`、`get_n_obj`、`set_cache_size`、`print_cache`。只读属性:`cache_size`、`cache_name`。 + +### 缓存算法 + +所有算法都接受公共参数 `cache_size`、`default_ttl=25920000`、`hashpower=24`、`consider_obj_metadata=False`、`admissioner=None`、`reader=None`,以及下表中的额外参数。各算法的原理见[缓存模拟](examples/simulation.md#caches)。 + +| 类 | 算法特有参数 | +|---|---| +| `LHD` | — | +| `LRU` | — | +| `FIFO` | — | +| `LFU` | — | +| `ARC` | — | +| `Clock` | `init_freq=0`、`n_bit_counter=1` | +| `Random` | — | +| `S3FIFO` | `small_size_ratio=0.1`、`ghost_size_ratio=0.9`、`move_to_main_threshold=2` | +| `Sieve` | — | +| `LIRS` | — | +| `TwoQ` | `a_in_size_ratio=0.25`、`a_out_size_ratio=0.5` | +| `SLRU` | — | +| `WTinyLFU` | `main_cache="SLRU"`、`window_size=0.01` | +| `LeCaR` | `update_weight=True`、`lru_weight=0.5` | +| `LFUDA` | — | +| `ClockPro` | `init_ref=0`、`init_ratio_cold=0.5` | +| `Cacheus` | — | +| `Belady` | — | +| `BeladySize` | `n_samples=128` | +| `LRUProb` | `prob=0.5` | +| `FlashProb` | `ram_size_ratio=0.05`、`disk_admit_prob=0.2`、`ram_cache="LRU"`、`disk_cache="FIFO"` | +| `Size` | — | +| `GDSF` | — | +| `Hyperbolic` | — | +| `ThreeLCache` | `objective="byte-miss-ratio"` —— 需要 `-DENABLE_3L_CACHE=ON` | +| `GLCache` | `segment_size=100`、`n_merge=2`、`type="learned"`、`rank_intvl=0.02`、`merge_consecutive_segs=True`、`train_source_y="online"`、`retrain_intvl=86400` —— 需要 `-DENABLE_GLCACHE=ON` | +| `LRB` | `objective="byte-miss-ratio"` —— 需要 `-DENABLE_LRB=ON` | + +`cache_size` 可以是 `int`(字节),也可以是 `(0, 1]` 区间内的 `float`(工作集的比例,此时需要提供 `reader`)。参见[按比例设置缓存大小](examples/simulation.md#cache-size-as-a-ratio)。 + +### `PluginCache` ```python -class Sampler: - """请求采样配置。""" - - def __init__(self, sample_ratio: float = 1.0, - type: str = "spatial"): - """初始化采样器。 - - 参数: - sample_ratio: 要采样的请求比例(0.0-1.0) - type: 采样类型("spatial" 或 "temporal") - """ - self.sample_ratio = sample_ratio - self.type = type +PluginCache( + cache_size: int | float, + cache_init_hook: Callable, + cache_hit_hook: Callable, + cache_miss_hook: Callable, + cache_eviction_hook: Callable, + cache_remove_hook: Callable, + cache_free_hook: Optional[Callable] = None, + cache_name: str = "PythonHookCache", + default_ttl: int = 25920000, + hashpower: int = 24, + consider_obj_metadata: bool = False, + admissioner: Optional[AdmissionerBase] = None, + reader: Optional[ReaderProtocol] = None, +) ``` -## 工具函数 +各 hook 的签名见[插件系统](examples/plugins.md#plugincache)。`set_hooks(...)` 可以替换已有实例上的 hook。 + +## 准入策略 + +所有准入器都派生自 `AdmissionerBase`,后者暴露 `admit(req)`、`update(req, cache_size)`、`clone()` 和 `free()`。把实例作为任意缓存的 `admissioner` 参数传入即可。 + +| 类 | 参数 | +|---|---| +| `BloomFilterAdmissioner` | — | +| `ProbAdmissioner` | `prob: float = None` | +| `SizeAdmissioner` | `size_threshold: int = None` | +| `SizeProbabilisticAdmissioner` | `exponent: float = None` | +| `AdaptSizeAdmissioner` | `max_iteration: int = None`、`reconf_interval: int = None` | +| `PluginAdmissioner` | `admissioner_name` 加五个 hook | -### 合成跟踪生成 +参数保持为 `None` 时会使用 C 库自身的默认值,这些默认值列在[准入策略](examples/simulation.md#admission-policies)中。 ```python -def create_zipf_requests(num_objects, num_requests, alpha, obj_size, seed=None): - """ - 创建 Zipf 分布的合成请求。 - - 参数: - num_objects (int): 唯一对象数量 - num_requests (int): 要生成的总请求数 - alpha (float): Zipf 偏斜参数(越高越偏斜) - obj_size (int): 每个对象的大小(字节) - seed (int, 可选): 随机种子,用于可重现性 - - 返回: - List[Request]: 生成的请求列表 - """ - -def create_uniform_requests(num_objects, num_requests, obj_size, seed=None): - """ - 创建均匀分布的合成请求。 - - 参数: - num_objects (int): 唯一对象数量 - num_requests (int): 要生成的总请求数 - obj_size (int): 每个对象的大小(字节) - seed (int, 可选): 随机种子,用于可重现性 - - 返回: - List[Request]: 生成的请求列表 - """ +PluginAdmissioner( + admissioner_name: str, + admissioner_init_hook: Callable, + admissioner_admit_hook: Callable, + admissioner_clone_hook: Callable, + admissioner_update_hook: Callable, + admissioner_free_hook: Callable, +) ``` -### 缓存算法 +## Reader + +### `ReaderProtocol` -可用的缓存算法及其工厂函数: +一个 `runtime_checkable` 的协议,描述任何 reader 必须提供的接口,因此自定义 reader 可以用在所有接受 `TraceReader` 的地方: ```python -# 基础算法 -LRU(cache_size: int) -> Cache -LFU(cache_size: int) -> Cache -FIFO(cache_size: int) -> Cache -Clock(cache_size: int) -> Cache -Random(cache_size: int) -> Cache - -# 高级算法 -ARC(cache_size: int) -> Cache -S3FIFO(cache_size: int) -> Cache -Sieve(cache_size: int) -> Cache -TinyLFU(cache_size: int) -> Cache -TwoQ(cache_size: int) -> Cache -LRB(cache_size: int) -> Cache - -# 实验性算法 -cache_3L(cache_size: int) -> Cache +get_num_of_req() -> int +read_one_req() -> Request +skip_n_req(n: int) -> int +reset() -> None +close() -> None +clone() -> ReaderProtocol +get_working_set_size() -> tuple[int, int] +__iter__() / __next__() / __len__() ``` -### 性能指标 +### `TraceReader` ```python -class CacheStats: - """缓存性能统计。""" - - def __init__(self): - self.hits = 0 - self.misses = 0 - self.evictions = 0 - self.bytes_written = 0 - self.bytes_read = 0 - - @property - def hit_ratio(self) -> float: - """计算命中率。""" - total = self.hits + self.misses - return self.hits / total if total > 0 else 0.0 - - @property - def miss_ratio(self) -> float: - """计算缺失率。""" - return 1.0 - self.hit_ratio +TraceReader( + trace: str | Reader, + trace_type: TraceType = TraceType.UNKNOWN_TRACE, + reader_init_params: Optional[ReaderInitParam] = None, +) ``` -## 错误处理 +`trace` 可以是本地路径,也可以是 `s3://bucket/key` 形式的 URI;S3 对象会在首次使用时下载并缓存到本地。参见 [Trace Reader](examples/reader.md)。 + +- 可以直接对 reader 进行遍历和 `len()`。 +- 支持下标和切片:`reader[0]`、`reader[:100]`、`reader[-100:]`。切片返回的是一个基于克隆 reader 的迭代器,原 reader 的位置不受影响。 +- 定位相关方法:`read_one_req()`、`read_first_req(req)`、`read_last_req(req)`、`skip_n_req(n)`、`go_back_one_req()`、`read_one_req_above()`、`set_read_pos(pos)`、`reset()`、`close()`、`clone()`。 +- `get_working_set_size()` 返回 `(n_object, n_byte)`。 +- 只读属性包括 `n_read_req`、`n_total_req`、`n_req_left`、`trace_path`、`file_size`、`trace_type`、`trace_format`、`is_zstd_file`、`cloned`、`sampler`、`read_direction`、`lcs_ver`、`init_params`。`ignore_obj_size`、`ignore_size_zero_req` 和 `block_size` 可写。 -库使用标准的 Python 异常: +`read_one_req()` 在 trace 结束后调用会抛出 `RuntimeError`,而遍历则会正常结束。 -- `ValueError`: 无效参数或配置 -- `FileNotFoundError`: 跟踪文件未找到 -- `RuntimeError`: 底层 C++ 库的运行时错误 -- `MemoryError`: 内存不足条件 +### `SyntheticReader` -错误处理示例: +在内存中生成请求,无需 trace 文件。 ```python -try: - reader = lcs.TraceReader("nonexistent.csv", lcs.TraceType.CSV_TRACE) -except FileNotFoundError: - print("跟踪文件未找到") -except ValueError as e: - print(f"无效配置: {e}") +SyntheticReader( + num_of_req: int, + obj_size: int = 4000, + time_span: int = 604800, + start_obj_id: int = 0, + seed: Optional[int] = None, + alpha: float = 1.0, + dist: str = "zipf", + num_objects: Optional[int] = None, +) ``` -## 配置选项 +`dist` 取 `"zipf"` 或 `"uniform"`;`alpha` 仅对 Zipf 生效。`num_objects` 默认等于 `num_of_req`。参数非法时抛出 `ValueError`。 -### 读取器配置 +!!! note + `SyntheticReader` 是纯 Python 实现的 reader(`c_reader = False`)。`process_trace` 仍然可用,但会回退为 Python 循环,而 `TraceAnalyzer` 会直接拒绝它。 + +### Trace 生成器 ```python -reader_params = lcs.ReaderInitParam( - has_header=True, # CSV 有标题行 - delimiter=",", # 字段分隔符 - obj_id_is_num=True, # 对象 ID 是数字 - ignore_obj_size=False, # 不忽略对象大小 - ignore_size_zero_req=True, # 忽略零大小请求 - cap_at_n_req=1000000, # 限制请求数量 - block_size=4096, # 块大小(用于基于块的跟踪) - trace_start_offset=0, # 跳过初始请求 -) +create_zipf_requests(num_objects, num_requests, alpha=1.0, obj_size=4000, + time_span=604800, start_obj_id=0, seed=None) -> Iterator[Request] -# 字段映射(从1开始索引) -reader_params.time_field = 1 -reader_params.obj_id_field = 2 -reader_params.obj_size_field = 3 -reader_params.op_field = 4 +create_uniform_requests(num_objects, num_requests, obj_size=4000, + time_span=604800, start_obj_id=0, seed=None) -> Iterator[Request] ``` -### 采样配置 +两者返回的都是**迭代器**而非列表;如果需要重复回放,请用 `list(...)` 包一层。 + +## `TraceAnalyzer` ```python -sampler = lcs.Sampler( - sample_ratio=0.1, # 采样 10% 的请求 - type=lcs.SamplerType.SPATIAL_SAMPLER # 空间采样 +TraceAnalyzer( + reader: ReaderProtocol, + output_path: str, + analysis_param: Optional[AnalysisParam] = None, + analysis_option: Optional[AnalysisOption] = None, ) -reader_params.sampler = sampler ``` -## 线程安全 +方法:`run()`、`cleanup()`。要求使用 C 实现的 reader,否则抛出 `ReaderException`。参见 [Trace 分析](examples/analysis.md)。 -库为大多数用例提供线程安全操作: +## `Util` -- 单个缓存实例内的缓存操作是线程安全的 -- 可以并发使用多个读取器 -- 分析操作可以利用多线程 +用于 trace 转换和模拟的静态辅助方法。 -对于高并发场景,考虑为每个线程使用单独的缓存实例。 +```python +Util.convert_to_oracleGeneral(reader, ofilepath, output_txt=False, remove_size_change=False) +Util.convert_to_lcs(reader, ofilepath, output_txt=False, remove_size_change=False, lcs_ver=1) +Util.process_trace(cache, reader, start_req=0, max_req=-1) -> tuple[float, float] +``` -## 内存管理 +- `convert_to_oracleGeneral` 把 trace 改写为 oracleGeneral 格式,并计算 `Belady` 所需的下次访问字段。 +- `convert_to_lcs` 写出 LCS 格式;`lcs_ver` 用于选择版本(1–8)。 +- `Util.process_trace` 等价于 `cache.process_trace(...)`,但要求使用 C 实现的 reader,否则抛出 `ValueError`。 -库自动管理大多数操作的内存: +## 元信息 -- 缓存对象处理自己的内存分配 -- 跟踪读取器自动管理缓冲 -- 请求对象轻量且可重用 +`libcachesim.__version__` 是已安装的包版本;`libcachesim.__doc__` 是扩展模块的 docstring。 -对于大规模模拟,监控内存使用并考虑: +## 异常 -- 使用采样减少跟踪大小 -- 分块处理跟踪 -- 适当限制缓存大小 +绑定层使用标准的 Python 异常: -## 最佳实践 +| 异常 | 触发场景 | +|---|---| +| `ValueError` | 参数非法——S3 URI 格式错误、`cache_size` 为超出 `(0, 1]` 的 float 或缺少 `reader`、`dist` 取值不支持,或把非 C reader 传给了 `Util.process_trace` | +| `TypeError` | `reader_init_params` 不是 `ReaderInitParam`;用 `int` 或 `slice` 以外的类型给 reader 取下标 | +| `IndexError` | reader 下标越界,或定位过程中已到达 trace 末尾 | +| `RuntimeError` | trace 已结束后仍调用 `read_one_req()` | +| `ImportError` | 在未开启对应编译选项的构建中构造 `LRB`、`ThreeLCache` 或 `GLCache` | +| `ReaderException` | 把非 C reader 传给了 `TraceAnalyzer` | -1. **使用适当的缓存大小**: 根据模拟目标确定缓存大小 -2. **设置随机种子**: 用于合成跟踪的可重现结果 -3. **处理错误**: 始终将文件操作包装在 try-catch 块中 -4. **监控内存**: 对于大型跟踪,考虑采样或分块 -5. **使用线程**: 为分析任务利用多线程 -6. **验证跟踪**: 在模拟前检查跟踪格式和内容 +`ReaderException` 未在包级别重新导出;如果需要按类型捕获它,请从 `libcachesim.trace_analyzer` 导入。 diff --git a/docs/src/zh/developer.md b/docs/src/zh/developer.md new file mode 100644 index 0000000..8d41efb --- /dev/null +++ b/docs/src/zh/developer.md @@ -0,0 +1,184 @@ +# 开发者指南 + +本页面向的是*开发* libCacheSim Python 本身的人,而非它的使用者。如果你只是想使用这个库,请从[安装指南](getting_started/installation.md)开始。 + +## 仓库结构 + +``` +libCacheSim-python/ +├── libcachesim/ # Python 包 +│ ├── __init__.py # 对外 API(__all__) +│ ├── __init__.pyi # 编译扩展的类型存根 +│ ├── cache.py # 缓存包装类 +│ ├── admissioner.py # 准入策略包装类 +│ ├── trace_reader.py # TraceReader,支持 S3 +│ ├── synthetic_reader.py +│ ├── trace_analyzer.py +│ ├── protocols.py # ReaderProtocol +│ └── util.py # trace 转换辅助函数 +├── src/ # pybind11 绑定(C++) +│ ├── export_cache.cpp +│ ├── export_reader.cpp +│ ├── export_analyzer.cpp +│ ├── export_admissioner.cpp +│ └── libCacheSim/ # git 子模块:C 语言库 +├── tests/ +├── examples/ +├── scripts/ +└── docs/ +``` + +整体结构是:C 库负责实际计算,`src/*.cpp` 通过 pybind11 将其暴露出来,`libcachesim/*.py` 再把它封装成符合 Python 习惯的 API。 + +## 环境搭建 + +C 库是一个 git 子模块,因此检出时必须递归拉取: + +```bash +git clone https://github.com/cacheMon/libCacheSim-python.git +cd libCacheSim-python +git submodule update --init --recursive +pip install -e ".[dev]" +``` + +`dev` 附加依赖会引入 `pytest`、`ruff`、`mypy` 和 `pre-commit`。 + +`scripts/install.sh` 会完成上述全部步骤并随后运行测试。加上 `--all` 可启用可选的学习型算法: + +```bash +bash scripts/install.sh --all +``` + +### 构建系统 + +构建通过 [scikit-build-core](https://scikit-build-core.readthedocs.io/) 完成,配置写在 `pyproject.toml` 中。它会先配置并构建内置的 C 库,再用 Ninja 编译绑定层。可选特性通过 `CMAKE_ARGS` 开关: + +```bash +CMAKE_ARGS="-DENABLE_LRB=ON -DENABLE_3L_CACHE=ON -DENABLE_GLCACHE=ON" pip install -e . +``` + +注意 `pyproject.toml` 设置了 `build-dir = "build"`,因此增量重建会复用此前的产物。如果重建时读到了陈旧的中间产物,请删除 `build/` 和 `src/libCacheSim/build/`。 + +## 测试 + +```bash +python -m pytest tests/ +``` + +!!! important + `pyproject.toml` 中设置了 `addopts = [..., "-m", "not optional"]`,因此直接运行 `pytest` 会**跳过**可选学习型算法的测试。要运行这些测试,需要用对应的 CMake 选项构建,并显式指定 marker: + + ```bash + python -m pytest tests/ -m optional + ``` + +测试套件还设置了 `filterwarnings = ["error", ...]`,因此新出现的警告会直接导致构建失败。 + +有几个测试会从公开的 S3 存储桶下载 trace,因此首次运行需要网络;后续运行会使用本地缓存。 + +## 代码风格 + +仓库中并未提交 `pre-commit` 配置,因此请直接运行相关工具: + +```bash +# 检查并自动修复 Python 代码 +ruff check libcachesim/ tests/ examples/ +ruff format libcachesim/ + +# 类型检查 +mypy libcachesim/ + +# 格式化 C++(使用仓库中的 .clang-format) +clang-format -i src/*.cpp src/*.h +``` + +`ruff` 在 `pyproject.toml` 中配置,行宽为 120,启用了 `E`、`F`、`UP`、`B`、`SIM` 和 `G` 规则集。 + +## 新增一个缓存算法 + +若该算法在 C 库中已经存在,接入它需要五步: + +1. **绑定。** 在 `src/export_cache.cpp` 中,参照已有算法的写法暴露该算法的 `*_init` 函数。 +2. **封装。** 在 `libcachesim/cache.py` 中新增一个继承自 `CacheBase` 的类。用已有的 `_create_common_params(...)` 辅助函数来构造公共参数——这也是每个缓存能够免费获得按比例设置 `cache_size` 能力的原因——并把算法特有的设置以 `cache_specific_params` 字符串传入: + + ```python + class MyAlgo(CacheBase): + """My algorithm + + Special parameters: + my_param: what it controls (default: 0.5) + """ + + def __init__( + self, + cache_size: int | float, + default_ttl: int = 86400 * 300, + hashpower: int = 24, + consider_obj_metadata: bool = False, + my_param: float = 0.5, + admissioner: AdmissionerBase = None, + reader: ReaderProtocol = None, + ): + cache_specific_params = f"my-param={my_param}" + super().__init__( + _cache=MyAlgo_init( + _create_common_params( + cache_size, default_ttl, hashpower, consider_obj_metadata, reader + ), + cache_specific_params, + ), + admissioner=admissioner, + ) + ``` + + 注意 C 库的参数名使用连字符(`my-param`),而 Python 关键字参数使用下划线。 +3. **导出。** 把该类同时加入 `libcachesim/__init__.py` 的 import 块和 `__all__`,并在 `libcachesim/__init__.pyi` 中补上存根。 +4. **测试。** 在 `tests/test_cache.py` 中增加用例。如果该算法依赖可选的编译选项,请标记 `@pytest.mark.optional`。 +5. **写文档。** 在 `docs/src/en/examples/simulation.md` 中新增一节,并在 `docs/src/en/api.md` 的表格中增加一行。 + +如果该算法依赖第三方库,请像 `ThreeLCache` 和 `GLCache` 那样保护 import——用 `try`/`except ImportError` 捕获后重新抛出,并在消息中给出用户需要的 `CMAKE_ARGS` 写法。 + +## 文档 + +站点使用 [MkDocs Material](https://squidfunk.github.io/mkdocs-material/) 加 `mkdocs-static-i18n` 构建。源文件位于 `docs/src//`,各语言目录必须彼此镜像:`en/examples/reader.md` 这个页面的译文必须位于 `zh/examples/reader.md`。放在其他路径的文件根本不会被渲染。缺失的译文会回退到英文,因此部分翻译也是可以接受的。 + +本地构建与预览: + +```bash +bash scripts/build_docs.sh --serve # http://127.0.0.1:8000 +``` + +或者直接运行: + +```bash +pip install -r docs/requirements.txt +cd docs && mkdocs build --clean --strict +``` + +提 PR 之前请务必用 `--strict` 构建一次——CI 跑的就是这个命令,它会把失效的站内链接变成构建失败。 + +页面之间请优先使用相对链接(`../faq.md`),而不是指向已发布站点的绝对 URL,这样在本地构建和语言回退时链接仍然有效。 + +翻译中文页面时,如果某个标题会被其他页面以锚点链接引用,请用 `attr_list` 显式固定锚点 ID,例如 `## 按比例设置缓存大小 {#cache-size-as-a-ratio}`,这样跨页链接在两种语言下都能正常工作。 + +## 持续集成 + +| 工作流 | 触发条件 | 作用 | +|---|---|---| +| `.github/workflows/build.yml` | `src/`、`libcachesim/`、`tests/` 下的改动 | 在 Ubuntu 和 macOS(Intel 与 Apple Silicon)上针对 Python 3.10–3.13 构建并测试,同时单独构建文档 | +| `.github/workflows/docs.yml` | `docs/` 下的改动 | 以 `--strict` 构建,并在 `main` 分支上部署到 GitHub Pages | +| `.github/workflows/pypi-release.yml` | 发布 release,或手动触发 | 用 cibuildwheel 构建 wheel 并发布到 PyPI | + +注意 `build.yml` 只在代码路径变动时触发,而 `docs.yml` 只在 `docs/` 变动时触发,因此仅改文档的 PR 不会运行测试套件,反之亦然。 + +## 发布 + +发布流程由创建 GitHub release 触发,进而运行 `pypi-release.yml`。 + +wheel 由 [cibuildwheel](https://cibuildwheel.pypa.io/) 按 `pyproject.toml` 中的配置构建,会为所有受支持的 CPython 版本构建 manylinux 和 macOS wheel,并启用**全部三个**可选算法,同时通过导入 wheel 并分别运行默认测试和 `optional` 测试来做校验。 + +`scripts/sync_version.py` 负责让 `pyproject.toml` 中的版本号与子模块中的 `src/libCacheSim/version.txt` 保持一致。 + +## 参与贡献 + +Bug 报告和功能需求请提交到 [GitHub issues](https://github.com/cacheMon/libCacheSim-python/issues/new/choose)。如果改动涉及的是模拟内核本身而非绑定层,正确的仓库是 [1a1a11a/libCacheSim](https://github.com/1a1a11a/libCacheSim)。 diff --git a/docs/src/zh/examples.md b/docs/src/zh/examples.md deleted file mode 100644 index 0e85828..0000000 --- a/docs/src/zh/examples.md +++ /dev/null @@ -1,488 +0,0 @@ -# 示例和教程 - -本页提供使用 libCacheSim Python 绑定的实际示例和深入教程。 - -## 基础示例 - -### 简单缓存模拟 - -最基本的缓存模拟示例: - -```python -import libcachesim as lcs - -# 创建一个1MB大小的LRU缓存 -cache = lcs.LRU(cache_size=1024*1024) - -# 模拟一些请求 -requests = [ - (1, 100), # 对象1,大小100字节 - (2, 200), # 对象2,大小200字节 - (1, 100), # 对象1,再次访问(命中) - (3, 150), # 对象3,大小150字节 -] - -for obj_id, size in requests: - hit = cache.get(obj_id, size) - print(f"对象 {obj_id}: {'命中' if hit else '缺失'}") - -# 获取统计信息 -print(f"命中率: {cache.get_hit_ratio():.2%}") -``` - -### 跟踪文件处理 - -从CSV文件读取和处理跟踪: - -```python -import libcachesim as lcs - -# 配置跟踪读取器 -reader_params = lcs.ReaderInitParam() -reader_params.has_header = True -reader_params.delimiter = "," -reader_params.time_field = 1 -reader_params.obj_id_field = 2 -reader_params.obj_size_field = 3 - -# 创建跟踪读取器 -reader = lcs.TraceReader("workload.csv", lcs.TraceType.CSV_TRACE, reader_params) - -# 创建缓存 -cache = lcs.LRU(cache_size=1024*1024) - -# 处理跟踪 -request_count = 0 -for request in reader: - hit = cache.get(request.obj_id, request.obj_size) - request_count += 1 - - if request_count % 10000 == 0: - print(f"处理了 {request_count} 个请求,命中率: {cache.get_hit_ratio():.2%}") - -print(f"最终命中率: {cache.get_hit_ratio():.2%}") -``` - -## 合成工作负载生成 - -### Zipf分布请求 - -生成具有Zipf分布的合成工作负载: - -```python -import libcachesim as lcs - -# 创建Zipf分布的合成读取器 -reader = lcs.SyntheticReader( - num_objects=10000, - num_requests=100000, - distribution="zipf", - alpha=1.0, # Zipf偏斜参数 - obj_size=4096, - seed=42 # 为了可重现性 -) - -# 创建缓存 -cache = lcs.LRU(cache_size=10*1024*1024) # 10MB - -# 运行模拟 -for request in reader: - cache.get(request.obj_id, request.obj_size) - -print(f"Zipf工作负载 (α=1.0) 命中率: {cache.get_hit_ratio():.2%}") - -# 尝试不同的偏斜参数 -for alpha in [0.5, 1.0, 1.5, 2.0]: - reader = lcs.SyntheticReader( - num_objects=10000, - num_requests=50000, - distribution="zipf", - alpha=alpha, - obj_size=4096, - seed=42 - ) - - cache = lcs.LRU(cache_size=5*1024*1024) - for request in reader: - cache.get(request.obj_id, request.obj_size) - - print(f"α={alpha}: 命中率 {cache.get_hit_ratio():.2%}") -``` - -### 均匀分布请求 - -```python -import libcachesim as lcs - -# 创建均匀分布的合成读取器 -reader = lcs.SyntheticReader( - num_objects=5000, - num_requests=50000, - distribution="uniform", - obj_size=4096, - seed=42 -) - -cache = lcs.LRU(cache_size=5*1024*1024) -for request in reader: - cache.get(request.obj_id, request.obj_size) - -print(f"均匀工作负载命中率: {cache.get_hit_ratio():.2%}") -``` - -## 缓存算法比较 - -### 多算法评估 - -比较不同缓存算法的性能: - -```python -import libcachesim as lcs - -# 创建合成工作负载 -reader = lcs.SyntheticReader( - num_objects=10000, - num_requests=100000, - distribution="zipf", - alpha=1.2, - obj_size=4096, - seed=42 -) - -# 保存请求以便重用 -requests = list(reader) - -# 测试的算法 -algorithms = { - 'LRU': lcs.LRU, - 'LFU': lcs.LFU, - 'FIFO': lcs.FIFO, - 'ARC': lcs.ARC, - 'S3FIFO': lcs.S3FIFO, - 'Sieve': lcs.Sieve, -} - -cache_size = 10*1024*1024 # 10MB - -results = {} -for name, algorithm in algorithms.items(): - cache = algorithm(cache_size) - - for request in requests: - cache.get(request.obj_id, request.obj_size) - - results[name] = cache.get_hit_ratio() - print(f"{name:8}: {cache.get_hit_ratio():.2%}") - -# 找到最佳算法 -best_algo = max(results, key=results.get) -print(f"\n最佳算法: {best_algo} ({results[best_algo]:.2%})") -``` - -## 跟踪采样 - -### 空间采样 - -使用采样减少大型跟踪的大小: - -```python -import libcachesim as lcs - -# 设置采样参数 -sampler = lcs.Sampler( - sample_ratio=0.1, # 采样10%的请求 - type=lcs.SamplerType.SPATIAL_SAMPLER -) - -reader_params = lcs.ReaderInitParam() -reader_params.has_header = True -reader_params.sampler = sampler - -# 读取采样跟踪 -reader = lcs.TraceReader("large_trace.csv", lcs.TraceType.CSV_TRACE, reader_params) - -cache = lcs.LRU(cache_size=1024*1024) -request_count = 0 - -for request in reader: - cache.get(request.obj_id, request.obj_size) - request_count += 1 - -print(f"处理了 {request_count} 个采样请求") -print(f"采样命中率: {cache.get_hit_ratio():.2%}") -``` - -### 时间采样 - -```python -import libcachesim as lcs - -# 时间采样配置 -sampler = lcs.Sampler( - sample_ratio=0.05, # 采样5% - type=lcs.SamplerType.TEMPORAL_SAMPLER -) - -reader_params = lcs.ReaderInitParam() -reader_params.sampler = sampler - -reader = lcs.TraceReader("timestamped_trace.csv", lcs.TraceType.CSV_TRACE, reader_params) - -# 运行模拟... -``` - -## 跟踪分析 - -### 基本跟踪统计 - -分析跟踪特征: - -```python -import libcachesim as lcs - -# 创建跟踪分析器 -analyzer = lcs.TraceAnalyzer("workload.csv", lcs.TraceType.CSV_TRACE) - -# 分析基本统计 -print("跟踪分析:") -print(f"总请求数: {analyzer.get_num_requests():,}") -print(f"唯一对象数: {analyzer.get_num_objects():,}") -print(f"平均对象大小: {analyzer.get_average_obj_size():.2f} 字节") -print(f"总数据大小: {analyzer.get_total_size():,} 字节") - -# 分析重用距离 -reuse_distances = analyzer.get_reuse_distance() -print(f"平均重用距离: {sum(reuse_distances)/len(reuse_distances):.2f}") -``` - -### 流行度分析 - -```python -import libcachesim as lcs -import matplotlib.pyplot as plt - -# 创建分析器 -analyzer = lcs.TraceAnalyzer("workload.csv", lcs.TraceType.CSV_TRACE) - -# 获取对象流行度 -popularity = analyzer.get_popularity() - -# 绘制流行度分布 -plt.figure(figsize=(10, 6)) -plt.loglog(range(1, len(popularity)+1), sorted(popularity, reverse=True)) -plt.xlabel('对象排名') -plt.ylabel('访问频率') -plt.title('对象流行度分布') -plt.grid(True) -plt.show() -``` - -## 高级场景 - -### 缓存层次结构 - -模拟多级缓存层次结构: - -```python -import libcachesim as lcs - -class CacheHierarchy: - def __init__(self, l1_size, l2_size): - self.l1_cache = lcs.LRU(l1_size) # L1缓存 - self.l2_cache = lcs.LRU(l2_size) # L2缓存 - self.l1_hits = 0 - self.l2_hits = 0 - self.misses = 0 - - def get(self, obj_id, obj_size): - # 首先检查L1 - if self.l1_cache.get(obj_id, obj_size): - self.l1_hits += 1 - return True - - # 然后检查L2 - if self.l2_cache.get(obj_id, obj_size): - self.l2_hits += 1 - # 将对象提升到L1 - self.l1_cache.get(obj_id, obj_size) - return True - - # 完全缺失 - self.misses += 1 - # 将对象加载到两个级别 - self.l1_cache.get(obj_id, obj_size) - self.l2_cache.get(obj_id, obj_size) - return False - - def get_stats(self): - total = self.l1_hits + self.l2_hits + self.misses - return { - 'l1_hit_ratio': self.l1_hits / total, - 'l2_hit_ratio': self.l2_hits / total, - 'overall_hit_ratio': (self.l1_hits + self.l2_hits) / total - } - -# 使用缓存层次结构 -hierarchy = CacheHierarchy(l1_size=1024*1024, l2_size=10*1024*1024) - -reader = lcs.SyntheticReader( - num_objects=50000, - num_requests=100000, - distribution="zipf", - alpha=1.0, - obj_size=4096, - seed=42 -) - -for request in reader: - hierarchy.get(request.obj_id, request.obj_size) - -stats = hierarchy.get_stats() -print(f"L1命中率: {stats['l1_hit_ratio']:.2%}") -print(f"L2命中率: {stats['l2_hit_ratio']:.2%}") -print(f"总命中率: {stats['overall_hit_ratio']:.2%}") -``` - -### 缓存预热 - -在评估前预热缓存: - -```python -import libcachesim as lcs - -reader = lcs.SyntheticReader( - num_objects=10000, - num_requests=200000, - distribution="zipf", - alpha=1.0, - obj_size=4096, - seed=42 -) - -cache = lcs.LRU(cache_size=5*1024*1024) - -# 分为预热和评估阶段 -warmup_requests = 50000 -eval_requests = 0 - -for i, request in enumerate(reader): - hit = cache.get(request.obj_id, request.obj_size) - - if i < warmup_requests: - # 预热阶段 - 不计算统计 - continue - else: - # 评估阶段 - eval_requests += 1 - -print(f"预热后命中率: {cache.get_hit_ratio():.2%}") -print(f"评估请求数: {eval_requests}") -``` - -### 动态缓存大小 - -随时间变化缓存大小: - -```python -import libcachesim as lcs - -reader = lcs.SyntheticReader( - num_objects=10000, - num_requests=100000, - distribution="zipf", - alpha=1.0, - obj_size=4096, - seed=42 -) - -# 从小缓存开始 -initial_size = 1024*1024 # 1MB -max_size = 10*1024*1024 # 10MB -growth_interval = 10000 # 每10000个请求增长 - -cache = lcs.LRU(initial_size) -current_size = initial_size - -for i, request in enumerate(reader): - # 定期增加缓存大小 - if i > 0 and i % growth_interval == 0 and current_size < max_size: - current_size = min(current_size * 2, max_size) - # 注意:这里需要创建新缓存,因为现有缓存大小无法动态更改 - new_cache = lcs.LRU(current_size) - cache = new_cache - print(f"在请求 {i} 处将缓存大小增加到 {current_size/1024/1024:.1f}MB") - - cache.get(request.obj_id, request.obj_size) - -print(f"最终命中率: {cache.get_hit_ratio():.2%}") -``` - -## 性能优化技巧 - -### 批量处理 - -```python -import libcachesim as lcs - -# 处理大型跟踪时批量处理请求 -def process_trace_in_batches(filename, cache, batch_size=10000): - reader = lcs.TraceReader(filename, lcs.TraceType.CSV_TRACE) - - batch = [] - total_processed = 0 - - for request in reader: - batch.append(request) - - if len(batch) >= batch_size: - # 处理批次 - for req in batch: - cache.get(req.obj_id, req.obj_size) - - total_processed += len(batch) - print(f"处理了 {total_processed} 个请求") - batch = [] - - # 处理剩余请求 - for req in batch: - cache.get(req.obj_id, req.obj_size) - - return total_processed + len(batch) - -# 使用 -cache = lcs.LRU(cache_size=10*1024*1024) -total = process_trace_in_batches("large_trace.csv", cache) -print(f"总共处理了 {total} 个请求") -``` - -### 内存高效的请求处理 - -```python -import libcachesim as lcs - -def memory_efficient_simulation(filename, cache_size): - """内存高效的缓存模拟。""" - - reader_params = lcs.ReaderInitParam() - reader_params.cap_at_n_req = 1000000 # 限制内存中的请求数 - - reader = lcs.TraceReader(filename, lcs.TraceType.CSV_TRACE, reader_params) - cache = lcs.LRU(cache_size) - - request_count = 0 - for request in reader: - cache.get(request.obj_id, request.obj_size) - request_count += 1 - - # 定期报告进度 - if request_count % 100000 == 0: - print(f"进度: {request_count:,} 请求,命中率: {cache.get_hit_ratio():.2%}") - - return cache.get_hit_ratio() - -# 使用 -hit_ratio = memory_efficient_simulation("workload.csv", 10*1024*1024) -print(f"最终命中率: {hit_ratio:.2%}") -``` - -这些示例展示了libCacheSim Python绑定的各种使用场景,从基础缓存模拟到高级性能分析和优化技术。根据您的具体需求调整这些示例。 diff --git a/docs/src/zh/examples/analysis.md b/docs/src/zh/examples/analysis.md new file mode 100644 index 0000000..077c0aa --- /dev/null +++ b/docs/src/zh/examples/analysis.md @@ -0,0 +1,133 @@ +# Trace 分析 + +除了模拟缓存之外,libCacheSim 还能直接刻画负载特征:请求速率、对象大小分布、复用距离、流行度等。这些都通过 `TraceAnalyzer` 完成,它是底层 libCacheSim lib 中分析器的一层轻量封装。 + +## 基本用法 + +`TraceAnalyzer` 接受一个 reader、一个输出路径前缀,以及两个可选的配置对象: + +```python +import libcachesim as lcs + +# 第 1 步:打开一条 trace(详见 Trace Reader 页面) +URI = "s3://cache-datasets/cache_dataset_oracleGeneral/2007_msr/msr_hm_0.oracleGeneral.zst" +reader = lcs.TraceReader( + trace=URI, + trace_type=lcs.TraceType.ORACLE_GENERAL_TRACE, + reader_init_params=lcs.ReaderInitParam(ignore_obj_size=False), +) + +# 第 2 步:运行分析 +analyzer = lcs.TraceAnalyzer(reader, "example_analysis") +analyzer.run() +``` + +构造参数如下: + +- `reader: ReaderProtocol`——待分析的 trace。 +- `output_path: str`——生成的结果文件的前缀。 +- `analysis_option: AnalysisOption`(可选)——要运行哪些分析。默认为 `AnalysisOption()`。 +- `analysis_param: AnalysisParam`(可选)——这些分析的调节参数。默认为 `AnalysisParam()`。 + +!!! important + 分析器完全运行在 C++ 后端,因此只接受由 C 实现的 reader——实际上就是 [`TraceReader`](reader.md)。传入 `SyntheticReader` 会抛出 `ReaderException: Only C/C++ reader is supported`。若要分析合成负载,请先用 `Util.convert_to_oracleGeneral` 将其写出,再用 `TraceReader` 重新打开。 + +## 选择分析项 {#selecting-analyses} + +`AnalysisOption` 的每个字段对应一项分析,其中五项默认开启: + +| 选项 | 默认值 | 含义 | +|---|---|---| +| `req_rate` | `True` | 请求速率与对象速率随时间的变化 | +| `access_pattern` | `True` | 单个对象随时间的访问模式 | +| `size` | `True` | 对象大小分布,分别按请求和按对象统计 | +| `reuse` | `True` | 复用时间 / 复用距离分布 | +| `popularity` | `True` | 对象流行度分布(Zipf 拟合) | +| `ttl` | `False` | TTL 分布(仅对带 TTL 的 trace 有意义) | +| `popularity_decay` | `False` | 对象流行度如何随时间衰减 | +| `lifetime` | `False` | 对象生命周期分布 | +| `create_future_reuse_ccdf` | `False` | 实验性——未来复用的 CCDF | +| `prob_at_age` | `False` | 实验性——访问概率随年龄的变化 | +| `size_change` | `False` | 对象大小在多次访问间的变化情况 | + +各项分析彼此独立,因此在大 trace 上关掉不需要的分析可以显著加快运行速度: + +```python +analysis_option = lcs.AnalysisOption( + req_rate=True, # 保留基本的请求速率分析 + access_pattern=False, + size=True, # 保留大小分析 + reuse=False, + popularity=False, + ttl=False, + popularity_decay=False, + lifetime=False, + create_future_reuse_ccdf=False, + prob_at_age=False, + size_change=False, +) + +analyzer = lcs.TraceAnalyzer(reader, "example_analysis", analysis_option=analysis_option) +analyzer.run() +``` + +## 调节分析行为 + +`AnalysisParam` 控制已启用分析的具体行为: + +| 参数 | 默认值 | 含义 | +|---|---|---| +| `access_pattern_sample_ratio_inv` | `10` | 访问模式分析的采样率倒数——取值为 `n` 时大约保留 `1/n` 的数据 | +| `track_n_popular` | `10` | 统计并报告请求数的最流行对象个数 | +| `track_n_hit` | `5` | 追踪多少个 "X-hit wonder" 分桶,即恰好被访问 1 次、2 次……直到 `track_n_hit` 次的对象数量 | +| `time_window` | `60` | 时间序列输出所用分桶的宽度,单位为秒 | +| `warmup_time` | `0` | 开始统计前跳过的 trace 秒数 | + +```python +analysis_param = lcs.AnalysisParam( + track_n_popular=4, + track_n_hit=4, + time_window=300, +) + +analyzer = lcs.TraceAnalyzer( + reader, "example_analysis", + analysis_option=analysis_option, + analysis_param=analysis_param, +) +analyzer.run() +``` + +!!! warning + 有两个约束很容易踩坑: + + - `warmup_time` 必须是 `time_window` 的整数倍,否则分析器会直接报错,因为流行度衰减的计算依赖这一关系。 + - 流行度分析和复用分析需要足够大的工作集才能给出有意义的结果。在很小的 trace 上——只有寥寥几个不同对象时——请把 `track_n_popular` 和 `track_n_hit` 设为不超过对象总数,或者干脆关闭 `popularity` 和 `reuse`。 + +## 分析结果 + +`run()` 会写出纯文本的结果文件,它们都以 `output_path` 为前缀。每项启用的分析至少产出一个文件——大小分析写出 `example_analysis.size`,某些分析还会额外输出按时间窗口划分的变体,例如 `example_analysis.sizeWindow_w60_req`。 + +```python +with open("example_analysis.size") as f: + print(f.read()) +``` + +本次运行的汇总信息——trace 路径、请求数与对象数、强制缺失率、平均对象大小、平均访问频率、时间跨度,以及 X-hit wonder 和流行度直方图——会写入**当前工作目录**下名为 `stat` 的文件。注意该路径是固定的,并非由 `output_path` 推导而来,而且分析器是以*追加*方式写入,因此多次运行的结果会累积在同一个文件里。 + +用完之后,可以调用 `cleanup()` 释放分析器的内部状态: + +```python +analyzer.cleanup() +``` + +## 工作集大小 + +对于最常用的那个统计量——trace 一共触及了多少数据——你根本不需要分析器。`TraceReader` 直接就能给出: + +```python +n_obj, n_byte = reader.get_working_set_size() +print(f"{n_obj} unique objects, {n_byte} bytes") +``` + +按比例设置的 `cache_size` 也正是以此为基准计算的,参见[缓存模拟](simulation.md#cache-size-as-a-ratio)。 diff --git a/docs/src/zh/examples/plugins.md b/docs/src/zh/examples/plugins.md new file mode 100644 index 0000000..a3ea8e1 --- /dev/null +++ b/docs/src/zh/examples/plugins.md @@ -0,0 +1,57 @@ +# 插件系统 + +## PluginCache + +我们允许用户通过 libCacheSim 的插件系统添加任意自定义缓存。 + +借助用户自定义的 Python hook 函数, + +```c++ + py::function cache_init_hook; + py::function cache_hit_hook; + py::function cache_miss_hook; + py::function cache_eviction_hook; + py::function cache_remove_hook; + py::function cache_free_hook; +``` + +我们可以完全从 Python 一侧模拟并决定缓存的淘汰行为。 + +这些 hook 函数的签名要求如下。 +```python +def cache_init_hook(ccparams: CommonCacheParams) -> CustomizedCacheData: ... +def cache_hit_hook(data: CustomizedCacheData, req: Request) -> None: ... +def cache_miss_hook(data: CustomizedCacheData, req: Request) -> None: ... +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) ->: ... +``` + +- **注意:** `CustomizedCacheData` 并不是本库提供的类型。它只是表示用户自行决定从 `cache_init_hook` 返回、并作为 `data` 传给其他 hook 函数的那个对象。 + +## PluginAdmissioner + +我们允许用户通过 libCacheSim 的插件系统定义自己的准入策略,并将其与已有的缓存实现(如 `LRU`、`S3FIFO`)配合使用。 + +借助用户自定义的 Python hook 函数: + +```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; +``` + +我们可以在 Python 中方便地完全掌控哪些对象被准入底层缓存。 + +这些 hook 函数的签名要求如下。 +```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: ... +``` + +- **注意:** `CustomizedAdmissionerData` 并不是本库提供的类型。它只是表示用户自行决定从 `admissioner_init_hook` 返回、并作为 `data` 传给其他 hook 函数的那个对象。 diff --git a/docs/src/zh/examples/reader.md b/docs/src/zh/examples/reader.md new file mode 100644 index 0000000..14ed599 --- /dev/null +++ b/docs/src/zh/examples/reader.md @@ -0,0 +1,56 @@ +# Trace Reader + +我们提供了统一的 trace reader,用于打开不同格式的 trace 文件并读取其中的请求。 + +## 基本用法 + +`TraceReader` 类是这一功能的核心。创建 `TraceReader` 实例时,就会打开一个 trace 文件以便读取请求。 + +`TraceReader` 接受三个参数: + +- `trace: str | TraceReader`:trace 路径或另一个 trace 实例。trace 路径可以是本机上的文件路径(例如 ~/data/trace.oracleGeneral.zst),也可以是 S3 URI(例如 s3://cache-datasets/cache_dataset_oracleGeneral/2007_msr/msr_hm_0.oracleGeneral.zst)。 +- `trace_type: TraceType`(可选):若不指定,将根据文件名推断。 +- `reader_init_params: ReaderInitParam`(可选):若不指定,将使用默认参数初始化 reader。 + +下面是通过 S3 URI 加载一条 trace 的例子。 + +```python +import libcachesim as lcs + +# 打开一条托管在 S3 上的 trace(更多 trace 见 https://github.com/cacheMon/cache_dataset) +URI = "s3://cache-datasets/cache_dataset_oracleGeneral/2007_msr/msr_hm_0.oracleGeneral.zst" +reader = lcs.TraceReader( + trace = URI, + trace_type = lcs.TraceType.ORACLE_GENERAL_TRACE, + reader_init_params = lcs.ReaderInitParam(ignore_obj_size=False) +) +``` + +然后就可以遍历整条 trace。 + +```python +for req in reader: + print(req.obj_id, req.obj_size) +``` + +## Reader 切片 + +`TraceReader` 支持切片和按下标访问。 + +```python +# 读取前 100 条请求 +for req in reader[:100]: + print(req.obj_id, req.obj_size) +``` + +```python +# 读取前 100 条之后的 100 条请求 +for req in reader[100:200]: + print(req.obj_id, req.obj_size) +``` + +```python +# 读取最后 100 条请求 +for req in reader[-100:]: + print(req.obj_id, req.obj_size) +``` diff --git a/docs/src/zh/examples/simulation.md b/docs/src/zh/examples/simulation.md new file mode 100644 index 0000000..abdcf80 --- /dev/null +++ b/docs/src/zh/examples/simulation.md @@ -0,0 +1,326 @@ +# 缓存模拟 + +## 基本用法 + +缓存类是缓存模拟的核心。创建一个缓存实例(例如 `LRU`、`S3FIFO`)时,我们可以配置缓存大小以及该算法特有的参数,比如晋升阈值。 + +```py +import libcachesim as lcs + +# 初始化缓存 +cache = lcs.S3FIFO( + cache_size=1024 * 1024, + # 算法特有参数 + small_size_ratio=0.2, + ghost_size_ratio=0.8, + move_to_main_threshold=2, +) +``` + +准入策略是可选的——如果不提供,缓存会直接按替换策略接纳所有对象。通过 `admissioner` 参数,可以在缓存前面放置一个准入器(例如 `BloomFilterAdmissioner`)。 + +```py +import libcachesim as lcs + +# 初始化准入器 +admissioner = lcs.BloomFilterAdmissioner() + +# 第 2 步:初始化缓存 +cache = lcs.S3FIFO( + cache_size=1024 * 1024, + # 算法特有参数 + small_size_ratio=0.2, + ghost_size_ratio=0.8, + move_to_main_threshold=2, + # 可选地提供准入器 + admissioner=admissioner, +) +``` + +然后就可以借助 trace reader 用真实世界的负载运行缓存模拟(关于 `TraceReader` 的更多用法见 [Trace Reader](reader.md)): + +```py +# 高效处理整条 trace(C++ 后端) +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}") +``` + +`process_trace` 还接受另外两个参数,用于把回放限制在 trace 的一部分上: + +```py +# 跳过前 10000 条请求,然后处理接下来的 1000 条 +req_miss_ratio, byte_miss_ratio = cache.process_trace(reader, start_req=10_000, max_req=1_000) +``` + +- `start_req: int` —— 第一条待处理请求的下标(默认:`0`) +- `max_req: int` —— 最多处理多少条请求;`-1` 表示整条 trace(默认:`-1`) + +!!! note + `process_trace` 会在回放前把 reader 倒回起点,所以你不需要自己调用 `reset()`。但*缓存*本身会跨多次调用保留状态——每测一种配置就新建一个缓存,而不要复用已经被上一轮预热过的缓存。 + +## 按比例设置缓存大小 {#cache-size-as-a-ratio} + +`cache_size` 既接受绝对字节数(`int`),也接受 trace 工作集的一个比例(`float`)。float 必须落在 `(0, 1]` 区间内,并且需要提供 `reader` 参数,因为要用它来调用 `reader.get_working_set_size()`: + +```py +# trace 总工作集大小(字节)的 10% +cache = lcs.S3FIFO( + cache_size=0.1, + reader=reader, # cache_size 为 float 时必须提供 +) +``` + +传入 float 却不给 `reader`,或者 float 超出 `(0, 1]`,都会抛出 `ValueError`。注意 `1024` 和 `1024.0` 因此含义截然不同——前者是 1 KiB,后者会被拒绝。 + +## 逐请求操作 {#working-with-individual-requests} + +`process_trace` 把整个回放过程放在 C++ 后端执行,是速度最快的方式。当你需要逐条请求进行观察或干预时,`CacheBase` 也暴露了底层操作: + +```py +for req in reader: + hit = cache.get(req) # 查找,缺失时插入(必要时触发淘汰) + if not hit: + print(f"miss on {req.obj_id}, cache now holds {cache.get_n_obj()} objects") +``` + +| 方法 | 说明 | +|---|---| +| `get(req)` | 完整的请求路径:查找 `req`,缺失时插入,必要时淘汰。命中返回 `True`。 | +| `find(req, update_cache=True)` | 查找对象,但缺失时不插入。设 `update_cache=False` 可做无副作用的探测。 | +| `can_insert(req)` | 该对象是否会被准入。 | +| `insert(req)` | 直接插入对象,不检查空间。 | +| `need_eviction(req)` | 插入 `req` 是否需要先淘汰。 | +| `to_evict(req)` | 返回下一个将被淘汰的对象,但并不真的淘汰它。 | +| `evict(req)` | 按策略淘汰一个对象。 | +| `remove(obj_id)` | 移除指定对象。若该对象不在缓存中则返回 `False`。 | +| `get_occupied_byte()` | 当前已占用的字节数。 | +| `get_n_obj()` | 当前缓存中的对象个数。 | +| `set_cache_size(new_size)` | 原地调整缓存大小。 | +| `print_cache()` | 返回描述当前缓存状态的字符串,调试时很有用。 | + +`cache_size` 和 `cache_name` 属性是只读的。 + +## 缓存算法 {#caches} + +下面这些缓存类都继承自 `CacheBase` 并共享同一套接口。除非另有说明,所有缓存类都接受以下公共参数: + +- `cache_size: int | float` —— 缓存大小(字节),或工作集的一个比例(见[上文](#cache-size-as-a-ratio)) +- `default_ttl: int`(可选)—— 默认 TTL,单位为秒(默认:`25920000`,即 300 天) +- `hashpower: int`(可选)—— 初始哈希表大小的以 2 为底的对数(默认:`24`) +- `consider_obj_metadata: bool`(可选)—— 每个对象的缓存元数据是否计入缓存大小(默认:`False`) +- `admissioner: AdmissionerBase`(可选)—— 置于缓存之前的准入策略(默认:`None`) +- `reader: ReaderProtocol`(可选)—— 仅当 `cache_size` 为比例时才需要(默认:`None`) + +### LHD +**Least Hit Density(最小命中密度)** 根据每个对象的单位空间期望命中数(命中密度)进行淘汰。 + +- *除公共参数外无额外参数* + +### LRU +**Least Recently Used(最近最少使用)** 淘汰最长时间未被访问的对象。 + +- *除公共参数外无额外参数* + +### FIFO +**First-In, First-Out(先进先出)** 按进入顺序淘汰对象,不考虑访问频率和时间局部性。 + +- *除公共参数外无额外参数* + +### LFU +**Least Frequently Used(最不经常使用)** 淘汰访问频率最低的对象。 + +- *除公共参数外无额外参数* + +### ARC +**Adaptive Replacement Cache(自适应替换缓存)** 是一种在时间局部性和访问频率之间取得平衡的混合算法。 + +- *除公共参数外无额外参数* + +### Clock +**Clock** 是 `LRU` 的一种低复杂度近似。 + +- `init_freq: int` —— 新对象的初始频率计数值(默认:`0`) +- `n_bit_counter: int` —— 频率计数器使用的位数(默认:`1`) + +### Random +**Random** 随机淘汰对象。 + +- *除公共参数外无额外参数* + +### S3FIFO +**Simple, Scalable FIFO** 把缓存拆分为两部分:一个用于新准入对象的小 FIFO 队列,以及一个用于已证明较热对象的主 FIFO 队列,同时用一个 ghost 队列记录最近被淘汰对象的标识。只被访问一次的对象(one-hit wonder)会很快被挤出小队列,而不会污染主队列。 + +- `small_size_ratio: float` —— 分配给小队列的缓存比例(默认:`0.1`) +- `ghost_size_ratio: float` —— ghost 队列大小占缓存的比例(默认:`0.9`) +- `move_to_main_threshold: int` —— 对象在小队列中被访问多少次后晋升到主队列(默认:`2`) + +### Sieve +**Sieve** 在一个 FIFO 队列上移动指针,淘汰第一个 visited 位未被置位的对象,同时清除途经对象的该位。它在保持 FIFO 简洁性的同时达到接近 LRU 的缺失率,且命中时不做任何晋升操作。 + +- *除公共参数外无额外参数* + +### LIRS +**Low Inter-reference Recency Set** 按对象倒数第二次访问(而非最后一次访问)的近期程度来排序,从而能把真正的热点对象与扫描过程中只被碰过一次的对象区分开。 + +- *除公共参数外无额外参数* + +### TwoQ +**2Q** 把新对象先放入一个 FIFO 队列(`Ain`),只有当对象的标识还在 ghost 队列(`Aout`)中时又被再次访问,才会晋升进入 LRU 主队列。 + +- `a_in_size_ratio: float` —— `Ain` 队列大小占缓存的比例(默认:`0.25`) +- `a_out_size_ratio: float` —— `Aout` ghost 队列大小占缓存的比例(默认:`0.5`) + +### SLRU +**Segmented LRU(分段 LRU)** 把缓存划分为若干有序的 LRU 分段;对象每命中一次就晋升一段,并随着新对象到来逐步向淘汰端降级。 + +- *除公共参数外无额外参数* + +### WTinyLFU +**Window TinyLFU** 在一个较大的主缓存前面放置一个小的 LRU 窗口,并用频率草图(frequency sketch)来判断离开窗口的对象是否值得挤掉主缓存中的淘汰候选者。 + +- `main_cache: str` —— 主缓存所用的淘汰算法(默认:`"SLRU"`) +- `window_size: float` —— LRU 窗口大小占主缓存的比例(默认:`0.01`) + +### LeCaR +**Learning Cache Replacement** 同时维护一个 LRU 候选者和一个 LFU 候选者,并用基于后悔最小化(regret minimisation)更新的权重在二者之间做选择,从而能随着负载在偏时间局部性与偏频率之间切换而自适应调整。 + +- `update_weight: bool` —— 回放过程中是否持续学习权重(默认:`True`) +- `lru_weight: float` —— 选择 LRU 候选者的初始概率;LFU 权重为 `1 - lru_weight`(默认:`0.5`) + +### LFUDA +**LFU with Dynamic Aging(带动态老化的 LFU)** 在 `LFU` 基础上,每次访问时给对象优先级加上一个全局年龄值,这样很久以前流行的对象最终会老化淘汰,而不会长期占住缓存。 + +- *除公共参数外无额外参数* + +### ClockPro +**CLOCK-Pro** 用 CLOCK 指针近似 `LIRS`,同时追踪热页和冷页,并为最近被淘汰的冷页设置一个试用期。 + +- `init_ref: int` —— 新准入对象的初始引用计数(默认:`0`) +- `init_ratio_cold: float` —— 缓存中初始被划为冷页的比例(默认:`0.5`) + +### Cacheus +**Cacheus** 在 `LeCaR` 的基础上增加了对学习率的轻量自适应以及扫描/抖动检测,使其在 `LeCaR` 表现不佳的负载上退化得更平缓。 + +- *除公共参数外无额外参数* + +### Belady +**Belady's MIN** 是最优的离线策略:它淘汰下一次访问时间最远的对象。该策略无法在线实现,其存在意义是作为可达缺失率的下界。 + +- *除公共参数外无额外参数* + +!!! important + `Belady` 和 `BeladySize` 会读取 `req.next_access_vtime`,只有 oracle trace 才带有这一字段。请使用 `ORACLE_GENERAL_TRACE` 格式的 trace(如本页各示例所示);在普通 trace 上该未来访问字段缺失,结果没有意义。 + +### BeladySize +**Size-aware Belady** 把 `Belady` 扩展到对象大小可变的场景,在一批候选对象的采样中综合考虑下次访问时间和对象大小来选择淘汰目标。 + +- `n_samples: int` —— 选择淘汰对象时采样的对象个数(默认:`128`) + +### LRUProb +**LRU with Probabilistic Promotion(概率晋升的 LRU)** 行为与 `LRU` 类似,但对象只以 `prob` 的概率被移到队首。取值越低,行为越接近 `FIFO`,晋升开销也越小。 + +- `prob: float` —— 命中时晋升对象的概率(默认:`0.5`) + +### FlashProb +**FlashProb** 建模 RAM + 闪存的两级缓存,只以一定概率把从 RAM 淘汰的对象写入闪存层,从而限制闪存设备上的写放大。 + +- `ram_size_ratio: float` —— RAM 层大小占总缓存的比例(默认:`0.05`) +- `disk_admit_prob: float` —— 对象被准入磁盘层的概率(默认:`0.2`) +- `ram_cache: str` —— RAM 层所用的淘汰算法(默认:`"LRU"`) +- `disk_cache: str` —— 磁盘层所用的淘汰算法(默认:`"FIFO"`) + +### Size +**Size** 优先淘汰最大的对象,从而最大化保留的对象数量。在对象大小差异很大的负载上适合作为基线。 + +- *除公共参数外无额外参数* + +### GDSF +**GreedyDual-Size with Frequency** 按频率除以大小对对象排序,并叠加一个全局老化因子,从而偏好体积小且访问频繁的对象。 + +- *除公共参数外无额外参数* + +### Hyperbolic +**Hyperbolic** 每次淘汰时采样若干对象,淘汰其中访问次数除以在缓存中驻留时间最小的那个,从而在不维护全局结构的情况下近似出一个优先级排序。 + +- *除公共参数外无额外参数* + +### ThreeLCache +**3LCache** 是一种学习型策略,它预测每个对象的保留价值,并据此把对象组织在三个层级中。 + +- `objective: str` —— 学习模型优化的目标指标(默认:`"byte-miss-ratio"`) + +!!! warning + 需要以 `-DENABLE_3L_CACHE=ON` 构建。参见[安装指南](../getting_started/installation.md#optional-eviction-algorithms)。在未开启该选项的构建中构造它会抛出 `ImportError`。 + +### GLCache +**Group-Learned Cache** 把对象分组为 segment,学习预测每个 segment 的未来价值,并通过合并价值最低的 segment 来完成淘汰,而不是逐个对象做决策。 + +- `segment_size: int` —— 每个 segment 包含的对象数(默认:`100`) +- `n_merge: int` —— 单次淘汰中合并的 segment 数(默认:`2`) +- `type: str` —— 缓存类型,例如学习型变体或某个基线(默认:`"learned"`) +- `rank_intvl: float` —— segment 重新排序的频率,以缓存的比例表示(默认:`0.02`) +- `merge_consecutive_segs: bool` —— 合并是否限定在连续的 segment 之间(默认:`True`) +- `train_source_y: str` —— 训练标签的来源(默认:`"online"`) +- `retrain_intvl: int` —— 模型重训练的间隔秒数(默认:`86400`) + +!!! warning + 需要以 `-DENABLE_GLCACHE=ON` 构建。参见[安装指南](../getting_started/installation.md#optional-eviction-algorithms)。在未开启该选项的构建中构造它会抛出 `ImportError`。 + +### LRB +**Learning Relaxed Belady** 训练一个模型在线近似 Belady 的决策,淘汰那些被预测为下次访问较远的对象。 + +- `objective: str` —— 学习模型优化的目标指标(默认:`"byte-miss-ratio"`) + +!!! warning + 需要以 `-DENABLE_LRB=ON` 构建。参见[安装指南](../getting_started/installation.md#optional-eviction-algorithms)。在未开启该选项的构建中构造它会抛出 `ImportError`。 + +### PluginCache +**PluginCache** 让你无需编译,仅通过 hook 函数就能用纯 Python 实现淘汰策略。它在[插件系统](plugins.md)中单独介绍。 + +## 准入策略 {#admission-policies} + +### BloomFilterAdmissioner +使用布隆过滤器,根据对象被看到的次数来决定是否准入。 + +- *无参数* + +### ProbAdmissioner +以固定概率准入对象。 + +- `prob: float`(可选)—— 准入对象的概率(默认:`0.5`) + +### SizeAdmissioner +仅在对象大小低于指定阈值时才准入。 + +- `size_threshold: int`(可选)—— 允许准入的最大对象大小(字节)(默认:`9_223_372_036_854_775_807`,即 `INT64_MAX`) + +### SizeProbabilisticAdmissioner +以随对象增大而递减的概率准入对象,从而偏好小对象。 + +- `exponent: float`(可选)—— 控制过滤大对象力度的指数(默认:`1e-6`) + +### AdaptSizeAdmissioner +实现 **AdaptSize**,一种基于反馈、周期性调整大小阈值的策略。 + +- `max_iteration: int`(可选)—— 参数调优的最大迭代次数(默认:`15`) +- `reconf_interval: int`(可选)—— 重新评估阈值的间隔(以请求数计)(默认:`30_000`) + +### PluginAdmissioner +让你通过 hook 函数用 Python 实现准入策略。参见[插件系统](plugins.md#pluginadmissioner)。 + +## 对比不同算法 + +由于所有缓存都暴露相同的接口,遍历多个算法非常简单。`process_trace` 会在开始前把 reader 倒回起点,因此同一个 reader 可以直接交给每一轮运行,无需显式调用 `reset()`: + +```py +import libcachesim as lcs + +URI = "s3://cache-datasets/cache_dataset_oracleGeneral/2007_msr/msr_hm_0.oracleGeneral.zst" +reader = lcs.TraceReader(trace=URI, trace_type=lcs.TraceType.ORACLE_GENERAL_TRACE) + +CACHE_SIZE = 1024 * 1024 +for cls in (lcs.LRU, lcs.FIFO, lcs.ARC, lcs.S3FIFO, lcs.Sieve): + cache = cls(cache_size=CACHE_SIZE) + req_miss_ratio, byte_miss_ratio = cache.process_trace(reader) + print(f"{cache.cache_name:>10}: req {req_miss_ratio:.4f} byte {byte_miss_ratio:.4f}") +``` diff --git a/docs/src/zh/faq.md b/docs/src/zh/faq.md new file mode 100644 index 0000000..b8135a7 --- /dev/null +++ b/docs/src/zh/faq.md @@ -0,0 +1,21 @@ +# 常见问题 + +1. pip install 失败时如何解决? + + 请参阅[安装指南](getting_started/installation.md)。 + +2. 构建时出现类似 "cannot find Python package" 的错误提示。 + + 原因是构建 Python 绑定需要 Python 的开发头文件和库文件。 + + 如果你拥有管理员权限,可以使用系统的包管理器安装所需的软件包。例如: + + * **Debian/Ubuntu**:`sudo apt install python3-dev` + * **RHEL/CentOS/Fedora**:`sudo yum install python3-devel` + * **macOS**:通常用 Homebrew 安装 Python(`brew install python`)即可。 + + 另外,如果你把 Python 安装在了自定义位置,则需要设置环境变量来帮助构建系统找到它。请记得将下面命令中的占位符替换为你的实际路径。 + + ```shell + export CMAKE_ARGS="-DPython3_ROOT_DIR=${Python3_ROOT_DIR} -DPython3_INCLUDE_DIR=${Python3_INCLUDE_DIR} -DPython3_EXECUTABLE=${Python3_EXECUTABLE}" + ``` diff --git a/docs/src/zh/getting_started/installation.md b/docs/src/zh/getting_started/installation.md new file mode 100644 index 0000000..d3c6df0 --- /dev/null +++ b/docs/src/zh/getting_started/installation.md @@ -0,0 +1,102 @@ +# 安装 + +## 环境要求 + +| | | +|---|---| +| **操作系统** | Linux / macOS | +| **Python** | 3.10 -- 3.13 | +| **架构** | x86_64 / aarch64 | + +不支持 Windows。 + +## 从 PyPI 安装 + +我们已将预编译的 wheel 发布到 [PyPI](https://pypi.org/project/libcachesim/),因此大多数情况下无需编译器: + +```bash +pip install libcachesim +``` + +推荐使用 [uv](https://docs.astral.sh/uv/) 创建和管理环境: + +```bash +uv venv --python 3.12 --seed +source .venv/bin/activate +uv pip install libcachesim +``` + +验证安装结果: + +```bash +python -c "import libcachesim; print(libcachesim.__version__)" +``` + +## 可选的淘汰算法 {#optional-eviction-algorithms} + +有三个算法依赖第三方机器学习库,因此由 CMake 选项控制,且默认全部为 `OFF`: + +| 算法 | CMake 选项 | 依赖 | +|---|---|---| +| [`LRB`](../examples/simulation.md#lrb) | `ENABLE_LRB` | LightGBM | +| [`ThreeLCache`](../examples/simulation.md#threelcache) | `ENABLE_3L_CACHE` | LightGBM | +| [`GLCache`](../examples/simulation.md#glcache) | `ENABLE_GLCACHE` | XGBoost | + +!!! note + 发布到 PyPI 的 wheel 已启用全部三个算法。只有当没有匹配你平台的 wheel、pip 回退到从源码构建,或者你自己从源码检出进行构建时,才需要执行下面的步骤。 + +首先安装第三方依赖: + +```bash +git clone https://github.com/cacheMon/libCacheSim-python.git +cd libCacheSim-python +bash scripts/install_deps.sh + +# 如果你无法安装系统软件包(例如没有 sudo 权限) +bash scripts/install_deps_user.sh +``` + +然后通过 `CMAKE_ARGS` 传入选项重新安装。加上 `--no-cache-dir` 可以强制重新构建,而不是复用缓存的 wheel: + +```bash +# 启用单个算法 +CMAKE_ARGS="-DENABLE_LRB=ON" pip install libcachesim --no-cache-dir + +# 或者三个全部启用 +CMAKE_ARGS="-DENABLE_LRB=ON -DENABLE_3L_CACHE=ON -DENABLE_GLCACHE=ON" \ + pip install libcachesim --no-cache-dir +``` + +!!! important + 由于这些选项默认为 `OFF`,普通的源码构建会**静默地略过**这三个算法——此时构造 `LRB`、`ThreeLCache` 或 `GLCache` 会在运行时失败。反过来,如果在未安装依赖的情况下把选项打开,CMake 会在配置阶段直接报错(`LIGHTGBM_PATH not found`,或提示缺少 `xgboost` 包)。请先运行依赖安装脚本。 + +## 从源码安装 + +C 语言库 [libCacheSim](https://github.com/1a1a11a/libCacheSim) 以 git 子模块的形式引入,因此检出时必须递归拉取: + +```bash +git clone https://github.com/cacheMon/libCacheSim-python.git +cd libCacheSim-python +git submodule update --init --recursive +pip install . +``` + +`scripts/install.sh` 封装了整个流程——它会更新子模块、以可编辑模式安装本包、检查导入是否正常,并运行测试套件: + +```bash +bash scripts/install.sh + +# 同上,但启用全部可选算法 +bash scripts/install.sh --all +``` + +构建扩展需要支持 C++17 的编译器、CMake ≥ 3.15 以及 Ninja。构建过程由 [scikit-build-core](https://scikit-build-core.readthedocs.io/) 驱动,它会先配置并构建内置的 C 库,再编译 [pybind11](https://pybind11.readthedocs.io/) 绑定。 + +## 疑难排查 + +有两类失败足够常见,已在[常见问题](../faq.md)中单列条目: + +- `pip install` 找不到合适的 wheel,并且源码构建报错。 +- 构建时提示 `cannot find Python package`——缺少 Python 的开发头文件,或者 Python 安装在非标准位置。 + +其他问题请[提交 issue](https://github.com/cacheMon/libCacheSim-python/issues/new/choose)。 diff --git a/docs/src/zh/getting_started/quickstart.md b/docs/src/zh/getting_started/quickstart.md new file mode 100644 index 0000000..e3c49c6 --- /dev/null +++ b/docs/src/zh/getting_started/quickstart.md @@ -0,0 +1,194 @@ +# 快速开始 + +本指南将帮助你快速上手 libCacheSim。 + +## 前置条件 + +- 操作系统:Linux / macOS +- Python:3.10 -- 3.13 + +## 安装 + +你可以直接使用 [pip](https://pypi.org/project/libcachesim/) 安装 libCacheSim。 + +我们推荐使用 [uv](https://docs.astral.sh/uv/)——一个非常快的 Python 环境管理器——来创建和管理 Python 环境。请参照其[官方文档](https://docs.astral.sh/uv/#getting-started)安装 `uv`。装好 `uv` 之后,用下面的命令创建新环境并安装 libCacheSim: + +```bash +uv venv --python 3.12 --seed +source .venv/bin/activate +uv pip install libcachesim +``` + +如需从源码构建,或需要启用 LRB、ThreeLCache 和 GLCache 这三个在源码构建中默认被排除的淘汰算法,请参阅[安装指南](installation.md)。 + +## 缓存模拟 + +装好 libcachesim 之后,你就可以针对某个淘汰算法和缓存 trace 开始模拟了。示例脚本如下: + +??? code + ```python + import libcachesim as lcs + + # 第 1 步:打开一条托管在 S3 上的 trace(更多 trace 见 https://github.com/cacheMon/cache_dataset) + URI = "s3://cache-datasets/cache_dataset_oracleGeneral/2007_msr/msr_hm_0.oracleGeneral.zst" + reader = lcs.TraceReader( + trace = URI, + trace_type = lcs.TraceType.ORACLE_GENERAL_TRACE, + reader_init_params = lcs.ReaderInitParam(ignore_obj_size=False) + ) + + # 第 2 步:初始化缓存 + cache = lcs.S3FIFO( + cache_size=1024*1024, + # 算法特有参数 + small_size_ratio=0.2, + ghost_size_ratio=0.8, + move_to_main_threshold=2, + ) + + # 第 3 步:高效处理整条 trace(C++ 后端) + 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}") + + # 第 3.1 步:只处理前 1000 条请求 + cache = lcs.S3FIFO( + cache_size=1024 * 1024, + # 算法特有参数 + small_size_ratio=0.2, + ghost_size_ratio=0.8, + move_to_main_threshold=2, + ) + req_miss_ratio, byte_miss_ratio = cache.process_trace(reader, start_req=0, max_req=1000) + print(f"Request miss ratio: {req_miss_ratio:.4f}, Byte miss ratio: {byte_miss_ratio:.4f}") + ``` + +上面的例子展示了用 `libcachesim` 做缓存模拟的基本流程: + +1. 用 `TraceReader` 打开并高效处理 trace 文件。 +2. 初始化一个缓存对象(这里是 `S3FIFO`),并指定缓存大小(例如 1MB)。 +3. 用 `process_trace` 在整条 trace 上运行模拟,得到对象缺失率和字节缺失率。 +4. 也可以通过 `start_req` 和 `max_req` 只处理 trace 的一部分,做局部模拟。 + +这套流程适用于大多数缓存算法和 trace 类型,既容易上手,也便于自定义实验。 + +### 按 trace 比例设置缓存大小 + +在比较规模差异很大的多条 trace 时,使用绝对字节数会很不方便。此时可以把 `cache_size` 设为 `(0, 1]` 区间内的 `float`,它会被解释为 trace 工作集的一个比例,但这要求同时把 `reader` 传给缓存: + +```python +cache = lcs.S3FIFO( + cache_size=0.1, # trace 工作集大小(字节)的 10% + reader=reader, # cache_size 为 float 时必须提供 +) +``` + +`int` 始终表示绝对字节数,所以 `1024` 表示 1 KiB,而 `1024.0` 超出取值范围,会抛出 `ValueError`。 + +## Trace 分析 + +下面这个例子演示了 `TraceAnalyzer` 的用法。 + +??? code + ```python + import libcachesim as lcs + + # 第 1 步:从 S3 存储桶获取一条 trace + URI = "s3://cache-datasets/cache_dataset_oracleGeneral/2007_msr/msr_hm_0.oracleGeneral.zst" + reader = lcs.TraceReader( + trace = URI, + trace_type = lcs.TraceType.ORACLE_GENERAL_TRACE, + reader_init_params = lcs.ReaderInitParam(ignore_obj_size=False) + ) + + analysis_option = lcs.AnalysisOption( + req_rate=True, # 保留基本的请求速率分析 + access_pattern=False, # 关闭访问模式分析 + size=True, # 保留大小分析 + reuse=False, # 小数据集上关闭复用分析 + popularity=False, # 小数据集(少于 200 个对象)上关闭流行度分析 + ttl=False, # 关闭 TTL 分析 + popularity_decay=False, # 关闭流行度衰减分析 + lifetime=False, # 关闭生命周期分析 + create_future_reuse_ccdf=False, # 关闭实验性功能 + prob_at_age=False, # 关闭实验性功能 + size_change=False, # 关闭大小变化分析 + ) + + analysis_param = lcs.AnalysisParam() + + analyzer = lcs.TraceAnalyzer( + reader, "example_analysis", analysis_option=analysis_option, analysis_param=analysis_param + ) + + analyzer.run() + ``` + +上面的代码演示了如何用 `libcachesim` 进行 trace 分析,流程如下: + +1. 用 `TraceReader` 打开 trace 文件,指定 trace 类型和所需的 reader 初始化参数。以 `s3://` 开头的 URI 会自动从 S3 存储桶下载 trace 文件。 +2. 用 `AnalysisOption` 配置分析项,开启或关闭特定分析(如请求速率、对象大小等)。 +3. 可选地用 `AnalysisParam` 设置额外的分析参数。 +4. 用 reader、输出目录以及选定的选项和参数创建 `TraceAnalyzer` 对象。 +5. 调用 `analyzer.run()` 运行分析。 + +运行结束后,你就可以查看分析结果,例如汇总统计(`stat`)或详细结果(如 `example_analysis.size`)。 + +## 插件系统 + +libCacheSim 还允许用户开发自己的缓存淘汰算法,并通过插件系统进行测试。 + +下面是通过插件系统实现 `LRU` 的例子。 + +??? code + ```python + from collections import OrderedDict + from typing import Any + + from libcachesim import PluginCache, LRU, CommonCacheParams, Request, SyntheticReader + + def init_hook(_: CommonCacheParams) -> Any: + return OrderedDict() + + def hit_hook(data: Any, req: Request) -> None: + data.move_to_end(req.obj_id, last=True) + + def miss_hook(data: Any, req: Request) -> None: + data.__setitem__(req.obj_id, req.obj_size) + + def eviction_hook(data: Any, _: Request) -> int: + return data.popitem(last=False)[0] + + def remove_hook(data: Any, obj_id: int) -> None: + data.pop(obj_id, None) + + def free_hook(data: Any) -> None: + data.clear() + + + plugin_lru_cache = PluginCache( + cache_size=128, + cache_init_hook=init_hook, + cache_hit_hook=hit_hook, + cache_miss_hook=miss_hook, + cache_eviction_hook=eviction_hook, + cache_remove_hook=remove_hook, + cache_free_hook=free_hook, + cache_name="Plugin_LRU", + ) + + reader = SyntheticReader(num_objects=1000, num_of_req=10000, obj_size=1) + req_miss_ratio, byte_miss_ratio = plugin_lru_cache.process_trace(reader) + ref_req_miss_ratio, ref_byte_miss_ratio = LRU(128).process_trace(reader) + print(f"plugin req miss ratio {req_miss_ratio}, ref req miss ratio {ref_req_miss_ratio}") + print(f"plugin byte miss ratio {byte_miss_ratio}, ref byte miss ratio {ref_byte_miss_ratio}") + ``` + +只要为缓存的初始化、命中、缺失、淘汰、移除和清理定义好自定义 hook 函数,用户就能轻松地对自己的缓存淘汰算法做原型验证和测试。 + +## 下一步 + +- [Trace Reader](../examples/reader.md)——打开本地和 S3 上的 trace、切片与遍历 +- [缓存模拟](../examples/simulation.md)——所有淘汰算法与准入策略及其参数 +- [Trace 分析](../examples/analysis.md)——用 `TraceAnalyzer` 刻画负载特征 +- [插件系统](../examples/plugins.md)——自定义缓存与准入策略的 hook 签名 +- [API 参考](../api.md)——完整的对外接口 diff --git a/docs/src/zh/index.md b/docs/src/zh/index.md index 997399a..d8763ec 100644 --- a/docs/src/zh/index.md +++ b/docs/src/zh/index.md @@ -1,68 +1,35 @@ -# libCacheSim Python 绑定 +# 欢迎使用 libCacheSim Python -欢迎使用 libCacheSim Python 绑定!这是一个高性能的缓存模拟库,提供了 Python 接口。 +!!! note + 为方便起见,下文将 *libCacheSim Python 包*(本仓库)简称为 *libCacheSim*,将其底层的 *C 语言库* 称为 *libCacheSim lib*。 -## 概述 +
+ ![](../assets/logos/logo.jpg){ align="center" alt="libCacheSim Light" class="logo-light" width="60%" } +
-libCacheSim 是一个高性能的缓存模拟框架,支持各种缓存算法和跟踪格式。Python 绑定为缓存模拟、分析和研究提供了易于使用的接口。 +

+一个用于构建和运行缓存模拟的高性能库 + +

-## 主要特性 +

+ +Star +Watch +Fork +

-- **高性能**: 基于优化的 C++ libCacheSim 库构建 -- **多种缓存算法**: 支持 LRU、LFU、FIFO、ARC、Clock、S3FIFO、Sieve 等多种算法 -- **跟踪支持**: 读取各种跟踪格式(CSV、二进制、OracleGeneral 等) -- **合成跟踪**: 生成 Zipf 和均匀分布的合成工作负载 -- **分析工具**: 内置跟踪分析和缓存性能评估 -- **易于集成**: 简单的 Python API,适用于研究和生产环境 +libCacheSim 是 [libCacheSim lib](https://github.com/1a1a11a/libCacheSim) 的 Python 绑定,简单易用,可用于构建和运行缓存模拟。 -## 快速示例 +得益于[底层的 libCacheSim lib](https://github.com/1a1a11a/libCacheSim),libCacheSim 速度很快: -```python -import libcachesim as lcs +- 高性能——真实 trace 回放可达每秒 2000 万条以上请求。 +- 高内存效率——内存占用小且可预测。 +- 开箱即用的并行能力——利用多核 CPU 加速 trace 分析与缓存模拟。 -# 创建缓存 -cache = lcs.LRU(cache_size=1024*1024) # 1MB 缓存 +libCacheSim 同时灵活易用: -# 生成合成跟踪 -reader = lcs.SyntheticReader( - num_of_req=10000, - obj_size=1024, - dist="zipf", - alpha=1.0 -) - -# 模拟缓存行为 -hit_count = 0 -for req in reader: - if cache.get(req): - hit_count += 1 - -hit_ratio = hit_count / reader.get_num_of_req() -print(f"命中率: {hit_ratio:.4f}") -``` - -## 安装 - -```bash -pip install libcachesim -``` - -或从源码安装: - -```bash -git clone https://github.com/cacheMon/libCacheSim-python.git -cd libCacheSim-python -pip install -e . -``` - -## 快速开始 - -查看我们的[快速开始指南](quickstart.md)开始使用 libCacheSim Python 绑定,或浏览 [API 参考](api.md)获取详细文档。 - -## 贡献 - -我们欢迎贡献!请查看我们的 [GitHub 仓库](https://github.com/cacheMon/libCacheSim-python)了解更多信息。 - -## 许可证 - -本项目采用 GPL-3.0 许可证。 +- 与[开源缓存数据集](https://github.com/cacheMon/cache_dataset)无缝集成,该数据集在 S3 上托管了数千条 trace。 +- 基于[底层 libCacheSim lib](https://github.com/1a1a11a/libCacheSim) 的高吞吐模拟。 +- 可细粒度控制缓存请求及其他内部数据。 +- 无需任何编译即可开发自定义的插件缓存。 diff --git a/docs/src/zh/quickstart.md b/docs/src/zh/quickstart.md deleted file mode 100644 index fbdc7f6..0000000 --- a/docs/src/zh/quickstart.md +++ /dev/null @@ -1,183 +0,0 @@ -# 快速开始指南 - -本指南将帮助您开始使用 libCacheSim Python 绑定。 - -## 安装 - -### 从 PyPI 安装(推荐) - -```bash -pip install libcachesim -``` - -### 从源码安装 - -```bash -git clone https://github.com/cacheMon/libCacheSim-python.git -cd libCacheSim-python -git submodule update --init --recursive -pip install -e . -``` - -## 基本用法 - -### 1. 创建缓存 - -```python -import libcachesim as lcs - -# 创建不同类型的缓存 -lru_cache = lcs.LRU(cache_size=1024*1024) # 1MB LRU 缓存 -lfu_cache = lcs.LFU(cache_size=1024*1024) # 1MB LFU 缓存 -fifo_cache = lcs.FIFO(cache_size=1024*1024) # 1MB FIFO 缓存 -``` - -### 2. 使用合成跟踪 - -```python -# 生成 Zipf 分布的请求 -reader = lcs.SyntheticReader( - num_of_req=10000, - obj_size=1024, - dist="zipf", - alpha=1.0, - num_objects=1000, - seed=42 -) - -# 模拟缓存行为 -cache = lcs.LRU(cache_size=50*1024) -hit_count = 0 - -for req in reader: - if cache.get(req): - hit_count += 1 - -print(f"命中率: {hit_count/reader.get_num_of_req():.4f}") -``` - -### 3. 读取真实跟踪 - -```python -# 读取 CSV 跟踪 -reader = lcs.TraceReader( - trace="path/to/trace.csv", - trace_type=lcs.TraceType.CSV_TRACE, - has_header=True, - delimiter=",", - obj_id_is_num=True -) - -# 处理请求 -cache = lcs.LRU(cache_size=1024*1024) -for req in reader: - result = cache.get(req) - # 处理结果... -``` - -### 4. 缓存性能分析 - -```python -# 运行综合分析 -analyzer = lcs.TraceAnalyzer(reader, "output_prefix") -analyzer.run() - -# 这会生成各种分析文件: -# - 命中率曲线 -# - 访问模式分析 -# - 时间局部性分析 -# - 等等... -``` - -## 可用的缓存算法 - -libCacheSim 支持众多缓存算法: - -### 基础算法 -- **LRU**: 最近最少使用 -- **LFU**: 最不经常使用 -- **FIFO**: 先进先出 -- **Clock**: 时钟算法 -- **Random**: 随机替换 - -### 高级算法 -- **ARC**: 自适应替换缓存 -- **S3FIFO**: 简单、快速、公平的 FIFO -- **Sieve**: Sieve 驱逐算法 -- **TinyLFU**: 带准入控制的 Tiny LFU -- **TwoQ**: 双队列算法 -- **LRB**: 学习松弛 Belady - -### 实验性算法 -- **3LCache**: 三级缓存 -- **等等...** - -## 跟踪格式 - -支持的跟踪格式包括: - -- **CSV**: 逗号分隔值 -- **Binary**: 自定义二进制格式 -- **OracleGeneral**: Oracle 通用格式 -- **Vscsi**: VMware vSCSI 格式 -- **等等...** - -## 高级功能 - -### 自定义缓存策略 - -您可以使用 Python 钩子实现自定义缓存策略: - -```python -from collections import OrderedDict - -def create_custom_lru(): - def init_hook(cache_size): - return OrderedDict() - - def hit_hook(cache_dict, obj_id, obj_size): - cache_dict.move_to_end(obj_id) - - def miss_hook(cache_dict, obj_id, obj_size): - cache_dict[obj_id] = obj_size - - def eviction_hook(cache_dict, obj_id, obj_size): - if cache_dict: - cache_dict.popitem(last=False) - - return lcs.PythonHookCache( - cache_size=1024*1024, - init_hook=init_hook, - hit_hook=hit_hook, - miss_hook=miss_hook, - eviction_hook=eviction_hook - ) - -custom_cache = create_custom_lru() -``` - -### 跟踪采样 - -```python -# 空间采样 10% 的请求 -reader = lcs.TraceReader( - trace="large_trace.csv", - trace_type=lcs.TraceType.CSV_TRACE, - sampling_ratio=0.1, - sampling_type=lcs.SamplerType.SPATIAL_SAMPLER -) -``` - -### 多线程分析 - -```python -# 使用多线程进行分析 -analyzer = lcs.TraceAnalyzer(reader, "output", n_threads=4) -analyzer.run() -``` - -## 下一步 - -- 探索 [API 参考](api.md) 获取详细文档 -- 查看[使用示例](examples.md)了解更复杂的用例 -- 访问我们的 [GitHub 仓库](https://github.com/cacheMon/libCacheSim-python) 获取源码和问题报告 diff --git a/libcachesim/__init__.pyi b/libcachesim/__init__.pyi index 508c2d3..72673f4 100644 --- a/libcachesim/__init__.pyi +++ b/libcachesim/__init__.pyi @@ -18,7 +18,7 @@ class Request: def __init__( self, obj_size: int = 1, - op: ReqOp = ReqOp.READ, + op: ReqOp = ReqOp.OP_NOP, valid: bool = True, obj_id: int = 0, clock_time: int = 0, @@ -151,6 +151,7 @@ class CacheBase: def to_evict(self, req: Request) -> CacheObject: ... def get_occupied_byte(self) -> int: ... def get_n_obj(self) -> int: ... + def set_cache_size(self, new_size: int) -> None: ... def print_cache(self) -> str: ... def process_trace(self, reader: ReaderProtocol, start_req: int = 0, max_req: int = -1) -> tuple[float, float]: ... @property @@ -323,7 +324,12 @@ class PluginCache(CacheBase): # Readers class TraceReader(ReaderProtocol): c_reader: bool - def __init__(self, trace: str, trace_type: TraceType = TraceType.UNKNOWN_TRACE, **kwargs): ... + def __init__( + self, + trace: str, + trace_type: TraceType = TraceType.UNKNOWN_TRACE, + reader_init_params: Optional[ReaderInitParam] = None, + ): ... class SyntheticReader(ReaderProtocol): c_reader: bool @@ -360,7 +366,13 @@ def create_uniform_requests( # Analyzer class TraceAnalyzer: - def __init__(self, analyzer, reader: ReaderProtocol, output_path: str, analysis_param, analysis_option): ... + def __init__( + self, + reader: ReaderProtocol, + output_path: str, + analysis_param: Optional[AnalysisParam] = None, + analysis_option: Optional[AnalysisOption] = None, + ): ... def run(self) -> None: ... def cleanup(self) -> None: ...