Repository navigation
Conversation
Add hill-climb=1, the window climber (WC-W-TinyLFU) of Adaptive Software Cache Management (Middleware'18). After every 10 x cache size bytes of requests it compares the hit ratio with the previous period and moves the window 5% in the same direction if it improved, the other way if not, between 1% and 80%. The window already holds whatever the main cache does not use, so a resize only sets the main cache's size: the main cache evicts its own victims when the window grows, and fills with window victims when it shrinks. SLRU_resize() keeps each SLRU segment's share of the cache. Off by default, and results without hill-climb=1 are unchanged. Only LRU and SLRU main caches are supported.
Drop comments that repeat the code, keep the paper deviations in the file header, and let WTinyLFU_resize_window() read the step itself. No change in results.
SLRU_resize() derived each segment's share from its truncated initial size, so a resize back to the original size could leave the first segment one byte smaller (e.g. 13 instead of 14 at size 71, for 2,868 of the sizes up to 200,000 with seg-size=1:4). Store the configured share where the segments are first sized, and size them from it in both places.
Turning around on a tie traps the climber when no object fits in the 1% and 5% windows: every period has no hit, so it switches between them forever and the cache never gets a hit (e.g. 10 and 19 objects, or small caches with object metadata). Keep the direction unless the hit ratio dropped, as Caffeine does.
After SLRU_resize() a resident object can be larger than the next segment. On a hit, SLRU_find() promoted it, cooled it back down and evicted it, then read the freed object (heap-use-after-free under ASan, e.g. a 20-byte object after resizing a 100-byte 1:4 SLRU to 20 bytes). Move it to the head of its own segment instead. Objects that fit the next segment, which is every object in the default layouts, are promoted as before.
Shorten the header note on the paper deviations, drop a comment that repeats the eviction loop, and trim two test headers. No code change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Review copy in my fork, before anything goes upstream. Upstream issue 21 asks for a dynamic window size in W-TinyLFU, based on Adaptive Software Cache Management (Middleware '18).
What does this PR do?
It adds
-e hill-climb=1to WTinyLFU: the paper's window climber (WC-W-TinyLFU).It's off by default, and results without
hill-climb=1are unchanged. Only LRU and SLRU main caches are supported; anything else is rejected with an error.How the resize works.
WTinyLFU_evictalready moves window victims into the main cache while the main cache has room, so the window holds whatever the main cache doesn't use. A resize therefore only sets the main cache's size:SLRU_resize()keeps each segment's share of the cache. Otherwise the protected segment would keep its old size, and probation would vanish once the window passed about 20%. The shares are stored at init, so repeated resizes don't drift.SLRU_find()now keeps such an object at the head of its own segment instead of promoting it. Promoting it cooled it straight back down and evicted it whileSLRU_find()was still using it: a heap-use-after-free through the publicSLRU_resize(). Through the climber this can't happen: for every cache size up to 500,000, the largest object W-TinyLFU can admit at any window size fits the smallest protected segment, but with zero margin. Objects that fit the next segment, which is every object in the default layouts, are promoted exactly as before.Deviations from the paper (also written in the
WTinyLFU.cheader):Type of change
How was it tested?
ctest:
ctest --test-dir _build --output-on-failure --parallel 4(Release) passes 15/15.CLI tests:
cd _build && bash ../test/test_cli.shgives 428 passed, 0 failed. New cases:hill-climb,hill-climb=,hill-climb=2andhill-climb=yesare rejected cleanly;hill-climb=0,hill-climb=1andmain-cache=LRU,hill-climb=1are accepted;hill-climb=1,printreports the setting;main-cache=FIFO,hill-climb=1is a clean error.New unit tests in
test_evictionAlgo.c:WTinyLFU_hill_climb_grows_window: after exactly 10 × C requests the window grows from 1% to 5%, and the main cache evicts down to its new size at once.WTinyLFU_hill_climb_no_hits: with 19 objects, where the 1% and 5% windows hold nothing, the climber must keep growing until it gets hits. This fails with turn-around-on-tie.SLRU_resize_keeps_large_object: a 20-byte object in a 100-byte 1:4 SLRU resized to 20 bytes must survive its next hit. It fails without the fix, and ASan reports a heap-use-after-free.SLRU_resize: each segment keeps its configured share. Size 71 → 30 → 71 gives a first segment of 14 → 6 → 14. Deriving the share from the truncated initial size gave 13 back at 71.Strict Debug build: with
scripts/debug.sh's C flags (-Wall -Wextra -Werror -Wpedantic …),cachesimandtestEvictionAlgobuild and the eviction tests pass. The Release build has one warning,bin/debug/main.cpp:27(-Wmain), which is pre-existing and not touched here.ASan + UBSan Debug build: 0 reports and 0 assertions on:
hill-climb=1on cloudPhysics with LRU and SLRU mains at 1000 and 5000 objects;window-size=0.5;Randomized stress test (an instrumented copy of the library plus a fuzz harness, not part of this PR):
findagrees withget; an admitted miss is resident; no object is in both window and main; SLRU lists match their counters; segment budgets add up after a resize; main fits its new size after every resize.hill-climboff (see "Not in this PR").Leaks: macOS
leaks --atExitreports 0 leaks with LRU and SLRU mains.Plain SLRU unchanged:
cachesim … slrugives output identical to develop in 32 runs: 8 segment layouts (default,1:4,1:1,n-seg=2,n-seg=3,1:2:3,4:1,3:1) × cloudPhysics in byte and unit mode, twitter_cluster52 and Zipf.Defaults unchanged: I compared printed results against develop f02f42e for cloudPhysics, twitter_cluster52 and Zipf(2M requests, 100K objects, α=1.0), with LRU, SLRU and FIFO mains at 1000 and 5000 objects (unit size), plus cloudPhysics byte mode at 0.01 and 0.1 with LRU and SLRU mains. All 11 are identical.
ctest --test-dir _build --output-on-failurepassesBuild is warning-free (CI uses
-Wall -Wextra -Werror): the changed files build with the strict flags; the one Release warning is the pre-existingbin/debug/main.cpp:27Results
These numbers are a sanity check on the bundled samples, not an evaluation. The cache_dataset runs (fixed 1% vs climb vs the best fixed window per trace and size) are still to do.
cloudPhysics and Twitter. The climber beats fixed 1% in all 8 cells, but doesn't reach the best fixed window. At 1000 objects, cloudPhysics only allows 10 decisions in 113,872 requests, so those rows say little.
Stationary Zipf: the climber is worse. Fixed 1% is the best window there, and the climber loses to it by +0.6 to +1.3 points. I traced the decisions with a temporary print (SLRU main, 1000 objects):
So the noise is about 3.5× the signal, and the climber random-walks. The paper warns about exactly this ("a noisy process", "it never stops oscillating"). Caffeine's decaying step is the obvious follow-up to try.
Choices to review (what this PR picked)
hill-climb=1, notadaptive-window=1orwindow-size=adaptive. It leaves room for an indicator variant later.S3FIFOddoes. It measured within noise of lazy, and SLRU's own size assert stays true.window-size± 5 points.get(), asCacheusandS3FIFOddo. Callers that drivefind/insertdirectly don't adapt.Limitations
SLRU_initalready rejects SLRU mains under 5 bytes.to_evictskips pinned objects, so a pinned object is evicted only when every object in main is pinned. A strict guarantee would mean deferring the shrink, which leaves main over its size.window-sizeis rounded to the nearest multiple of 5%, and a result of 0% means 1%.Not in this PR
Pre-existing issues I found along the way. They're separate concerns, so none are touched here:
cachesim data/cloudPhysicsIO.vscsi vscsi wtinylfu 0.01,0.1 --hashpower=12 --num-thread=1 -e main-cache=SLRUAssertion failed: ((cache->occupied_byte <= cache->cache_size)), function SLRU_find, file SLRU.c, line 275.WTinyLFU_evictevicts exactly one main victim, whatever the incoming object's size. Release builds compile the assert out.--consider-obj-metadata.cachesim data/twitter_cluster52.csv csv wtinylfu 0.001 -t "time-col=1,obj-id-col=2,obj-size-col=3,obj-id-is-num=1" --consider-obj-metadata=1slru.SLRU_coolmovesobj_sizebetween segment counters, while promotion and eviction moveobj_size + obj_md_size. The counters drift untilSLRU_to_evictreturns NULL. Moving the metadata too made every such crash disappear in the stress test.--ignore-obj-size=1, cloudPhysics at 50 and 99 objects gives a miss ratio of 1.0000, while LRU at 50 gives 0.9014.Cacheus.c:375.num_hit / update_intervalis integer division, so Cacheus's learning-rate update sees a hit rate of 0. Found by reading only; not run.Follow-ups: Caffeine's step decay and restart, the indicator variant (WI-W-TinyLFU), and an evaluation on cache_dataset traces.
Checklist
clang-format-e printshowshill-climb=, and the paper deviations are in theWTinyLFU.cheader)