diff --git a/README.md b/README.md index 9a605dac5..9a84f253b 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,7 @@ See [libCacheSim-node](/libCacheSim-node/) for the full API and the bundled `cac The name in `code` is what you pass to `cachesim` (names are case-insensitive). ### Eviction algorithms -* [FIFO](/libCacheSim/cache/eviction/FIFO.c) `fifo`, [LRU](/libCacheSim/cache/eviction/LRU.c) `lru`, [Clock](/libCacheSim/cache/eviction/Clock.c) `clock`, [SLRU](/libCacheSim/cache/eviction/SLRU.c) `slru`, [Random](/libCacheSim/cache/eviction/Random.c) `random`, [RandomTwo](/libCacheSim/cache/eviction/RandomTwo.c) `randomtwo` +* [FIFO](/libCacheSim/cache/eviction/FIFO.c) `fifo`, [LRU](/libCacheSim/cache/eviction/LRU.c) `lru`, [MRU](/libCacheSim/cache/eviction/MRU.c) `mru`, [Clock](/libCacheSim/cache/eviction/Clock.c) `clock`, [SLRU](/libCacheSim/cache/eviction/SLRU.c) `slru`, [Random](/libCacheSim/cache/eviction/Random.c) `random`, [RandomTwo](/libCacheSim/cache/eviction/RandomTwo.c) `randomtwo` * [LFU](/libCacheSim/cache/eviction/LFU.c) `lfu`, [LFU with dynamic aging](/libCacheSim/cache/eviction/LFUDA.c) `lfuda` * [ARC](/libCacheSim/cache/eviction/ARC.c) `arc`, [TwoQ](/libCacheSim/cache/eviction/TwoQ.c) `2q`, [MQ](/libCacheSim/cache/eviction/MQ.c) `mq`, [CLOCK-PRO](/libCacheSim/cache/eviction/ClockPro.c) `clockpro`, [CAR](/libCacheSim/cache/eviction/CAR.c) `car`, [LIRS](/libCacheSim/cache/eviction/LIRS.c) `lirs`, [Clock2QPlus](/libCacheSim/cache/eviction/Clock2QPlus.c) `clock2qplus` * [LRU-K](/libCacheSim/cache/eviction/cpp/LRU_K.cpp) `lru-k`, [LRU-Prob](/libCacheSim/cache/eviction/LRUProb.c) `lru-prob`, [Size](/libCacheSim/cache/eviction/Size.c) `size` diff --git a/doc/advanced_lib_extend.md b/doc/advanced_lib_extend.md index 204266ac1..f13b48bb4 100644 --- a/doc/advanced_lib_extend.md +++ b/doc/advanced_lib_extend.md @@ -53,7 +53,7 @@ Then wire it in. Using `myCache` as the example name: 2. **Add per-object metadata, if you need it.** Add a struct for it to the union in [include/libCacheSim/cacheObj.h](/libCacheSim/include/libCacheSim/cacheObj.h). 3. **Declare the constructor.** Add `myCache_init()` to [include/libCacheSim/evictionAlgo.h](/libCacheSim/include/libCacheSim/evictionAlgo.h). 4. **Compile it.** Add `eviction/myCache.c` to [cache/CMakeLists.txt](/libCacheSim/cache/CMakeLists.txt). -5. **Give it a name.** Add `{"mycache", myCache_init}` to the table in [cache/cacheAlgoRegistry.c](/libCacheSim/cache/cacheAlgoRegistry.c). The table is alphabetical. This one entry makes the algorithm available to `cachesim`, to the MINISIM mode of `mrcProfiler`, and to `create_cache_by_name()`. +5. **Give it a name.** Add `{"mycache", myCache_init}` to the table in [cache/cacheAlgoRegistry.c](/libCacheSim/cache/cacheAlgoRegistry.c). The table is alphabetical. This one entry makes the algorithm available to `cachesim`, to the MINISIM mode of `mrcProfiler`, and to `create_cache_by_name()`. Names are matched case-insensitively, so users can type `mycache` or `myCache`. Only an algorithm that needs more than a lookup, such as a smaller hash table or a check that the trace carries future access times, also needs a branch in [bin/cachesim/cache_init.h](/libCacheSim/bin/cachesim/cache_init.h). 6. **Test and document it.** Add a test to [test/test_evictionAlgo.c](/test/test_evictionAlgo.c), and list the algorithm in the [README](/README.md#supported-algorithms) with the name users type. If it takes `-e` parameters, add a case to the `testCLI` target too, as described in [CONTRIBUTING.md](/CONTRIBUTING.md#testing). Then rebuild and try it: diff --git a/doc/quickstart_mrcProfiler.md b/doc/quickstart_mrcProfiler.md index bfff77aeb..9bc058e26 100644 --- a/doc/quickstart_mrcProfiler.md +++ b/doc/quickstart_mrcProfiler.md @@ -49,7 +49,7 @@ Each row is one cache size. When you give sizes as fractions, the first column i | Option | Meaning | | --- | --- | -| `--algo` | Eviction algorithm. SHARDS only supports `LRU`. MINISIM accepts any name `cachesim` accepts. | +| `--algo` | Eviction algorithm, case-insensitive (`fifo` and `FIFO` are the same). SHARDS only supports `LRU`. MINISIM accepts any name `cachesim` accepts. | | `--profiler` | `SHARDS` or `MINISIM` | | `--profiler-params` | See [Profiler parameters](#profiler-parameters) | | `--size` | See [Choose the cache sizes](#choose-the-cache-sizes) | diff --git a/libCacheSim/bin/mrcProfiler/cli_parser.cpp b/libCacheSim/bin/mrcProfiler/cli_parser.cpp index 18e810bbb..543276a8f 100644 --- a/libCacheSim/bin/mrcProfiler/cli_parser.cpp +++ b/libCacheSim/bin/mrcProfiler/cli_parser.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -370,10 +371,10 @@ void mrc_profiler_params_parse(const char *cache_algorithm_str, mrcProfiler::mrc_profiler_e &profiler_type, mrcProfiler::mrc_profiler_params_t ¶ms) { // initial the params of mrc profiler - if (strcmp(profiler_str, "SHARDS") == 0 || - strcmp(profiler_str, "shards") == 0) { + // names are case-insensitive, the same as cachesim and the MINISIM registry + if (strcasecmp(profiler_str, "SHARDS") == 0) { profiler_type = mrcProfiler::SHARDS_PROFILER; - if (strcmp(cache_algorithm_str, "LRU")) { + if (strcasecmp(cache_algorithm_str, "LRU") != 0) { ERROR("cache algorithm must be LRU for SHARDS\n") exit(1); } @@ -382,8 +383,7 @@ void mrc_profiler_params_parse(const char *cache_algorithm_str, // init shards params params.shards_params.parse_params(params_str); - } else if (strcmp(profiler_str, "MINISIM") == 0 || - strcmp(profiler_str, "minisim") == 0) { + } else if (strcasecmp(profiler_str, "MINISIM") == 0) { profiler_type = mrcProfiler::MINISIM_PROFILER; params.cache_algorithm_str = (char *)cache_algorithm_str; diff --git a/libCacheSim/cache/cacheAlgoRegistry.c b/libCacheSim/cache/cacheAlgoRegistry.c index bb70f9332..a85f341fc 100644 --- a/libCacheSim/cache/cacheAlgoRegistry.c +++ b/libCacheSim/cache/cacheAlgoRegistry.c @@ -53,8 +53,13 @@ static const cache_algo_entry_t g_cache_algos[] = { {"lru", LRU_init}, {"lru-k", LRU_K_init}, {"lru-prob", LRU_Prob_init}, + /* lruv0 is deliberately absent. LRUv0 is the older GLib-based LRU and keeps + * its own hash table, so it bypasses the shared find, insert and evict + * paths that implement TTL expiry, admission and the prefetcher callbacks. + * Naming it here would let the CLI silently ignore those options. */ {"merlin", merlin_init}, {"mq", MQ_init}, + {"mru", MRU_init}, {"multiqueue", MQ_init}, {"nop", nop_init}, /* plugin cache that allows user to implement custom cache */ diff --git a/libCacheSim/cache/eviction/MRU.c b/libCacheSim/cache/eviction/MRU.c index 8c018586f..5a1520624 100644 --- a/libCacheSim/cache/eviction/MRU.c +++ b/libCacheSim/cache/eviction/MRU.c @@ -177,8 +177,14 @@ static cache_obj_t *MRU_to_evict(cache_t *cache, const request_t *req) { static void MRU_evict(cache_t *cache, const request_t *req) { MRU_params_t *params = (MRU_params_t *)cache->eviction_params; cache_obj_t *obj_to_evict = MRU_to_evict(cache, req); - params->q_head = params->q_head->queue.next; - params->q_head->queue.prev = NULL; + DEBUG_ASSERT(obj_to_evict != NULL); + params->q_head = obj_to_evict->queue.next; + if (likely(params->q_head != NULL)) { + params->q_head->queue.prev = NULL; + } else { + /* that was the only object, so the queue is now empty */ + params->q_tail = NULL; + } cache_evict_base(cache, obj_to_evict, true); } diff --git a/scripts/README.md b/scripts/README.md index 5775bf550..5fe988ae4 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -28,6 +28,7 @@ python3 plot_mrc_time.py \ ## Profile miss ratio curves `mrcProfiler` enables fast profiling of miss ratio curves. With a proper parameter setting (such as `sampling_rate`, see [here](../doc/quickstart_mrcProfiler.md) for details) `mrcProfiler` can profile miss ratio curves in a fast way with acceptable accuracy. +Names passed to `--algos` are case-insensitive, so `LRU,FIFO` and `lru,fifo` both work. ```bash # plot profiled miss ratio over sizes diff --git a/test/test_evictionAlgo.c b/test/test_evictionAlgo.c index 331e94392..0a71a5d0c 100644 --- a/test/test_evictionAlgo.c +++ b/test/test_evictionAlgo.c @@ -419,6 +419,28 @@ static void test_MRU(gconstpointer user_data) { test_cache_algorithm(user_data, &test_data_truth[18]); } +// Evicting the only object empties the queue. MRU_evict used to write through +// the new, NULL, head and leave the tail pointing at the freed object, so the +// second miss in a one-object cache segfaulted. +static void test_MRU_evict_only_object(void) { + common_cache_params_t cc_params = {.cache_size = 1, .hashpower = 8}; + cache_t *cache = MRU_init(cc_params, NULL); + request_t *req = new_request(); + req->obj_size = 1; + + for (obj_id_t id = 1; id <= 4; id++) { + req->obj_id = id; + g_assert_false(cache->get(cache, req)); + g_assert_cmpint(cache->get_n_obj(cache), ==, 1); + g_assert_nonnull(cache->find(cache, req, false)); + req->obj_id = id - 1; + g_assert_null(cache->find(cache, req, false)); + } + + free_request(req); + cache->cache_free(cache); +} + static void test_QDLP_FIFO(gconstpointer user_data) { test_cache_algorithm(user_data, &test_data_truth[19]); } @@ -563,6 +585,36 @@ static void test_WTinyLFU(gconstpointer user_data) { // TODO: to be implemented } +/* The MINISIM profiler only knows an algorithm by its name and reaches it + * through create_cache_using_plugin(). That used to go straight to dlsym(), + * which cannot see a built-in constructor in a statically linked build — the + * archive member holding it is never pulled in — so every built-in name failed + * with "undefined symbol: lru_init" (issue #304). The registry lookup that + * replaced it matches names case-insensitively, the same way cachesim does. */ +static void test_create_cache_by_name(gconstpointer user_data) { + common_cache_params_t cc_params = { + .cache_size = CACHE_SIZE, .hashpower = 20, .default_ttl = DEFAULT_TTL}; + + static const struct { + const char *requested; + const char *expected_name; + } names[] = { + {"lru", "LRU"}, {"LRU", "LRU"}, {"Lru", "LRU"}, {"fifo", "FIFO"}, + {"FIFO", "FIFO"}, {"fIfO", "FIFO"}, {"mru", "MRU"}, {"MRU", "MRU"}, + }; + + for (size_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) { + cache_t *cache = + create_cache_using_plugin(names[i].requested, cc_params, NULL); + g_assert_nonnull(cache); + g_assert_cmpstr(cache->cache_name, ==, names[i].expected_name); + cache->cache_free(cache); + } + + // left out of the registry on purpose, see cacheAlgoRegistry.c + g_assert_null(create_cache_by_name("lruv0", cc_params, NULL)); +} + static void empty_test(gconstpointer user_data) { ; } static void test_WorkloadAware(gconstpointer user_data) { @@ -611,6 +663,8 @@ int main(int argc, char *argv[]) { g_test_add_data_func("/libCacheSim/cacheAlgo_Merlin", reader, test_Merlin); g_test_add_data_func("/libCacheSim/cacheAlgo_MQ", reader, test_MQ); g_test_add_data_func("/libCacheSim/cacheAlgo_MRU", reader, test_MRU); + g_test_add_func("/libCacheSim/cacheAlgo_MRU_evict_only_object", + test_MRU_evict_only_object); g_test_add_data_func("/libCacheSim/cacheAlgo_QDLP_FIFO", reader, test_QDLP_FIFO); g_test_add_data_func("/libCacheSim/cacheAlgo_Random", reader, test_Random); @@ -646,6 +700,9 @@ int main(int argc, char *argv[]) { g_test_add_data_func("/libCacheSim/cacheAlgo_BeladySize", reader, test_BeladySize); + g_test_add_data_func("/libCacheSim/create_cache_by_name", reader, + test_create_cache_by_name); + g_test_add_data_func_full("/libCacheSim/empty", reader, empty_test, test_teardown);