# How to Build a Cache Simulator Using the C++ API of libCacheSim: A Complete Guide

> Learn to build a high-performance cache simulator using the libCacheSim C++ API. This guide details using the trace reader, cache object, and request handler for efficient simulation.

- Repository: [Juncheng Yang/libcachesim](https://github.com/1a1a11a/libcachesim)
- Tags: tutorial
- Published: 2026-02-23

---

**libCacheSim provides a thin, function-pointer-based C API that integrates seamlessly with C++ to build high-performance cache simulators using only three core components: a trace reader, a cache object, and a request handler.**

libCacheSim is a high-performance caching library designed for large-scale trace-driven simulation. While written in C, its API is fully compatible with C++ and allows you to build a cache simulator using the C++ API of libCacheSim with minimal overhead. The library exposes three essential building blocks—the trace reader, the cache instance, and the request handler—that form the foundation of any simulation workflow.

## Core Components of a libCacheSim Simulator

Building a simulator requires understanding three interconnected components that manage the simulation lifecycle.

### Trace Reader

The **trace reader** parses trace files (CSV, VSCSI, binary, or others) and yields `request_t` objects. The core functions `open_trace()`, `read_one_req()`, and `close_trace()` are defined in [`libCacheSim/traceReader/reader.c`](https://github.com/1a1a11a/libcachesim/blob/main/libCacheSim/traceReader/reader.c). These functions handle file I/O, format detection, and deserialization of request metadata such as object IDs, sizes, and timestamps.

### Cache Object

The **cache object** maintains eviction state using algorithms like LRU, LFU, or S3-FIFO. Creation occurs through init functions such as `LRU_init()`, declared in [`libCacheSim/include/libCacheSim/evictionAlgo.h`](https://github.com/1a1a11a/libcachesim/blob/main/libCacheSim/include/libCacheSim/evictionAlgo.h) and implemented in algorithm-specific files like [`libCacheSim/cache/eviction/LRU.c`](https://github.com/1a1a11a/libcachesim/blob/main/libCacheSim/cache/eviction/LRU.c). These functions return a `cache_t*` pointer containing function pointers for `get`, `insert`, and `evict` operations.

### Request Handler

The **request handler** orchestrates the simulation loop. The function `new_request()` allocates a reusable `request_t` structure defined in [`libCacheSim/include/libCacheSim.h`](https://github.com/1a1a11a/libcachesim/blob/main/libCacheSim/include/libCacheSim.h). During simulation, `cache->get()` serves as the high-level interface, internally invoking `find`, `insert`, and `evict` as needed while updating internal counters like `cache->n_req` and virtual time.

## Step-by-Step Implementation Guide

Follow these steps to implement a single-threaded cache simulator in C++.

### 1. Include the Public Header

Begin by including the main header file that provides type definitions and function prototypes.

```cpp
#include <libCacheSim.h>

```

### 2. Configure the Trace Reader

Initialize a reader for your specific trace format. For CSV files, populate a `reader_init_param_t` structure to map columns to request fields.

```cpp
reader_init_param_t csv_params = {
    .delimiter = ',',
    .time_field = 2,
    .obj_id_field = 5,
    .obj_size_field = 4,
    .has_header = TRUE,
    .has_header_set = TRUE,
    .obj_id_is_num = TRUE
};
reader_t *reader = open_trace("../data/trace.csv", CSV_TRACE, &csv_params);

```

### 3. Allocate a Request Object

Create a single request object that you reuse across all iterations to minimize allocation overhead.

```cpp
request_t *req = new_request();

```

### 4. Initialize the Cache

Set cache parameters using `common_cache_params_t` and instantiate your chosen eviction algorithm. The example below configures a 1 GiB LRU cache with 2^16 hash buckets.

```cpp
common_cache_params_t cc = default_common_cache_params();
cc.cache_size = 1ULL * GiB;
cc.hashpower = 16;

cache_t *cache = LRU_init(cc, nullptr);

```

### 5. Execute the Simulation Loop

Drive the simulation by reading requests sequentially and querying the cache. The `cache->get()` method returns `true` for hits and `false` for misses.

```cpp
uint64_t n_req = 0, n_miss = 0;
while (read_one_req(reader, req) == 0) {
    if (!cache->get(cache, req))
        ++n_miss;
    ++n_req;
}

```

### 6. Report Results and Cleanup

Calculate statistics and release allocated resources to prevent memory leaks.

```cpp
printf("Miss ratio = %.4f%%\n", 100.0 * (double)n_miss / (double)n_req);

cache->cache_free(cache);
close_trace(reader);
free_request(req);

```

## Complete Working Example

Below is a complete, compilable C++ program that demonstrates the full simulation pipeline.

```cpp
#include <libCacheSim.h>
#include <cstdio>

int main() {
    /* ---- 1. open a CSV trace ----------------------------------- */
    reader_init_param_t csv_params = {
        .delimiter = ',',
        .time_field = 2,
        .obj_id_field = 5,
        .obj_size_field = 4,
        .has_header = TRUE,
        .has_header_set = TRUE,
        .obj_id_is_num = TRUE
    };
    reader_t *r = open_trace("../data/trace.csv", CSV_TRACE, &csv_params);
    if (!r) { fprintf(stderr, "Cannot open trace\n"); return 1; }

    /* ---- 2. allocate a request object --------------------------- */
    request_t *req = new_request();

    /* ---- 3. create a cache (LRU) ------------------------------ */
    common_cache_params_t cc = default_common_cache_params();
    cc.cache_size = 512ULL * MiB;   // 512 MiB
    cc.hashpower  = 16;
    cache_t *c = LRU_init(cc, nullptr);

    /* ---- 4. simulation loop ----------------------------------- */
    uint64_t n_req = 0, n_miss = 0;
    while (read_one_req(r, req) == 0) {
        if (!c->get(c, req)) ++n_miss;
        ++n_req;
    }

    /* ---- 5. report & cleanup --------------------------------- */
    printf("Requests: %lu, Misses: %lu, Miss ratio: %.4f%%\n",
           n_req, n_miss,
           100.0 * (double)n_miss / (double)n_req);

    c->cache_free(c);
    close_trace(r);
    free_request(req);
    return 0;
}

```

## Multi-Threaded and Bulk Simulation

For large-scale experiments, libCacheSim provides bulk simulation APIs that parallelize execution. The `simulate_at_multi_sizes()` function, declared in [`libCacheSim/include/libCacheSim/simulator.h`](https://github.com/1a1a11a/libcachesim/blob/main/libCacheSim/include/libCacheSim/simulator.h), launches multiple threads—each running an independent cache instance across different size configurations—and returns aggregated statistics as `cache_stat_t` structures.

The repository includes a comprehensive example in [`example/cacheSimulatorConcurrent/main.cpp`](https://github.com/1a1a11a/libcachesim/blob/main/example/cacheSimulatorConcurrent/main.cpp) demonstrating how to leverage these concurrent capabilities for throughput-intensive workloads.

## Build Instructions

Compile your simulator using CMake and Ninja, linking against libCacheSim and its dependencies (glib, zstd).

```bash
mkdir _build && cd _build
cmake -G Ninja ..
ninja
./my_cache_sim

```

## Summary

- **libCacheSim** exposes a C-compatible API that works natively with C++ through three core abstractions: the trace reader (`open_trace`, `read_one_req`), the cache object (`LRU_init`, `cache->get`), and the request handler (`new_request`).
- The simulation loop follows a simple pattern: read requests sequentially, invoke `cache->get()` to check for hits or misses, and accumulate statistics.
- Resource cleanup requires calling `cache->cache_free()`, `close_trace()`, and `free_request()` to release internal hash tables and file handles.
- For high-throughput scenarios, use the bulk simulation APIs in [`libCacheSim/include/libCacheSim/simulator.h`](https://github.com/1a1a11a/libcachesim/blob/main/libCacheSim/include/libCacheSim/simulator.h) and refer to [`example/cacheSimulatorConcurrent/main.cpp`](https://github.com/1a1a11a/libcachesim/blob/main/example/cacheSimulatorConcurrent/main.cpp) for multi-threaded implementations.

## Frequently Asked Questions

### How do I switch from LRU to a different eviction algorithm?

Replace `LRU_init()` with the corresponding init function declared in [`libCacheSim/include/libCacheSim/evictionAlgo.h`](https://github.com/1a1a11a/libcachesim/blob/main/libCacheSim/include/libCacheSim/evictionAlgo.h). For example, use `LFU_init()` for Least Frequently Used or `S3FIFO_init()` for the S3-FIFO algorithm. All init functions accept the same `common_cache_params_t` structure and return a `cache_t*` pointer with the same interface.

### Can I use libCacheSim with C++ smart pointers or RAII?

While libCacheSim returns raw pointers (`cache_t*`, `reader_t*`), you can wrap them in `std::unique_ptr` with custom deleters. For instance, use `std::unique_ptr<cache_t, decltype(&cache_free)>` to ensure automatic cleanup, though you must reference the specific free functions (`cache_free`, `close_trace`, `free_request`) as deleters rather than direct destructors.

### What trace formats does libCacheSim support?

The library supports CSV, VSCSI, binary, and several other formats through the `trace_type` parameter in `open_trace()`. The dispatcher in [`libCacheSim/traceReader/reader.c`](https://github.com/1a1a11a/libcachesim/blob/main/libCacheSim/traceReader/reader.c) automatically selects the appropriate parser based on this enum value, allowing you to process standard research traces without manual preprocessing.

### How does `cache->get()` handle misses internally?

According to the implementation in [`libCacheSim/cache/eviction/LRU.c`](https://github.com/1a1a11a/libcachesim/blob/main/libCacheSim/cache/eviction/LRU.c), `cache->get()` wraps `cache_get_base()`, which first calls `find()` to check object presence. On a miss, it may invoke `evict()` if the cache is full, followed by `insert()` to admit the new object. This entry point automatically updates `cache->n_req` and internal virtual time counters, ensuring consistent statistics across all algorithms.