diff --git a/doc/quickstart_mrcProfiler.md b/doc/quickstart_mrcProfiler.md index 86b437a26..821720eae 100644 --- a/doc/quickstart_mrcProfiler.md +++ b/doc/quickstart_mrcProfiler.md @@ -18,12 +18,12 @@ First, [build libCacheSim](/doc/install.md). After building libCacheSim, `mrcPro ## Basic Usage ``` -./mrcProfiler trace_path trace_type --algo=[LRU] --profiler=[SHARDS|MINISIM] +./bin/mrcProfiler trace_path trace_type --algo=[LRU] --profiler=[SHARDS|MINISIM] --profiler-params=[FIX_RATE,0.01,hash_salt|FIX_SIZE,8192,hash_salt|FIX_RATE,0.01,thread_num(for MINISIM)] --size=[0.01,1,100|1MiB,100MiB,100|0.001,0.002,0.004,0.008,0.016|1MiB,10MiB,10MiB,1GiB] ``` -Use ./mrcProfiler --help for more details. +Use ./bin/mrcProfiler --help for more details. Plot scripts are provided in `scripts/profile_mrc.py`. See [here](/scripts/README.md) for more details. @@ -35,13 +35,13 @@ SHARDS is configured in `fixed sampling rate` mode with a sampling rate of `0.01 The cache sizes for MRC generation are specified in `fixed-size mode`, spanning `10` evenly spaced points from `100MB` to `1GB`: ```bash -./mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=LRU --profiler=SHARDS --profiler-params=FIX_RATE,0.01,42 --size=100MB,1GB,10 +./bin/mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=LRU --profiler=SHARDS --profiler-params=FIX_RATE,0.01,42 --size=100MB,1GB,10 ``` SHARDS can also operate in `fixed sample size` mode, limiting memory usage by sampling a fixed number of unique objects. The example below samples `2048` objects: ```bash -./mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=LRU --profiler=SHARDS --profiler-params=FIX_SIZE,2048,42 --size=100MB,1GB,10 +./bin/mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=LRU --profiler=SHARDS --profiler-params=FIX_SIZE,2048,42 --size=100MB,1GB,10 ``` ### Profiling MRC with WSS-Based Sizes @@ -49,7 +49,7 @@ SHARDS can also operate in `fixed sample size` mode, limiting memory usage by sa Generate an MRC based on WSS percentages. The example below creates `10` evenly spaced points from `10%` to `50%` of the WSS: ```bash -./mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=LRU --profiler=SHARDS --profiler-params=FIX_RATE,0.01,42 --size=0.1,0.5,10 +./bin/mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=LRU --profiler=SHARDS --profiler-params=FIX_RATE,0.01,42 --size=0.1,0.5,10 ``` ### Profiling MRC with Specific Sizes @@ -60,13 +60,13 @@ mrcProfiler supports both `WSS-based` and `fixed-size` MRC generation for specif **WSS-based sizes:** ```bash -./mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=LRU --profiler=SHARDS --profiler-params=FIX_RATE,0.01,42 --size=0.01,0.02,0.04,0.08,0.16 +./bin/mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=LRU --profiler=SHARDS --profiler-params=FIX_RATE,0.01,42 --size=0.01,0.02,0.04,0.08,0.16 ``` **Fixed cache sizes:** ```bash -./mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=LRU --profiler=SHARDS --profiler-params=FIX_RATE,0.01,42 --size=10MB,20MB,40MB,80MB,160MB +./bin/mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=LRU --profiler=SHARDS --profiler-params=FIX_RATE,0.01,42 --size=10MB,20MB,40MB,80MB,160MB ``` @@ -76,15 +76,21 @@ mrcProfiler supports both `WSS-based` and `fixed-size` MRC generation for specif In the example below, `FIX_RATE,0.01,10` sets a `1%` sampling rate and `10` threads. Note: Sampling rates above 0.5 disable sampling (full trace replay). ```bash -./mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=FIFO --profiler=MINISIM --profiler-params=FIX_RATE,0.01,10 --size=0.1,0.5,10 +./bin/mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=FIFO --profiler=MINISIM --profiler-params=FIX_RATE,0.01,10 --size=0.1,0.5,10 ``` +`--algo` accepts the same names as `cachesim`, so any built-in algorithm works — ARC, S3FIFO, sieve, twoq, and the rest. See the [README](/README.md#supported-algorithms) for the full list. + +`belady` and `beladySize` need a trace that carries future access times, so they only run on an oracle format such as `oracleGeneral` or `lcs`; the profiler says so and stops otherwise. + +`beladySize` is additionally approximate under sampling, beyond the usual sampling error, and warns when you ask for it. It ranks candidates by reuse distance, computed as `next_access_vtime - n_req`, but `next_access_vtime` counts requests in the full trace while `n_req` counts only the requests the sampler kept, so the distance comes out inflated. On `cloudPhysicsIO` at a 100 MB cache, sample rate 0.5 puts it 0.0126 away from the unsampled miss ratio, against 0.0003 for `belady` and 0.0023 for LRU. Use `FIX_RATE,1,` for an exact run, or `belady`, which compares future times directly and is unaffected. + ### Ignoring Object Sizes To ignore object sizes (treat all objects as 1-byte): ```bash -./mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=LRU --profiler=SHARDS --profiler-params=FIX_RATE,0.01,42 --size=0.1,0.5,10 --ignore-obj-size +./bin/mrcProfiler ../data/cloudPhysicsIO.vscsi vscsi --algo=LRU --profiler=SHARDS --profiler-params=FIX_RATE,0.01,42 --size=0.1,0.5,10 --ignore-obj-size ``` ### Supporting Different Trace Formats @@ -105,13 +111,13 @@ Commands: ```bash # cachesim -time ./cachesim /path_to/cluster52.oracleGeneral.sample10 oracleGeneral LRU 10MB,20MB,30MB,40MB,50MB,60MB,70MB,80MB,90MB,100MB --verbose=0 +time ./bin/cachesim /path_to/cluster52.oracleGeneral.sample10 oracleGeneral LRU 10MB,20MB,30MB,40MB,50MB,60MB,70MB,80MB,90MB,100MB --verbose=0 # mrcProfiler with SHARDS with 0.01 sample rate -time ./mrcProfiler /path_to/cluster52.oracleGeneral.sample10 oracleGeneral --algo=LRU --profiler=SHARDS --profiler-params=FIX_RATE,0.01,10 --size=10MB,100MB,10 +time ./bin/mrcProfiler /path_to/cluster52.oracleGeneral.sample10 oracleGeneral --algo=LRU --profiler=SHARDS --profiler-params=FIX_RATE,0.01,10 --size=10MB,100MB,10 # mrcProfiler with SHARDS with 8192 sample size -time ./mrcProfiler /path_to/cluster52.oracleGeneral.sample10 oracleGeneral --algo=LRU --profiler=SHARDS --profiler-params=FIX_SIZE,32768,10 --size=10MB,100MB,10 +time ./bin/mrcProfiler /path_to/cluster52.oracleGeneral.sample10 oracleGeneral --algo=LRU --profiler=SHARDS --profiler-params=FIX_SIZE,32768,10 --size=10MB,100MB,10 ``` resluts: @@ -128,22 +134,22 @@ Commands: ```bash # cachesim for FIFO -time ./cachesim /path_to/cluster52.oracleGeneral.sample10 oracleGeneral FIFO 10MB,20MB,30MB,40MB,50MB,60MB,70MB,80MB,90MB,100MB --verbose=0 +time ./bin/cachesim /path_to/cluster52.oracleGeneral.sample10 oracleGeneral FIFO 10MB,20MB,30MB,40MB,50MB,60MB,70MB,80MB,90MB,100MB --verbose=0 # cachesim for ARC -time ./cachesim /path_to/cluster52.oracleGeneral.sample10 oracleGeneral ARC 10MB,20MB,30MB,40MB,50MB,60MB,70MB,80MB,90MB,100MB --verbose=0 +time ./bin/cachesim /path_to/cluster52.oracleGeneral.sample10 oracleGeneral ARC 10MB,20MB,30MB,40MB,50MB,60MB,70MB,80MB,90MB,100MB --verbose=0 # cachesim for S3FIFO -time ./cachesim /path_to/cluster52.oracleGeneral.sample10 oracleGeneral S3FIFO 10MB,20MB,30MB,40MB,50MB,60MB,70MB,80MB,90MB,100MB --verbose=0 +time ./bin/cachesim /path_to/cluster52.oracleGeneral.sample10 oracleGeneral S3FIFO 10MB,20MB,30MB,40MB,50MB,60MB,70MB,80MB,90MB,100MB --verbose=0 # mrcProfiler for FIFO eviction algorithm with MINISIM with 0.01 sample rate -time ./mrcProfiler /path_to/cluster52.oracleGeneral.sample10 oracleGeneral --algo=FIFO --profiler=MINISIM --profiler-params=FIX_RATE,0.01,10 --size=10MB,100MB,10 +time ./bin/mrcProfiler /path_to/cluster52.oracleGeneral.sample10 oracleGeneral --algo=FIFO --profiler=MINISIM --profiler-params=FIX_RATE,0.01,10 --size=10MB,100MB,10 # mrcProfiler for ARC eviction algorithm with MINISIM with 0.01 sample rate -time ./mrcProfiler /path_to/cluster52.oracleGeneral.sample10 oracleGeneral --algo=ARC --profiler=MINISIM --profiler-params=FIX_RATE,0.01,10 --size=10MB,100MB,10 +time ./bin/mrcProfiler /path_to/cluster52.oracleGeneral.sample10 oracleGeneral --algo=ARC --profiler=MINISIM --profiler-params=FIX_RATE,0.01,10 --size=10MB,100MB,10 # mrcProfiler for S3FIFO eviction algorithm with MINISIM with 0.01 sample rate -time ./mrcProfiler /path_to/cluster52.oracleGeneral.sample10 oracleGeneral --algo=S3FIFO --profiler=MINISIM --profiler-params=FIX_RATE,0.01,10 --size=10MB,100MB,10 +time ./bin/mrcProfiler /path_to/cluster52.oracleGeneral.sample10 oracleGeneral --algo=S3FIFO --profiler=MINISIM --profiler-params=FIX_RATE,0.01,10 --size=10MB,100MB,10 ``` resluts: diff --git a/libCacheSim/bin/MRC/parser_mini.c b/libCacheSim/bin/MRC/parser_mini.c index 741319011..4e72855e2 100644 --- a/libCacheSim/bin/MRC/parser_mini.c +++ b/libCacheSim/bin/MRC/parser_mini.c @@ -332,9 +332,10 @@ void parse_mini_cmd(int argc, char *argv[], struct MINI_arguments *args) { for (int i = 0; i < args->n_eviction_algo; i++) { for (int j = 0; j < args->n_cache_size; j++) { int idx = i * args->n_cache_size + j; - args->caches[idx] = create_cache( - args->trace_path, args->eviction_algo[i], args->cache_sizes[j], - args->eviction_params, args->consider_obj_metadata); + args->caches[idx] = + create_cache(args->trace_path, args->eviction_algo[i], + args->cache_sizes[j], args->eviction_params, + args->consider_obj_metadata, DEFAULT_HASHPOWER); if (args->admission_algo != NULL) { args->caches[idx]->admissioner = diff --git a/libCacheSim/bin/cachesim/cache_init.h b/libCacheSim/bin/cachesim/cache_init.h index 95a7536ee..636c17897 100644 --- a/libCacheSim/bin/cachesim/cache_init.h +++ b/libCacheSim/bin/cachesim/cache_init.h @@ -14,121 +14,48 @@ extern "C" { #endif +/* log2 of the hash table size; 24 gives 16M entries */ +#define DEFAULT_HASHPOWER 24 + +/** + * @brief create a cache for the CLI, given the algorithm name + * + * @param hashpower log2 of the hash table size. This used to be adjusted by + * sniffing the trace path for "data/trace.", a file that has not existed for a + * long time, so the adjustment never fired. It is a --hashpower option now + * rather than a hidden rule, because sampling-based algorithms draw candidates + * from the hash table and so their miss ratios depend on its size — not + * something to change silently based on where a trace happens to live. + */ static inline cache_t *create_cache(const char *trace_path, const char *eviction_algo, const uint64_t cache_size, const char *eviction_params, - const bool consider_obj_metadata) { + const bool consider_obj_metadata, + const int hashpower) { common_cache_params_t cc_params = { .cache_size = cache_size, .default_ttl = 86400 * 300, - .hashpower = 24, + .hashpower = hashpower, .consider_obj_metadata = consider_obj_metadata, }; cache_t *cache; - /* the trace provided is small */ - if (trace_path != NULL && strstr(trace_path, "data/trace.") != NULL) - cc_params.hashpower -= 8; - typedef struct { - const char *name; - cache_t *(*init_func)(common_cache_params_t, const char *); - } eviction_algo_entry_t; - static const eviction_algo_entry_t simple_algos[] = { - {"2q", TwoQ_init}, - {"arc", ARC_init}, - {"arcv0", ARCv0_init}, - {"CAR", CAR_init}, - {"cacheus", Cacheus_init}, - {"clock", Clock_init}, - {"clock2qplus", Clock2QPlus_init}, - {"clockpro", ClockPro_init}, - {"fifo", FIFO_init}, - {"fifo-merge", FIFO_Merge_init}, - {"fifo-reinsertion", Clock_init}, - {"fifomerge", FIFO_Merge_init}, - {"flashProb", flashProb_init}, - {"gdsf", GDSF_init}, - {"lhd", LHD_init}, - {"lecar", LeCaR_init}, - {"lecarv0", LeCaRv0_init}, - {"lfu", LFU_init}, - {"lfucpp", LFUCpp_init}, - {"lfuda", LFUDA_init}, - {"lirs", LIRS_init}, - {"lru", LRU_init}, - {"lru-k", LRU_K_init}, - {"lru-prob", LRU_Prob_init}, - {"mq", MQ_init}, - {"multiqueue", MQ_init}, - {"nop", nop_init}, - // plugin cache that allows user to implement custom cache - {"pluginCache", pluginCache_init}, - {"qdlp", QDLP_init}, - {"random", Random_init}, - {"RandomLRU", RandomLRU_init}, - {"randomTwo", RandomTwo_init}, - {"s3-fifo", S3FIFO_init}, - {"s3-fifov0", S3FIFOv0_init}, - {"s3fifo", S3FIFO_init}, - {"s3fifod", S3FIFOd_init}, - {"s3fifov0", S3FIFOv0_init}, - {"sieve", Sieve_init}, - {"size", Size_init}, - {"slru", SLRU_init}, - {"slruv0", SLRUv0_init}, - {"twoq", TwoQ_init}, - {"wtinyLFU", WTinyLFU_init}, -#ifdef ENABLE_3L_CACHE - {"3LCache", ThreeLCache_init}, -#endif -#ifdef ENABLE_GLCACHE - {"GLCache", GLCache_init}, - {"gl-cache", GLCache_init}, -#endif -#ifdef ENABLE_LRB - {"lrb", LRB_init}, -#endif - }; - - cache_t *(*init_func)(common_cache_params_t, const char *) = NULL; - for (size_t i = 0; i < sizeof(simple_algos) / sizeof(simple_algos[0]); ++i) { - if (strcasecmp(eviction_algo, simple_algos[i].name) == 0) { - init_func = simple_algos[i].init_func; - break; - } - } - - // Initializing for algorithms which require special handling (not in - // simple_algos) - if (init_func) { - cache = init_func(cc_params, eviction_params); - } else if (strcasecmp(eviction_algo, "hyperbolic") == 0) { + /* The name to constructor mapping lives in the library + * (cache/cacheAlgoRegistry.c) so that the MINISIM profiler, which only knows + * the algorithm by name, shares one table with the CLI. The cases below need + * more than a lookup — a smaller hash table, or a check that the trace + * carries the future information the algorithm needs — so they are handled + * here rather than in the registry. + * + * tinyLFU used to be one of them, appending window-size=0.01 when the caller + * had not given one. WTinyLFU's DEFAULT_PARAMS already sets exactly that + * before applying the caller's parameters, so the append never changed + * anything; it is a plain alias in the registry now, which is also what makes + * it reachable from the MRC profiler. */ + if (strcasecmp(eviction_algo, "hyperbolic") == 0) { cc_params.hashpower = MAX(cc_params.hashpower - 8, 16); cache = Hyperbolic_init(cc_params, eviction_params); - } else if (strcasecmp(eviction_algo, "tinyLFU") == 0) { - if (eviction_params == NULL || eviction_params[0] == '\0') { - cache = WTinyLFU_init(cc_params, NULL); - } else { - const char *window_size = strstr(eviction_params, "window-size="); - if (window_size == NULL) { - // Calculate exact size needed: original + ",window-size=0.01" + null - // terminator - size_t new_params_len = - strlen(eviction_params) + strlen(",window-size=0.01") + 1; - char *new_params = (char *)malloc(new_params_len); - if (new_params == NULL) { - ERROR("failed to allocate memory for new_params\n"); - abort(); - } - snprintf(new_params, new_params_len, "%s,window-size=0.01", - eviction_params); - cache = WTinyLFU_init(cc_params, new_params); - free(new_params); // Free the allocated memory - } else { - cache = WTinyLFU_init(cc_params, eviction_params); - } - } } else if (strcasecmp(eviction_algo, "belady") == 0) { if (strcasestr(trace_path, "oracleGeneral") == NULL && strcasestr(trace_path, "lcs") == NULL) { @@ -151,8 +78,11 @@ static inline cache_t *create_cache(const char *trace_path, cc_params.hashpower = MAX(cc_params.hashpower - 8, 16); cache = BeladySize_init(cc_params, eviction_params); } else { - ERROR("do not support algorithm %s\n", eviction_algo); - abort(); + cache = create_cache_by_name(eviction_algo, cc_params, eviction_params); + if (cache == NULL) { + ERROR("do not support algorithm %s\n", eviction_algo); + abort(); + } } return cache; diff --git a/libCacheSim/bin/cachesim/cli_parser.c b/libCacheSim/bin/cachesim/cli_parser.c index a7586322d..448c45112 100644 --- a/libCacheSim/bin/cachesim/cli_parser.c +++ b/libCacheSim/bin/cachesim/cli_parser.c @@ -46,6 +46,7 @@ enum argp_option_short { OPTION_PREFETCH_ALGO = 'p', OPTION_PREFETCH_PARAMS = 0x109, OPTION_PRINT_HEAD_REQ = 0x10a, + OPTION_HASHPOWER = 0x10b, }; /* @@ -94,6 +95,11 @@ static struct argp_option options[] = { {"verbose", OPTION_VERBOSE, "1", 0, "Produce verbose output", 10}, {"print-head-req", OPTION_PRINT_HEAD_REQ, "false", 0, "Print the first few requests", 10}, + {"hashpower", OPTION_HASHPOWER, "24", 0, + "Log2 of the hash table size, default 24 (16M entries). Lower it to save " + "memory on small traces. Note that sampling-based algorithms draw " + "candidates from the hash table, so their miss ratios depend on this", + 10}, {0, 0, 0, 0, 0, 0}}; @@ -163,6 +169,13 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) { case OPTION_CONSIDER_OBJ_METADATA: arguments->consider_obj_metadata = is_true(arg) ? true : false; break; + case OPTION_HASHPOWER: + arguments->hashpower = atoi(arg); + if (arguments->hashpower <= 0 || arguments->hashpower >= 40) { + ERROR("hashpower must be between 1 and 39, got %d\n", + arguments->hashpower); + } + break; case OPTION_WARMUP_SEC: arguments->warmup_sec = atoi(arg); break; @@ -226,6 +239,7 @@ static void init_arg(struct arguments *args) { args->use_ttl = false; args->ignore_obj_size = false; args->consider_obj_metadata = false; + args->hashpower = DEFAULT_HASHPOWER; args->report_interval = 3600 * 24; args->n_thread = n_cores(); args->warmup_sec = -1; @@ -347,7 +361,7 @@ void parse_cmd(int argc, char *argv[], struct arguments *args) { int idx = i * args->n_cache_size + j; args->caches[idx] = create_cache( args->trace_path, args->eviction_algo[i], args->cache_sizes[j], - args->eviction_params, args->consider_obj_metadata); + args->eviction_params, args->consider_obj_metadata, args->hashpower); if (args->admission_algo != NULL) { args->caches[idx]->admissioner = diff --git a/libCacheSim/bin/cachesim/internal.h b/libCacheSim/bin/cachesim/internal.h index 9b43e7717..f6db49031 100644 --- a/libCacheSim/bin/cachesim/internal.h +++ b/libCacheSim/bin/cachesim/internal.h @@ -47,6 +47,7 @@ struct arguments { bool consider_obj_metadata; bool use_ttl; bool print_head_req; + int hashpower; /* arguments generated */ reader_t *reader; diff --git a/libCacheSim/cache/CMakeLists.txt b/libCacheSim/cache/CMakeLists.txt index 34c17f5a6..551928048 100644 --- a/libCacheSim/cache/CMakeLists.txt +++ b/libCacheSim/cache/CMakeLists.txt @@ -135,6 +135,7 @@ set(cache_sources_c ${eviction_sources_c} ${prefetch_sources_c} cache.c + cacheAlgoRegistry.c plugin.c ) diff --git a/libCacheSim/cache/cacheAlgoRegistry.c b/libCacheSim/cache/cacheAlgoRegistry.c new file mode 100644 index 000000000..55e514329 --- /dev/null +++ b/libCacheSim/cache/cacheAlgoRegistry.c @@ -0,0 +1,122 @@ +/** + * @file cacheAlgoRegistry.c + * @brief Maps eviction algorithm names to their constructors. + * + * Callers that only have the algorithm's name — the CLI tools and the MINISIM + * profiler — used to find the constructor two different ways: cachesim carried + * its own table, while the profiler went through dlsym() against the running + * executable. The latter cannot work for a statically linked build, because the + * constructors live in an archive member nothing references, so the linker + * never pulls them in and the lookup fails at run time. + * + * Referencing the table from this translation unit is what pulls those archive + * members in, so the lookup is a plain function call with no dynamic loading. + */ + +#include + +#include "libCacheSim/cache.h" +#include "libCacheSim/evictionAlgo.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + const char *name; + cache_t *(*init_func)(common_cache_params_t, const char *); +} cache_algo_entry_t; + +/* Keep alphabetical; several names are aliases for the same constructor. */ +static const cache_algo_entry_t g_cache_algos[] = { + {"2q", TwoQ_init}, + {"arc", ARC_init}, + {"arcv0", ARCv0_init}, + {"CAR", CAR_init}, + {"cacheus", Cacheus_init}, + {"clock", Clock_init}, + {"clock2qplus", Clock2QPlus_init}, + {"clockpro", ClockPro_init}, + {"fifo", FIFO_init}, + {"fifo-merge", FIFO_Merge_init}, + {"fifo-reinsertion", Clock_init}, + {"fifomerge", FIFO_Merge_init}, + {"flashProb", flashProb_init}, + {"gdsf", GDSF_init}, + {"lhd", LHD_init}, + {"lecar", LeCaR_init}, + {"lecarv0", LeCaRv0_init}, + {"lfu", LFU_init}, + {"lfucpp", LFUCpp_init}, + {"lfuda", LFUDA_init}, + {"lirs", LIRS_init}, + {"lru", LRU_init}, + {"lru-k", LRU_K_init}, + {"lru-prob", LRU_Prob_init}, + {"mq", MQ_init}, + {"multiqueue", MQ_init}, + {"nop", nop_init}, + /* plugin cache that allows user to implement custom cache */ + {"pluginCache", pluginCache_init}, + {"qdlp", QDLP_init}, + {"random", Random_init}, + {"RandomLRU", RandomLRU_init}, + {"randomTwo", RandomTwo_init}, + {"s3-fifo", S3FIFO_init}, + {"s3-fifov0", S3FIFOv0_init}, + {"s3fifo", S3FIFO_init}, + {"s3fifod", S3FIFOd_init}, + {"s3fifov0", S3FIFOv0_init}, + {"sieve", Sieve_init}, + {"size", Size_init}, + {"slru", SLRU_init}, + {"slruv0", SLRUv0_init}, + {"tinyLFU", WTinyLFU_init}, + {"twoq", TwoQ_init}, + {"wtinyLFU", WTinyLFU_init}, + /* these need future information and are only valid on oracle traces, so + * callers that know the trace type should check before using them */ + {"belady", Belady_init}, + {"beladySize", BeladySize_init}, + {"hyperbolic", Hyperbolic_init}, +#ifdef ENABLE_3L_CACHE + {"3LCache", ThreeLCache_init}, +#endif +#ifdef ENABLE_GLCACHE + {"GLCache", GLCache_init}, + {"gl-cache", GLCache_init}, +#endif +#ifdef ENABLE_LRB + {"lrb", LRB_init}, +#endif +}; + +cache_init_func_ptr find_cache_init_func(const char *cache_algo_name) { + if (cache_algo_name == NULL) { + return NULL; + } + + for (size_t i = 0; i < sizeof(g_cache_algos) / sizeof(g_cache_algos[0]); + i++) { + if (strcasecmp(cache_algo_name, g_cache_algos[i].name) == 0) { + return g_cache_algos[i].init_func; + } + } + + return NULL; +} + +cache_t *create_cache_by_name(const char *cache_algo_name, + const common_cache_params_t cc_params, + const char *cache_specific_params) { + cache_init_func_ptr init_func = find_cache_init_func(cache_algo_name); + if (init_func == NULL) { + return NULL; + } + + return init_func(cc_params, cache_specific_params); +} + +#ifdef __cplusplus +} +#endif diff --git a/libCacheSim/cache/plugin.c b/libCacheSim/cache/plugin.c index 4751aae32..5b2e69221 100644 --- a/libCacheSim/cache/plugin.c +++ b/libCacheSim/cache/plugin.c @@ -23,13 +23,18 @@ cache_t *create_cache_external(const char *const cache_alg_name, char shared_lib_path[256]; char cache_init_func_name[256]; - sprintf(shared_lib_path, "./lib%s.so", cache_alg_name); - sprintf(cache_init_func_name, "%s_init", cache_alg_name); - + snprintf(shared_lib_path, sizeof(shared_lib_path), "./lib%s.so", + cache_alg_name); + snprintf(cache_init_func_name, sizeof(cache_init_func_name), "%s_init", + cache_alg_name); + + /* Failure returns NULL, as the header documents, so the caller can report + * which algorithm it could not find. Exiting here instead made that + * reporting unreachable and left the user with a bare dlerror string. */ handle = dlopen(shared_lib_path, RTLD_LAZY); if (!handle) { - fprintf(stderr, "%s\n", dlerror()); - exit(EXIT_FAILURE); + WARN("cannot load %s: %s\n", shared_lib_path, dlerror()); + return NULL; } dlerror(); /* Clear any existing error */ @@ -43,8 +48,12 @@ cache_t *create_cache_external(const char *const cache_alg_name, cache_init = dlsym_ptr.func_ptr; if ((error = dlerror()) != NULL) { - fprintf(stderr, "%s\n", error); - exit(EXIT_FAILURE); + WARN("cannot find %s in %s: %s\n", cache_init_func_name, shared_lib_path, + error); + /* nothing from the library is in use on this path, unlike the success path + * below, so the handle can be closed rather than leaked */ + dlclose(handle); + return NULL; } else { INFO("external cache %s loaded\n", cache_alg_name); } @@ -59,15 +68,25 @@ cache_t *create_cache_external(const char *const cache_alg_name, cache_t *create_cache_internal(const char *const cache_alg_name, common_cache_params_t cc_params, void *cache_specific_params) { - cache_t *(*cache_init)(common_cache_params_t, void *) = NULL; - char *err = NULL; + /* Built-in algorithms are looked up in the registry rather than through + * dlsym(). Their constructors live in an archive member that nothing else + * references, so in a statically linked build the linker never pulls them in + * and dlsym() cannot find them however the executable is linked. */ + cache_t *cache = create_cache_by_name(cache_alg_name, cc_params, + (const char *)cache_specific_params); + if (cache != NULL) { + return cache; + } + /* Fall back to dlsym for an algorithm that is not built in, e.g. one loaded + * into the process from elsewhere. */ char cache_init_func_name[256]; void *handle = dlopen(NULL, RTLD_GLOBAL); /* should not check err here, otherwise ubuntu will report err even though * everything is OK */ - sprintf(cache_init_func_name, "%s_init", cache_alg_name); + snprintf(cache_init_func_name, sizeof(cache_init_func_name), "%s_init", + cache_alg_name); // ISO C compliant way to convert void* to function pointer union { @@ -76,18 +95,16 @@ cache_t *create_cache_internal(const char *const cache_alg_name, } dlsym_ptr; dlsym_ptr.obj_ptr = dlsym(handle, cache_init_func_name); - cache_init = dlsym_ptr.func_ptr; - - err = dlerror(); + cache_t *(*cache_init)(common_cache_params_t, void *) = dlsym_ptr.func_ptr; if (cache_init == NULL) { - WARN("cannot load internal cache %s: error %s\n", cache_alg_name, err); - abort(); + /* Not an error yet: the caller falls back to loading a shared library. */ + (void)dlerror(); + return NULL; } INFO("internal cache %s loaded\n", cache_alg_name); - cache_t *cache = cache_init(cc_params, cache_specific_params); - return cache; + return cache_init(cc_params, cache_specific_params); } cache_t *create_cache_using_plugin(const char *const cache_alg_name, diff --git a/libCacheSim/include/libCacheSim/evictionAlgo.h b/libCacheSim/include/libCacheSim/evictionAlgo.h index 0df813198..f15be0add 100644 --- a/libCacheSim/include/libCacheSim/evictionAlgo.h +++ b/libCacheSim/include/libCacheSim/evictionAlgo.h @@ -200,6 +200,37 @@ cache_t *GLCache_init(const common_cache_params_t ccache_params, #endif +// *********************************************************************** +// **** **** +// **** lookup by algorithm name **** +// **** **** +// *********************************************************************** + +/** + * @brief look up the constructor for a built-in eviction algorithm + * + * The name is matched case-insensitively and accepts the same aliases as the + * command-line tools, e.g. "s3fifo", "s3-fifo". + * + * @param cache_algo_name algorithm name, may be NULL + * @return the constructor, or NULL if the name is not a built-in algorithm or + * was not compiled in (GLCache, LRB and 3LCache are behind build flags) + */ +cache_init_func_ptr find_cache_init_func(const char *cache_algo_name); + +/** + * @brief construct a built-in eviction algorithm by name + * + * Note that belady and beladySize need future information, so they only work on + * oracle traces; callers that know the trace type should check first. + * + * @return the new cache, or NULL if the name is not a built-in algorithm. The + * caller owns the result and frees it with cache->cache_free(). + */ +cache_t *create_cache_by_name(const char *cache_algo_name, + const common_cache_params_t cc_params, + const char *cache_specific_params); + #ifdef __cplusplus } #endif diff --git a/libCacheSim/mrcProfiler/mrcProfiler.cpp b/libCacheSim/mrcProfiler/mrcProfiler.cpp index 4fdd71e39..bdfe29c7a 100644 --- a/libCacheSim/mrcProfiler/mrcProfiler.cpp +++ b/libCacheSim/mrcProfiler/mrcProfiler.cpp @@ -21,6 +21,43 @@ * rounds to this value and warns under -Wimplicit-const-int-float-conversion */ static constexpr double kHashSpaceSize = 18446744073709551616.0; +/* log2 of the hash table size for the miniature caches MINISIM simulates. The + * caches are small, so a smaller table than cachesim's is appropriate. */ +static constexpr int kMiniSimHashPower = 20; + +/* what cachesim uses, mirroring DEFAULT_HASHPOWER in bin/cachesim/cache_init.h. + * Above sample rate 0.5 MINISIM replays the whole trace, and that run is meant + * to be exact rather than approximate, so it has to size the table the way + * cachesim would: Random, RandomTwo, RandomLRU and Hyperbolic draw eviction + * candidates through the hash mask, so a different table gives a different + * curve. */ +static constexpr int kCacheSimHashPower = 24; + +/* whether a reader fills in req->next_access_vtime, which the Belady policies + * need; every other reader leaves it at -2. + * + * Most oracle formats carry it unconditionally, so the trace type alone + * answers for them. The generic binary reader is the exception: it populates + * the field only when the caller points next_access_vtime_field at the right + * column, so a BIN_TRACE has to be asked rather than assumed. That is a + * library-only configuration today — no CLI exposes the field — but the + * profiler is part of the library, so a caller can set it up. */ +static bool reader_has_next_access_vtime(const reader_t *reader) { + switch (reader->trace_type) { + case ORACLE_GENERAL_TRACE: + case LCS_TRACE: + case ORACLE_SIM_TWR_TRACE: + case ORACLE_SYS_TWR_TRACE: + case ORACLE_SIM_TWRNS_TRACE: + case ORACLE_SYS_TWRNS_TRACE: + return true; + case BIN_TRACE: + return reader->init_params.next_access_vtime_field > 0; + default: + return false; + } +} + mrcProfiler::MRCProfilerBase *mrcProfiler::create_mrc_profiler( mrc_profiler_e type, reader_t *reader, std::string output_path, const mrc_profiler_params_t ¶ms) { @@ -283,10 +320,29 @@ void mrcProfiler::MRCProfilerMINISIM::run() { sampler_t *sampler = nullptr; if (sample_rate > 0.5) { INFO("sample_rate is too large, do not sample\n"); + /* the whole trace is replayed, so the miniature caches have to be + * full-sized; leaving the requested rate in place would scale them down + * while every request still reached them, reporting the miss ratios of + * smaller caches than were asked for */ + sample_rate = 1.0; } else { sampler = create_spatial_sampler(sample_rate); set_spatial_sampler_salt(sampler, 10000019); // TODO: salt can be changed by params + + /* the sampler keeps one object in sampling_ratio_inv, an integer, so it can + * only represent rates of the form 1/n: create_spatial_sampler truncates + * 1/0.3 to 3 and then keeps a third. Sizing the caches by the rate that was + * asked for rather than the one in force makes them too small by that + * ratio -- 10% at 0.3 -- and the curve is reported against the size that + * was asked for, so the error is invisible in the output. Take the rate the + * sampler actually applies. */ + double effective_rate = 1.0 / sampler->sampling_ratio_inv; + if (effective_rate != sample_rate) { + INFO("sample rate %.6f is not of the form 1/n, using %.6f\n", sample_rate, + effective_rate); + sample_rate = effective_rate; + } } // 1. obtain the n_req_, sum_obj_size_req, sampled_cnt and sampled_size @@ -307,13 +363,65 @@ void mrcProfiler::MRCProfilerMINISIM::run() { reader_->init_params.sampler = sampler; reader_->sampler = sampler; + /* Belady and BeladySize read next_access_vtime, which ordinary readers leave + * at -2, so on any other trace they would produce a plausible-looking but + * meaningless curve rather than failing. cachesim checks this before building + * the cache; do the same here. */ + if (strcasecmp(params_.cache_algorithm_str, "belady") == 0 || + strcasecmp(params_.cache_algorithm_str, "beladySize") == 0) { + if (!reader_has_next_access_vtime(reader_)) { + ERROR( + "%s needs future information, which %s traces do not carry; use an " + "oracle format such as oracleGeneral or lcs, or convert with " + "./bin/traceConv\n", + params_.cache_algorithm_str, g_trace_type_name[reader_->trace_type]); + } + } + + /* BeladySize picks its victim by drawing samples from the hash table, so an + * oversized table costs memory and leaves the sampler probing empty buckets. + * cachesim shrinks it by 8 before constructing the cache; do the same here, + * since the miniature caches are built straight from the registry and would + * otherwise get a 1M-slot table each. Hyperbolic gets it for a different + * reason: Hyperbolic_init shrinks its own table as well, so cachesim ends up + * two reductions down, and matching that is what makes an unsampled run + * reproduce cachesim rather than land 0.0001 away. */ + int minisim_hashpower = + (sampler == nullptr) ? kCacheSimHashPower : kMiniSimHashPower; + if (strcasecmp(params_.cache_algorithm_str, "hyperbolic") == 0) { + minisim_hashpower = MAX(minisim_hashpower - 8, 16); + } + if (strcasecmp(params_.cache_algorithm_str, "beladySize") == 0) { + minisim_hashpower = MAX(minisim_hashpower - 8, 16); + + /* BeladySize scores a candidate with next_access_vtime - cache->n_req. + * next_access_vtime counts requests in the full trace, but once the + * sampler drops requests, n_req counts only the ones that survived, so the + * two are in different units and the reuse distance comes out inflated. + * Belady is unaffected because it uses next_access_vtime as an ordering + * and never takes a difference. Measured on cloudPhysicsIO at a 100MB + * cache: at sample rate 0.5 BeladySize is off by 0.0126 against the + * unsampled miss ratio, where Belady is off by 0.0003 and LRU by 0.0023. + * Warn rather than refuse -- the curve is still in the right region, and + * remapping future times into sampled virtual time is a change to the + * sampler that belongs to the maintainers, not a silent correction here. */ + if (sampler != nullptr) { + WARN( + "beladySize scores candidates by reuse distance, which spatial " + "sampling distorts because next_access_vtime stays in full-trace " + "request numbers; the curve is approximate beyond the usual sampling " + "error. Use --profiler-params=FIX_RATE,1, for an exact " + "run, or belady, which is not affected.\n"); + } + } + // 3. run the simulate_with_multi_caches cache_t *caches[MAX_MRC_PROFILE_POINTS]; for (size_t i = 0; i < params_.profile_size.size(); i++) { size_t _cache_size = mrc_size_vec[i] * sample_rate; common_cache_params_t cc_params = {.cache_size = _cache_size, .default_ttl = 0, - .hashpower = 20, + .hashpower = minisim_hashpower, .consider_obj_metadata = false}; caches[i] = create_cache_using_plugin(params_.cache_algorithm_str, cc_params, nullptr); diff --git a/libCacheSim/traceReader/generalReader/binary.c b/libCacheSim/traceReader/generalReader/binary.c index b198ce7ea..f6fe4c0b1 100644 --- a/libCacheSim/traceReader/generalReader/binary.c +++ b/libCacheSim/traceReader/generalReader/binary.c @@ -263,6 +263,14 @@ int binary_read_one_req(reader_t *reader, request_t *req) { if (params->next_access_vtime_field_idx > 0) { req->next_access_vtime = read_data(start + params->next_access_vtime_offset, params->next_access_vtime_format); + /* traces spell "no next access" as either -1 or INT64_MAX. The eviction + * algorithms expect MAX_REUSE_DISTANCE (which is INT64_MAX, so that form + * already arrives correct) and Belady rejects a raw -1 outright, so + * normalize it the way the oracle readers do and a binary trace behaves + * like an oracle one. */ + if (req->next_access_vtime == -1) { + req->next_access_vtime = MAX_REUSE_DISTANCE; + } } (reader->mmap_offset) += reader->item_size;