source: tags/ms_r16q3/ARBDB/adcache.cxx

Last change on this file was 15176, checked in by westram, 8 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 9.4 KB
Line 
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
15struct 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
29inline 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
36inline 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
55inline 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
80inline 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
106gb_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)
127static long sum_hash_values(const char */*key*/, long val, void *client_data) {
128    size_t *sum  = (size_t*)client_data;
129    *sum      += val;
130    return val;
131}
132static long list_hash_entries(const char *key, long val, void *client_data) {
133    if (client_data) {
134        GB_HASH *reuse_sum_hash = (GB_HASH*)client_data;
135        long     reuse_sum      = GBS_read_hash(reuse_sum_hash, key);
136
137        printf("%s %li (%5.2f)\n", key, val, (double)reuse_sum/val);
138    }
139    else {
140        printf("%s %li\n", key, val);
141    }
142    return val;
143}
144#endif // GEN_CACHE_STATS
145
146gb_cache::~gb_cache() {
147    if (entries) {
148        gb_assert(newest_entry == 0); // cache has to be flushed before!
149        gb_assert(sum_data_size == 0);
150        freenull(entries);
151
152#if defined(GEN_CACHE_STATS)
153        size_t NotReUsed = 0;
154        size_t ReUsed    = 0;
155        size_t ReUseSum  = 0;
156
157        GBS_hash_do_loop(reuse_sum, sum_hash_values, &ReUseSum);
158        GBS_hash_do_loop(not_reused, sum_hash_values, &NotReUsed);
159        GBS_hash_do_loop(reused, sum_hash_values, &ReUsed);
160
161        size_t overall = NotReUsed+ReUsed;
162
163        printf("Cache stats:\n"
164               "Overall entries:  %zu\n"
165               "Reused entries:   %zu (%5.2f%%)\n"
166               "Mean reuse count: %5.2f\n",
167               overall,
168               ReUsed, (double)ReUsed/overall*100.0,
169               (double)ReUseSum/ReUsed);
170
171        printf("Not reused:\n");
172        GBS_hash_do_sorted_loop(not_reused, list_hash_entries, GBS_HCF_sortedByKey, NULL);
173        printf("Reused:\n");
174        GBS_hash_do_sorted_loop(reused, list_hash_entries, GBS_HCF_sortedByKey, reuse_sum);
175
176        GBS_free_hash(not_reused);
177        GBS_free_hash(reused);
178        GBS_free_hash(reuse_sum);
179#endif // GEN_CACHE_STATS
180    }
181}
182
183char *gb_read_cache(GBENTRY *gbe) {
184    char         *cached_data = NULL;
185    gb_cache_idx  index       = gbe->cache_index;
186
187    if (index) {
188        gb_cache&       cache = GB_MAIN(gbe)->cache;
189        gb_cache_entry& entry = unlink_cache_entry(cache, index);
190        gb_assert(entry.gbe == gbe);
191
192        // check validity
193        if (gbe->update_date() > entry.clock) {
194            flush_cache_entry(cache, index);
195        }
196        else {
197            link_cache_entry_to_top(cache, index);
198            cached_data = entry.data;
199#if defined(GEN_CACHE_STATS)
200            entry.reused++;
201#endif // GEN_CACHE_STATS
202        }
203    }
204
205    return cached_data;
206}
207
208void gb_free_cache(GB_MAIN_TYPE *Main, GBENTRY *gbe) {
209    gb_cache_idx index = gbe->cache_index;
210
211    if (index) {
212        gb_cache& cache = Main->cache;
213        unlink_cache_entry(cache, index);
214        flush_cache_entry(cache, index);
215    }
216}
217
218static void gb_uncache(GBCONTAINER *gbc);
219void gb_uncache(GBENTRY *gbe) { gb_free_cache(GB_MAIN(gbe), gbe); }
220
221inline void gb_uncache(GBDATA *gbd) {
222    if (gbd->is_container()) gb_uncache(gbd->as_container());
223    else                     gb_uncache(gbd->as_entry());
224}
225static void gb_uncache(GBCONTAINER *gbc) {
226    for (GBDATA *gb_child = GB_child(gbc); gb_child; gb_child = GB_nextChild(gb_child)) {
227        gb_uncache(gb_child);
228    }
229}
230void GB_flush_cache(GBDATA *gbd) {
231    // flushes cache of DB-entry or -subtree
232    gb_uncache(gbd);
233}
234
235static char *cache_free_some_memory(gb_cache& cache, size_t needed_mem) {
236    // free up cache entries until
237    // - at least 'needed_mem' bytes are available and
238    // - at least one free cache entry exists
239    // (if one of the free'd entries has exactly 'needed_mem' bytes size,
240    // it will be returned and can be re-used or has to be freed)
241
242    long  avail_mem    = (long)cache.max_data_size - (long)cache.sum_data_size; // may be negative!
243    long  need_to_free = needed_mem-avail_mem;
244    char *data         = NULL;
245
246    // ignore really big requests (such cache entries will
247    // be appended to end of cache list and flushed quickly)
248    if (need_to_free>(long)cache.sum_data_size) need_to_free = 0;
249
250    while ((!cache.firstfree_entry || need_to_free>0) && cache.oldest_entry) {
251        gb_cache_idx index = cache.oldest_entry;
252        gb_assert(index);
253
254        gb_cache_entry& entry = unlink_cache_entry(cache, index);
255
256        need_to_free -= entry.sizeof_data;
257        if (entry.sizeof_data == needed_mem) reassign(data, entry.data);
258        flush_cache_entry(cache, index);
259    }
260
261    return data;
262}
263
264char *gb_alloc_cache_index(GBENTRY *gbe, size_t size) {
265    gb_assert(gbe->cache_index == 0);
266
267    gb_cache&  cache = GB_MAIN(gbe)->cache;
268    char      *data  = cache_free_some_memory(cache, size);
269    gb_cache_idx index = cache.firstfree_entry;
270
271    gb_assert(index);
272    gb_cache_entry& entry = cache.entries[index];
273
274    cache.firstfree_entry = entry.next;             // remove free element from free-list
275    entry.next            = 0;
276
277    // create data
278    if (!data) ARB_alloc(data, size);
279
280    entry.sizeof_data = size;
281    entry.data        = data;
282    entry.gbe         = gbe;
283    entry.clock       = gbe->update_date();
284   
285#if defined(GEN_CACHE_STATS)
286    entry.reused     = 0;
287    entry.dbpath     = ARB_strdup(GB_get_db_path(gbe));
288#endif                                              // GEN_CACHE_STATS
289
290    gbe->cache_index = index;
291
292    link_cache_entry_to_top(cache, index);
293    cache.sum_data_size += size; 
294
295    return data;
296}
297
298char *GB_set_cache_size(GBDATA *gbd, size_t size) {
299    gb_cache& cache = GB_MAIN(gbd)->cache;
300
301    cache.max_data_size     = size;
302    cache.big_data_min_size = cache.max_data_size / 4;
303    return 0;
304}
305
Note: See TracBrowser for help on using the repository browser.