Skip to content

feat: Add S4-FIFO algo - #335

Merged
haochengxia merged 3 commits into
1a1a11a:dev/s4fifofrom
haochengxia:feat/s4fifo-clean
Sep 16, 2026
Merged

haochengxia merged 3 commits into
1a1a11a:dev/s4fifofrom
haochengxia:feat/s4fifo-clean

Conversation

@haochengxia

@haochengxia haochengxia commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

S4-FIFO generalizes S3-FIFO with two extra tunable parameters. Build with -DENABLE_S4FIFO=ON. #332

Usage:

# learned (default)
cachesim TRACE oracleGeneral s4fifo cache-size-ratio --ignore-obj-size 1

# static S3-FIFO-equivalent behaviour
cachesim TRACE oracleGeneral s4fifo cache-size-ratio  --ignore-obj-size 1 -e "auto-tune=0"

# a different observation window, as a fraction of the trace or an absolute count
cachesim TRACE oracleGeneral s4fifo cache-size-ratio  -e "feature-collect-reqs=0.3"
cachesim TRACE oracleGeneral s4fifo cache-size-ratio  -e "feature-collect-reqs=2000000"

Results

Example 1

10M-request twitter trace, whole trace scored, miss-ratio reduction vs FIFO:

size ratio S3-FIFO S4-FIFO
0.001 14.4% 16.2%
0.01 22.8% 25.0%
0.03 21.9% 24.7%
0.1 18.8% 21.0%
0.8 3.7% 3.8%

Example 2

Haocheng@node0:~/libCacheSim$ ./_build/bin/cachesim /mntData2/data/oracleReuse/cphy/w71.oracleGeneral.bin.zst oracleGeneral s4fifo 0.1 --ignore-obj-size 1
[INFO]  09-16-2026 04:05:51 cli_reader_utils.c:268  (tid=140355046479104): calculating working set size...
[INFO]  09-16-2026 04:05:51 cli_reader_utils.c:297  (tid=140355046479104): working set size: 673334 object 673334 byte
[INFO]  09-16-2026 04:05:52 cli_parser.c:598  (tid=140355046479104): trace path: /mntData2/data/oracleReuse/cphy/w71.oracleGeneral.bin.zst, trace_type ORACLE_GENERAL_TRACE, ofilepath result/w71.oracleGeneral.bin.zst.cachesim, 64 threads, warmup -1 sec, total 1 algo x 1 size = 1 caches, s4fifo, ignore object size
[DEBUG] 09-16-2026 04:05:52 request.h:130  (tid=140355046479104): req clock_time 27145606, id 14779568, size 1, cost 1, op nop, valid 1
[DEBUG] 09-16-2026 04:05:52 request.h:130  (tid=140355046479104): req clock_time 27145606, id 14779568, size 1, cost 1, op nop, valid 1
[INFO]  09-16-2026 04:05:52    sim.c:65   (tid=140355046479104): w71.oracleGeneral.bin.zst S4FIFO-0.1000-2-0-0.00 24.00 hour: 1172163 requests, miss ratio 0.2126, interval miss ratio 0.2126
[INFO]  09-16-2026 04:05:52 S4FIFO.c:772  (tid=140355046479104): S4FIFO-0.1000-2-0-0.00: applying learned configuration small-size-ratio=0.0500,ghost-size-ratio=0.9000,move-to-main-threshold=1,ghost-to-main-threshold=0,small-skip-ratio=0.2500
[INFO]  09-16-2026 04:05:52    sim.c:65   (tid=140355046479104): w71.oracleGeneral.bin.zst S4FIFO-0.1000-2-0-0.00 48.00 hour: 2412842 requests, miss ratio 0.1860, interval miss ratio 0.1609
[INFO]  09-16-2026 04:05:52    sim.c:65   (tid=140355046479104): w71.oracleGeneral.bin.zst S4FIFO-0.1000-2-0-0.00 72.00 hour: 3623208 requests, miss ratio 0.1820, interval miss ratio 0.1741
[INFO]  09-16-2026 04:05:53    sim.c:65   (tid=140355046479104): w71.oracleGeneral.bin.zst S4FIFO-0.1000-2-0-0.00 96.00 hour: 5825046 requests, miss ratio 0.2274, interval miss ratio 0.3021
/mntData2/data/oracleReuse/cphy/w71.oracleGeneral.bin.zst S4FIFO-0.1000-2-0-0.00 cache size    67333,          6840776 req, miss ratio 0.2164, throughput 5.89 MQPS
Haocheng@node0:~/libCacheSim$ ./_build/bin/cachesim /mntData2/data/oracleReuse/cphy/w71.oracleGeneral.bin.zst oracleGeneral s4fifo 0.1 --ignore-obj-size 1 -e "auto-tune=0"
[INFO]  09-16-2026 04:06:04 cli_reader_utils.c:268  (tid=140679681869056): calculating working set size...
[INFO]  09-16-2026 04:06:05 cli_reader_utils.c:297  (tid=140679681869056): working set size: 673334 object 673334 byte
[INFO]  09-16-2026 04:06:05 cli_parser.c:598  (tid=140679681869056): trace path: /mntData2/data/oracleReuse/cphy/w71.oracleGeneral.bin.zst, trace_type ORACLE_GENERAL_TRACE, ofilepath result/w71.oracleGeneral.bin.zst.cachesim, 64 threads, warmup -1 sec, total 1 algo x 1 size = 1 caches, s4fifo, eviction-params: auto-tune=0, ignore object size
[DEBUG] 09-16-2026 04:06:05 request.h:130  (tid=140679681869056): req clock_time 27145606, id 14779568, size 1, cost 1, op nop, valid 1
[DEBUG] 09-16-2026 04:06:05 request.h:130  (tid=140679681869056): req clock_time 27145606, id 14779568, size 1, cost 1, op nop, valid 1
[INFO]  09-16-2026 04:06:05    sim.c:65   (tid=140679681869056): w71.oracleGeneral.bin.zst S4FIFO-0.1000-2-0-0.00 24.00 hour: 1172163 requests, miss ratio 0.2126, interval miss ratio 0.2126
[INFO]  09-16-2026 04:06:05    sim.c:65   (tid=140679681869056): w71.oracleGeneral.bin.zst S4FIFO-0.1000-2-0-0.00 48.00 hour: 2412842 requests, miss ratio 0.1864, interval miss ratio 0.1617
[INFO]  09-16-2026 04:06:05    sim.c:65   (tid=140679681869056): w71.oracleGeneral.bin.zst S4FIFO-0.1000-2-0-0.00 72.00 hour: 3623208 requests, miss ratio 0.1871, interval miss ratio 0.1886
[INFO]  09-16-2026 04:06:06    sim.c:65   (tid=140679681869056): w71.oracleGeneral.bin.zst S4FIFO-0.1000-2-0-0.00 96.00 hour: 5825046 requests, miss ratio 0.2327, interval miss ratio 0.3077
/mntData2/data/oracleReuse/cphy/w71.oracleGeneral.bin.zst S4FIFO-0.1000-2-0-0.00 cache size    67333,          6840776 req, miss ratio 0.2218, throughput 5.81 MQPS

haochengxia and others added 3 commits September 15, 2026 21:57
…ure tracking

Five fixes from the review of the S4-FIFO PR:

- Size each sub-FIFO's hash table from its own byte size, capped by any
  requested --hashpower, rather than inheriting the parent's. Previously a
  single S4FIFO allocated three full-size tables; at a 1GiB cache size peak
  RSS drops from 527MiB to 338MiB, matching S3FIFO's 340MiB, with an
  identical miss ratio.

- Leave WARMUP on the first capacity eviction instead of when occupancy
  reaches cache_size. Eviction runs before occupancy can exceed the limit,
  so the old test needed the trace to fill the cache exactly and in practice
  never fired: on cloudPhysicsIO the learned mode spent the whole trace in
  warmup and never predicted a configuration.

- Only pre-scan the trace for its request count when an algorithm actually
  reads it. Every cachesim and MRC run paid for a full extra pass, which on
  a txt or zstd trace costs as much as the simulation (LRU on a 1M-request
  csv: 0.48s -> 0.33s).

- Stop double-counting one-hit-wonders: S4FIFO_track_ghost_insert already
  records them, so the extra call doubled one_hit_count whenever a ghost
  FIFO was configured.

- Stamp clock reinsertions in the main FIFO with S4FIFO_track_main_insert.
  A reinserted object takes a fresh tail position but kept a stale
  insert_seq, skewing the main queue's hit-position histogram.

The eviction path itself is unchanged: the static heuristic still matches
S3-FIFO exactly, and the full test suite passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ue tracking

Second round of review fixes.

- Adding n_total_req grew common_cache_params_t from 24 to 32 bytes, but
  clone_cache and create_cache_with_new_size still asserted the old size, so
  any assertion-enabled build aborted in simulate_at_multi_sizes (testSimulator
  exited 134 under -DCMAKE_BUILD_TYPE=Debug). That assert was a tripwire for
  exactly the bug that had happened - both functions rebuild the struct field
  by field and were silently dropping the new field - so copy n_total_req in
  both, retain it on cache_t to copy it from, and make the tripwire a
  _Static_assert. As a runtime assert it was compiled out of release builds and
  so never fired in CI; now it breaks the build for everyone, which is the
  point. Verified it still trips by adding a scratch field.

- MINISIM hardcoded n_total_req = 0 even though it had just counted the trace,
  so the default fractional feature-collect-reqs fell back to a fixed 10000 and
  MRC disagreed with cachesim. Pass sampled_cnt - not n_req_, since spatial
  sampling drops requests before the cache sees them, so sampled_cnt is what
  cache->n_req climbs to. On twitter_cluster52 at wss 0.055 the miss ratio goes
  0.216370 -> 0.205729, and the curve now matches cachesim exactly (0.299438
  and 0.185243 against 0.2994 and 0.1852).

- S4FIFO_track_ghost_insert returned early outside a collection window, so
  objects inserted during warmup or between windows kept a zero insert_seq and
  ghost_insert_seq froze. A later hit inside a window was then scored against a
  stamp from a different sequence. Always stamp and advance, as the small and
  main helpers do, and gate only the per-window one-hit counter. Costs ~1%
  throughput (4.82 -> 4.78 MQPS) for the extra lookup.

- record_ghost_removal ran on every ghost hit, but with a positive
  ghost-to-main-threshold the entry deliberately stays in the queue. The
  tracker subtracts recorded removals as holes from later hit positions, so
  this invented holes that were never there; record it only in the branch that
  actually removes.

Release and Debug both build clean and pass all 10 tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-16T03:50:37.669459Z 7eaab04 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7eaab046d0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +673 to +674
return req->obj_size <= params->small_fifo->cache_size &&
cache_can_insert_default(cache, req);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Admit ghost hits according to their destination queue

When auto-tuning shrinks the small FIFO, an object previously admitted under the larger size can remain in the ghost FIFO while being larger than the new small FIFO. A subsequent ghost hit is supposed to promote that object into the main FIFO, but this check rejects it solely against small_fifo->cache_size, so cache_get_base() never calls S4FIFO_insert() and the promotion is silently lost even when the object fits in the main cache. This is especially likely for the supplied configurations that reduce the small ratio from 10% to 1.5–8%; permit ghost promotions based on their actual destination capacity.

Useful? React with 👍 / 👎.

Comment on lines +428 to +432
int64_t probation_len =
(int64_t)(params->small_skip_ratio *
params->small_fifo->get_n_obj(params->small_fifo));
if (S4FIFO_small_fifo_age(params, obj) >= probation_len) {
obj->S4FIFO.freq += 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Exclude the entire requested probation span

Insertion increments small_insert_seq after stamping the object, so resident objects have ages from 1 for the newest through n_obj for the oldest. Using >= probation_len therefore skips only probation_len - 1 objects; for example, small-skip-ratio=1.0 still counts a hit on the oldest object even though the entire small FIFO should be probationary. This changes promotion decisions for the learned configurations, including the shipped class with a 1.00 skip ratio; use a strict boundary or make the age zero-based.

Useful? React with 👍 / 👎.

Comment on lines +137 to +144
if (model->n_classes == 0 ||
model->n_classes > S4FIFO_REAL_MODEL_MAX_CLASSES ||
model->n_models == 0 || model->n_trees_total == 0) {
WARN(
"S4FIFO: %s has implausible header (n_models=%u n_classes=%u "
"n_trees_total=%u)\n",
path, model->n_models, model->n_classes, model->n_trees_total);
goto out;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate model dimensions and indices before accepting it

When model-path names a magic-valid but incompatible or malformed .s4m, this validation accepts it without checking that estimator counts match n_trees_total, feature indices fit the 76-element input, or child/tree offsets stay within their arrays. eval_tree() then trusts those values and can read past trees, nodes, leaves, or the input buffer, causing a crash or corrupted prediction instead of the documented fallback to the embedded model. Validate the complete structure before publishing the loaded model.

Useful? React with 👍 / 👎.

@haochengxia haochengxia changed the title feat: Add S4-FIFO algo- #4 feat: Add S4-FIFO algo Sep 16, 2026
@haochengxia
haochengxia merged commit 4d183aa into 1a1a11a:dev/s4fifo Sep 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant