| 1 | // =============================================================== // |
|---|
| 2 | // // |
|---|
| 3 | // File : adcache.cxx // |
|---|
| 4 | // Purpose : // |
|---|
| 5 | // // |
|---|
| 6 | // Institute of Microbiology (Technical University Munich) // |
|---|
| 7 | // http://www.arb-home.de/ // |
|---|
| 8 | // // |
|---|
| 9 | // =============================================================== // |
|---|
| 10 | |
|---|
| 11 | #include "gb_storage.h" |
|---|
| 12 | #include "gb_main.h" |
|---|
| 13 | #include "gb_tune.h" |
|---|
| 14 | |
|---|
| 15 | struct gb_cache_entry { |
|---|
| 16 | GBENTRY *gbe; |
|---|
| 17 | gb_cache_idx prev; |
|---|
| 18 | gb_cache_idx next; |
|---|
| 19 | char *data; |
|---|
| 20 | long clock; |
|---|
| 21 | size_t sizeof_data; |
|---|
| 22 | |
|---|
| 23 | #if defined(GEN_CACHE_STATS) |
|---|
| 24 | long reused; |
|---|
| 25 | char *dbpath; |
|---|
| 26 | #endif // GEN_CACHE_STATS |
|---|
| 27 | }; |
|---|
| 28 | |
|---|
| 29 | inline bool entry_is_linked(gb_cache& cache, gb_cache_idx index) { |
|---|
| 30 | gb_cache_entry& entry = cache.entries[index]; |
|---|
| 31 | return |
|---|
| 32 | (entry.prev || cache.newest_entry == index) && |
|---|
| 33 | (entry.next || cache.oldest_entry == index); |
|---|
| 34 | } |
|---|
| 35 | |
|---|
| 36 | inline gb_cache_entry& unlink_cache_entry(gb_cache& cache, gb_cache_idx index) { |
|---|
| 37 | gb_assert(entry_is_linked(cache, index)); |
|---|
| 38 | |
|---|
| 39 | gb_cache_entry& entry = cache.entries[index]; |
|---|
| 40 | |
|---|
| 41 | gb_cache_idx p = entry.prev; |
|---|
| 42 | gb_cache_idx n = entry.next; |
|---|
| 43 | |
|---|
| 44 | if (index == cache.newest_entry) cache.newest_entry = n; |
|---|
| 45 | if (index == cache.oldest_entry) cache.oldest_entry = p; |
|---|
| 46 | |
|---|
| 47 | cache.entries[n].prev = p; |
|---|
| 48 | cache.entries[p].next = n; |
|---|
| 49 | |
|---|
| 50 | entry.prev = entry.next = 0; |
|---|
| 51 | |
|---|
| 52 | return entry; |
|---|
| 53 | } |
|---|
| 54 | |
|---|
| 55 | inline void link_cache_entry_to_top(gb_cache& cache, gb_cache_idx index) { |
|---|
| 56 | gb_assert(!entry_is_linked(cache, index)); |
|---|
| 57 | |
|---|
| 58 | gb_cache_entry& entry = cache.entries[index]; |
|---|
| 59 | |
|---|
| 60 | entry.prev = entry.next = 0; |
|---|
| 61 | |
|---|
| 62 | if (!cache.newest_entry) { // first entry |
|---|
| 63 | gb_assert(!cache.oldest_entry); |
|---|
| 64 | cache.newest_entry = cache.oldest_entry = index; |
|---|
| 65 | } |
|---|
| 66 | else if (entry.sizeof_data >= cache.big_data_min_size) { |
|---|
| 67 | // Do NOT put big entries to top - instead put them to bottom! |
|---|
| 68 | // This is done to avoid that reading a big entry flushes the complete cache. |
|---|
| 69 | entry.prev = cache.oldest_entry; |
|---|
| 70 | cache.entries[entry.prev].next = index; |
|---|
| 71 | cache.oldest_entry = index; |
|---|
| 72 | } |
|---|
| 73 | else { |
|---|
| 74 | entry.next = cache.newest_entry; |
|---|
| 75 | cache.entries[entry.next].prev = index; |
|---|
| 76 | cache.newest_entry = index; |
|---|
| 77 | } |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | inline void flush_cache_entry(gb_cache& cache, gb_cache_idx index) { |
|---|
| 81 | gb_assert(!entry_is_linked(cache, index)); |
|---|
| 82 | |
|---|
| 83 | gb_cache_entry& entry = cache.entries[index]; |
|---|
| 84 | |
|---|
| 85 | freenull(entry.data); |
|---|
| 86 | cache.sum_data_size -= entry.sizeof_data; |
|---|
| 87 | gb_assert(entry.gbe->cache_index == index); // oops - cache error |
|---|
| 88 | entry.gbe->cache_index = 0; |
|---|
| 89 | |
|---|
| 90 | // insert deleted entry in free list |
|---|
| 91 | entry.next = cache.firstfree_entry; |
|---|
| 92 | cache.firstfree_entry = index; |
|---|
| 93 | |
|---|
| 94 | #if defined(GEN_CACHE_STATS) |
|---|
| 95 | if (entry.reused) { |
|---|
| 96 | GBS_incr_hash(cache.reused, entry.dbpath); |
|---|
| 97 | GBS_write_hash(cache.reuse_sum, entry.dbpath, GBS_read_hash(cache.reuse_sum, entry.dbpath)+entry.reused); |
|---|
| 98 | } |
|---|
| 99 | else { |
|---|
| 100 | GBS_incr_hash(cache.not_reused, entry.dbpath); |
|---|
| 101 | } |
|---|
| 102 | freenull(entry.dbpath); |
|---|
| 103 | #endif // GEN_CACHE_STATS |
|---|
| 104 | } |
|---|
| 105 | |
|---|
| 106 | gb_cache::gb_cache() |
|---|
| 107 | : entries(ARB_calloc<gb_cache_entry>(GB_MAX_CACHED_ENTRIES)), |
|---|
| 108 | firstfree_entry(1), |
|---|
| 109 | newest_entry(0), |
|---|
| 110 | oldest_entry(0), |
|---|
| 111 | sum_data_size(0), |
|---|
| 112 | max_data_size(GB_TOTAL_CACHE_SIZE), |
|---|
| 113 | big_data_min_size(max_data_size / 4) |
|---|
| 114 | { |
|---|
| 115 | for (gb_cache_idx i=0; i<GB_MAX_CACHED_ENTRIES-1; i++) { |
|---|
| 116 | entries[i].next = i+1; |
|---|
| 117 | } |
|---|
| 118 | |
|---|
| 119 | #if defined(GEN_CACHE_STATS) |
|---|
| 120 | not_reused = GBS_create_hash(1000, GB_MIND_CASE); |
|---|
| 121 | reused = GBS_create_hash(1000, GB_MIND_CASE); |
|---|
| 122 | reuse_sum = GBS_create_hash(1000, GB_MIND_CASE); |
|---|
| 123 | #endif // GEN_CACHE_STATS |
|---|
| 124 | } |
|---|
| 125 | |
|---|
| 126 | #if defined(GEN_CACHE_STATS) |
|---|
| 127 | static void sum_hash_values(const char */*key*/, long val, void *client_data) { |
|---|
| 128 | size_t *sum = (size_t*)client_data; |
|---|
| 129 | *sum += val; |
|---|
| 130 | } |
|---|
| 131 | static void list_hash_entries(const char *key, long val, void *client_data) { |
|---|
| 132 | if (client_data) { |
|---|
| 133 | GB_HASH *reuse_sum_hash = (GB_HASH*)client_data; |
|---|
| 134 | long reuse_sum = GBS_read_hash(reuse_sum_hash, key); |
|---|
| 135 | |
|---|
| 136 | printf("%s %li (%5.2f)\n", key, val, (double)reuse_sum/val); |
|---|
| 137 | } |
|---|
| 138 | else { |
|---|
| 139 | printf("%s %li\n", key, val); |
|---|
| 140 | } |
|---|
| 141 | } |
|---|
| 142 | #endif // GEN_CACHE_STATS |
|---|
| 143 | |
|---|
| 144 | gb_cache::~gb_cache() { |
|---|
| 145 | if (entries) { |
|---|
| 146 | gb_assert(newest_entry == 0); // cache has to be flushed before! |
|---|
| 147 | gb_assert(sum_data_size == 0); |
|---|
| 148 | freenull(entries); |
|---|
| 149 | |
|---|
| 150 | #if defined(GEN_CACHE_STATS) |
|---|
| 151 | size_t NotReUsed = 0; |
|---|
| 152 | size_t ReUsed = 0; |
|---|
| 153 | size_t ReUseSum = 0; |
|---|
| 154 | |
|---|
| 155 | GBS_hash_do_const_loop(reuse_sum, sum_hash_values, &ReUseSum); |
|---|
| 156 | GBS_hash_do_const_loop(not_reused, sum_hash_values, &NotReUsed); |
|---|
| 157 | GBS_hash_do_const_loop(reused, sum_hash_values, &ReUsed); |
|---|
| 158 | |
|---|
| 159 | size_t overall = NotReUsed+ReUsed; |
|---|
| 160 | |
|---|
| 161 | printf("Cache stats:\n" |
|---|
| 162 | "Overall entries: %zu\n" |
|---|
| 163 | "Reused entries: %zu (%5.2f%%)\n" |
|---|
| 164 | "Mean reuse count: %5.2f\n", |
|---|
| 165 | overall, |
|---|
| 166 | ReUsed, (double)ReUsed/overall*100.0, |
|---|
| 167 | (double)ReUseSum/ReUsed); |
|---|
| 168 | |
|---|
| 169 | printf("Not reused:\n"); GBS_hash_do_const_sorted_loop(not_reused, list_hash_entries, GBS_HCF_sortedByKey, NULp); |
|---|
| 170 | printf("Reused:\n"); GBS_hash_do_const_sorted_loop(reused, list_hash_entries, GBS_HCF_sortedByKey, reuse_sum); |
|---|
| 171 | |
|---|
| 172 | GBS_free_hash(not_reused); |
|---|
| 173 | GBS_free_hash(reused); |
|---|
| 174 | GBS_free_hash(reuse_sum); |
|---|
| 175 | #endif // GEN_CACHE_STATS |
|---|
| 176 | } |
|---|
| 177 | } |
|---|
| 178 | |
|---|
| 179 | char *gb_read_cache(GBENTRY *gbe) { |
|---|
| 180 | char *cached_data = NULp; |
|---|
| 181 | gb_cache_idx index = gbe->cache_index; |
|---|
| 182 | |
|---|
| 183 | if (index) { |
|---|
| 184 | gb_cache& cache = GB_MAIN(gbe)->cache; |
|---|
| 185 | gb_cache_entry& entry = unlink_cache_entry(cache, index); |
|---|
| 186 | gb_assert(entry.gbe == gbe); |
|---|
| 187 | |
|---|
| 188 | // check validity |
|---|
| 189 | if (gbe->update_date() > entry.clock) { |
|---|
| 190 | flush_cache_entry(cache, index); |
|---|
| 191 | } |
|---|
| 192 | else { |
|---|
| 193 | link_cache_entry_to_top(cache, index); |
|---|
| 194 | cached_data = entry.data; |
|---|
| 195 | #if defined(GEN_CACHE_STATS) |
|---|
| 196 | entry.reused++; |
|---|
| 197 | #endif // GEN_CACHE_STATS |
|---|
| 198 | } |
|---|
| 199 | } |
|---|
| 200 | |
|---|
| 201 | return cached_data; |
|---|
| 202 | } |
|---|
| 203 | |
|---|
| 204 | void gb_free_cache(GB_MAIN_TYPE *Main, GBENTRY *gbe) { |
|---|
| 205 | gb_cache_idx index = gbe->cache_index; |
|---|
| 206 | |
|---|
| 207 | if (index) { |
|---|
| 208 | gb_cache& cache = Main->cache; |
|---|
| 209 | unlink_cache_entry(cache, index); |
|---|
| 210 | flush_cache_entry(cache, index); |
|---|
| 211 | } |
|---|
| 212 | } |
|---|
| 213 | |
|---|
| 214 | static void gb_uncache(GBCONTAINER *gbc); |
|---|
| 215 | void gb_uncache(GBENTRY *gbe) { gb_free_cache(GB_MAIN(gbe), gbe); } |
|---|
| 216 | |
|---|
| 217 | inline void gb_uncache(GBDATA *gbd) { |
|---|
| 218 | if (gbd->is_container()) gb_uncache(gbd->as_container()); |
|---|
| 219 | else gb_uncache(gbd->as_entry()); |
|---|
| 220 | } |
|---|
| 221 | static void gb_uncache(GBCONTAINER *gbc) { |
|---|
| 222 | for (GBDATA *gb_child = GB_child(gbc); gb_child; gb_child = GB_nextChild(gb_child)) { |
|---|
| 223 | gb_uncache(gb_child); |
|---|
| 224 | } |
|---|
| 225 | } |
|---|
| 226 | void GB_flush_cache(GBDATA *gbd) { |
|---|
| 227 | // flushes cache of DB-entry or -subtree |
|---|
| 228 | gb_uncache(gbd); |
|---|
| 229 | } |
|---|
| 230 | |
|---|
| 231 | static char *cache_free_some_memory(gb_cache& cache, size_t needed_mem) { |
|---|
| 232 | // free up cache entries until |
|---|
| 233 | // - at least 'needed_mem' bytes are available and |
|---|
| 234 | // - at least one free cache entry exists |
|---|
| 235 | // (if one of the free'd entries has exactly 'needed_mem' bytes size, |
|---|
| 236 | // it will be returned and can be re-used or has to be freed) |
|---|
| 237 | |
|---|
| 238 | long avail_mem = (long)cache.max_data_size - (long)cache.sum_data_size; // may be negative! |
|---|
| 239 | long need_to_free = needed_mem-avail_mem; |
|---|
| 240 | char *data = NULp; |
|---|
| 241 | |
|---|
| 242 | // ignore really big requests (such cache entries will |
|---|
| 243 | // be appended to end of cache list and flushed quickly) |
|---|
| 244 | if (need_to_free>(long)cache.sum_data_size) need_to_free = 0; |
|---|
| 245 | |
|---|
| 246 | while ((!cache.firstfree_entry || need_to_free>0) && cache.oldest_entry) { |
|---|
| 247 | gb_cache_idx index = cache.oldest_entry; |
|---|
| 248 | gb_assert(index); |
|---|
| 249 | |
|---|
| 250 | gb_cache_entry& entry = unlink_cache_entry(cache, index); |
|---|
| 251 | |
|---|
| 252 | need_to_free -= entry.sizeof_data; |
|---|
| 253 | if (entry.sizeof_data == needed_mem) reassign(data, entry.data); |
|---|
| 254 | flush_cache_entry(cache, index); |
|---|
| 255 | } |
|---|
| 256 | |
|---|
| 257 | return data; |
|---|
| 258 | } |
|---|
| 259 | |
|---|
| 260 | char *gb_alloc_cache_index(GBENTRY *gbe, size_t size) { |
|---|
| 261 | gb_assert(gbe->cache_index == 0); |
|---|
| 262 | |
|---|
| 263 | gb_cache& cache = GB_MAIN(gbe)->cache; |
|---|
| 264 | char *data = cache_free_some_memory(cache, size); |
|---|
| 265 | gb_cache_idx index = cache.firstfree_entry; |
|---|
| 266 | |
|---|
| 267 | gb_assert(index); |
|---|
| 268 | gb_cache_entry& entry = cache.entries[index]; |
|---|
| 269 | |
|---|
| 270 | cache.firstfree_entry = entry.next; // remove free element from free-list |
|---|
| 271 | entry.next = 0; |
|---|
| 272 | |
|---|
| 273 | // create data |
|---|
| 274 | if (!data) ARB_alloc(data, size); |
|---|
| 275 | |
|---|
| 276 | entry.sizeof_data = size; |
|---|
| 277 | entry.data = data; |
|---|
| 278 | entry.gbe = gbe; |
|---|
| 279 | entry.clock = gbe->update_date(); |
|---|
| 280 | |
|---|
| 281 | #if defined(GEN_CACHE_STATS) |
|---|
| 282 | entry.reused = 0; |
|---|
| 283 | entry.dbpath = ARB_strdup(GB_get_db_path(gbe)); |
|---|
| 284 | #endif // GEN_CACHE_STATS |
|---|
| 285 | |
|---|
| 286 | gbe->cache_index = index; |
|---|
| 287 | |
|---|
| 288 | link_cache_entry_to_top(cache, index); |
|---|
| 289 | cache.sum_data_size += size; |
|---|
| 290 | |
|---|
| 291 | return data; |
|---|
| 292 | } |
|---|
| 293 | |
|---|
| 294 | char *GB_set_cache_size(GBDATA *gbd, size_t size) { |
|---|
| 295 | gb_cache& cache = GB_MAIN(gbd)->cache; |
|---|
| 296 | |
|---|
| 297 | cache.max_data_size = size; |
|---|
| 298 | cache.big_data_min_size = cache.max_data_size / 4; |
|---|
| 299 | return NULp; |
|---|
| 300 | } |
|---|
| 301 | |
|---|