source: branches/profile/ARBDB/adcache.cxx

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