]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
Merge pull request #7042 from vcaputo/iteratedcache
authorLennart Poettering <lennart@poettering.net>
Thu, 1 Feb 2018 17:08:50 +0000 (18:08 +0100)
committerGitHub <noreply@github.com>
Thu, 1 Feb 2018 17:08:50 +0000 (18:08 +0100)
RFC: Optionally cache hashmap iterated results

src/basic/hashmap.c
src/basic/hashmap.h
src/journal/journal-internal.h
src/journal/sd-journal.c
src/test/test-hashmap.c

index cc4423b2af10f960a608daabd42126f85b0a7072..32be96455de1e2983a358ab4e4a239c4459d1126 100644 (file)
@@ -229,6 +229,8 @@ struct HashmapBase {
         unsigned n_direct_entries:3; /* Number of entries in direct storage.
                                       * Only valid if !has_indirect. */
         bool from_pool:1;            /* whether was allocated from mempool */
+        bool dirty:1;                /* whether dirtied since last iterated_cache_get() */
+        bool cached:1;               /* whether this hashmap is being cached */
         HASHMAP_DEBUG_FIELDS         /* optional hashmap_debug_info */
 };
 
@@ -248,6 +250,17 @@ struct Set {
         struct HashmapBase b;
 };
 
+typedef struct CacheMem {
+        const void **ptr;
+        size_t n_populated, n_allocated;
+        bool active:1;
+} CacheMem;
+
+struct IteratedCache {
+        HashmapBase *hashmap;
+        CacheMem keys, values;
+};
+
 DEFINE_MEMPOOL(hashmap_pool,         Hashmap,        8);
 DEFINE_MEMPOOL(ordered_hashmap_pool, OrderedHashmap, 8);
 /* No need for a separate Set pool */
@@ -351,6 +364,11 @@ static unsigned base_bucket_hash(HashmapBase *h, const void *p) {
 }
 #define bucket_hash(h, p) base_bucket_hash(HASHMAP_BASE(h), p)
 
+static inline void base_set_dirty(HashmapBase *h) {
+        h->dirty = true;
+}
+#define hashmap_set_dirty(h) base_set_dirty(HASHMAP_BASE(h))
+
 static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) {
         static uint8_t current[HASH_KEY_SIZE];
         static bool current_initialized = false;
@@ -568,6 +586,7 @@ static void base_remove_entry(HashmapBase *h, unsigned idx) {
 
         bucket_mark_free(h, prev);
         n_entries_dec(h);
+        base_set_dirty(h);
 }
 #define remove_entry(h, idx) base_remove_entry(HASHMAP_BASE(h), idx)
 
@@ -737,6 +756,25 @@ bool set_iterate(Set *s, Iterator *i, void **value) {
              (idx != IDX_NIL); \
              (idx) = hashmap_iterate_entry((h), &(i)))
 
+IteratedCache *internal_hashmap_iterated_cache_new(HashmapBase *h) {
+        IteratedCache *cache;
+
+        assert(h);
+        assert(!h->cached);
+
+        if (h->cached)
+                return NULL;
+
+        cache = new0(IteratedCache, 1);
+        if (!cache)
+                return NULL;
+
+        cache->hashmap = h;
+        h->cached = true;
+
+        return cache;
+}
+
 static void reset_direct_storage(HashmapBase *h) {
         const struct hashmap_type_info *hi = &hashmap_type_info[h->type];
         void *p;
@@ -897,6 +935,8 @@ void internal_hashmap_clear(HashmapBase *h) {
                 OrderedHashmap *lh = (OrderedHashmap*) h;
                 lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL;
         }
+
+        base_set_dirty(h);
 }
 
 void internal_hashmap_clear_free(HashmapBase *h) {
@@ -1041,6 +1081,8 @@ static int hashmap_base_put_boldly(HashmapBase *h, unsigned idx,
         h->debug.max_entries = MAX(h->debug.max_entries, n_entries(h));
 #endif
 
+        base_set_dirty(h);
+
         return 1;
 }
 #define hashmap_put_boldly(h, idx, swap, may_resize) \
@@ -1277,6 +1319,8 @@ int hashmap_replace(Hashmap *h, const void *key, void *value) {
 #endif
                 e->b.key = key;
                 e->value = value;
+                hashmap_set_dirty(h);
+
                 return 0;
         }
 
@@ -1299,6 +1343,8 @@ int hashmap_update(Hashmap *h, const void *key, void *value) {
 
         e = plain_bucket_at(h, idx);
         e->value = value;
+        hashmap_set_dirty(h);
+
         return 0;
 }
 
@@ -1851,3 +1897,95 @@ int set_put_strsplit(Set *s, const char *v, const char *separators, ExtractFlags
                         return r;
         }
 }
+
+/* expand the cachemem if needed, return true if newly (re)activated. */
+static int cachemem_maintain(CacheMem *mem, unsigned size) {
+        int r = false;
+
+        assert(mem);
+
+        if (!GREEDY_REALLOC(mem->ptr, mem->n_allocated, size)) {
+                if (size > 0)
+                        return -ENOMEM;
+        }
+
+        if (!mem->active)
+                mem->active = r = true;
+
+        return r;
+}
+
+int iterated_cache_get(IteratedCache *cache, const void ***res_keys, const void ***res_values, unsigned *res_n_entries) {
+        bool sync_keys = false, sync_values = false;
+        unsigned size;
+        int r;
+
+        assert(cache);
+        assert(cache->hashmap);
+
+        size = n_entries(cache->hashmap);
+
+        if (res_keys) {
+                r = cachemem_maintain(&cache->keys, size);
+                if (r < 0)
+                        return r;
+
+                sync_keys = r;
+        } else
+                cache->keys.active = false;
+
+        if (res_values) {
+                r = cachemem_maintain(&cache->values, size);
+                if (r < 0)
+                        return r;
+
+                sync_values = r;
+        } else
+                cache->values.active = false;
+
+        if (cache->hashmap->dirty) {
+                if (cache->keys.active)
+                        sync_keys = true;
+                if (cache->values.active)
+                        sync_values = true;
+
+                cache->hashmap->dirty = false;
+        }
+
+        if (sync_keys || sync_values) {
+                unsigned i, idx;
+                Iterator iter;
+
+                i = 0;
+                HASHMAP_FOREACH_IDX(idx, cache->hashmap, iter) {
+                        struct hashmap_base_entry *e;
+
+                        e = bucket_at(cache->hashmap, idx);
+
+                        if (sync_keys)
+                                cache->keys.ptr[i] = e->key;
+                        if (sync_values)
+                                cache->values.ptr[i] = entry_value(cache->hashmap, e);
+                        i++;
+                }
+        }
+
+        if (res_keys)
+                *res_keys = cache->keys.ptr;
+        if (res_values)
+                *res_values = cache->values.ptr;
+        if (res_n_entries)
+                *res_n_entries = size;
+
+        return 0;
+}
+
+IteratedCache *iterated_cache_free(IteratedCache *cache) {
+        if (cache) {
+                free(cache->keys.ptr);
+                free(cache->values.ptr);
+                free(cache);
+        }
+
+        return NULL;
+}
index 0eb763944cd4932ee2a5e19aa2a223b09cba5b8a..b674910397881c3249319f2fbeae7ae3879a9bfe 100644 (file)
@@ -53,6 +53,8 @@ typedef struct Hashmap Hashmap;               /* Maps keys to values */
 typedef struct OrderedHashmap OrderedHashmap; /* Like Hashmap, but also remembers entry insertion order */
 typedef struct Set Set;                       /* Stores just keys */
 
+typedef struct IteratedCache IteratedCache;   /* Caches the iterated order of one of the above */
+
 /* Ideally the Iterator would be an opaque struct, but it is instantiated
  * by hashmap users, so the definition has to be here. Do not use its fields
  * directly. */
@@ -126,6 +128,9 @@ static inline OrderedHashmap *ordered_hashmap_free_free_free(OrderedHashmap *h)
         return (void*)hashmap_free_free_free(PLAIN_HASHMAP(h));
 }
 
+IteratedCache *iterated_cache_free(IteratedCache *cache);
+int iterated_cache_get(IteratedCache *cache, const void ***res_keys, const void ***res_values, unsigned *res_n_entries);
+
 HashmapBase *internal_hashmap_copy(HashmapBase *h);
 static inline Hashmap *hashmap_copy(Hashmap *h) {
         return (Hashmap*) internal_hashmap_copy(HASHMAP_BASE(h));
@@ -139,6 +144,14 @@ int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct h
 #define hashmap_ensure_allocated(h, ops) internal_hashmap_ensure_allocated(h, ops  HASHMAP_DEBUG_SRC_ARGS)
 #define ordered_hashmap_ensure_allocated(h, ops) internal_ordered_hashmap_ensure_allocated(h, ops  HASHMAP_DEBUG_SRC_ARGS)
 
+IteratedCache *internal_hashmap_iterated_cache_new(HashmapBase *h);
+static inline IteratedCache *hashmap_iterated_cache_new(Hashmap *h) {
+        return (IteratedCache*) internal_hashmap_iterated_cache_new(HASHMAP_BASE(h));
+}
+static inline IteratedCache *ordered_hashmap_iterated_cache_new(OrderedHashmap *h) {
+        return (IteratedCache*) internal_hashmap_iterated_cache_new(HASHMAP_BASE(h));
+}
+
 int hashmap_put(Hashmap *h, const void *key, void *value);
 static inline int ordered_hashmap_put(OrderedHashmap *h, const void *key, void *value) {
         return hashmap_put(PLAIN_HASHMAP(h), key, value);
@@ -394,3 +407,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free);
 #define _cleanup_ordered_hashmap_free_ _cleanup_(ordered_hashmap_freep)
 #define _cleanup_ordered_hashmap_free_free_ _cleanup_(ordered_hashmap_free_freep)
 #define _cleanup_ordered_hashmap_free_free_free_ _cleanup_(ordered_hashmap_free_free_freep)
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(IteratedCache*, iterated_cache_free);
+
+#define _cleanup_iterated_cache_free_ _cleanup_(iterated_cache_freep)
index 5ec87e83d70d3a08389393b72188e7b8479de4de..d0d2842cc47e4e298b0ad59472b4bd99e0e1439b 100644 (file)
@@ -89,6 +89,7 @@ struct sd_journal {
         char *prefix;
 
         OrderedHashmap *files;
+        IteratedCache *files_cache;
         MMapCache *mmap;
 
         Location current_location;
index 6da7bf8e81a41068d339dd47985413337466d16a..a8812c9af83c0f4710f5dab33788ee8bb1c67457 100644 (file)
@@ -820,15 +820,21 @@ static int next_beyond_location(sd_journal *j, JournalFile *f, direction_t direc
 }
 
 static int real_journal_next(sd_journal *j, direction_t direction) {
-        JournalFile *f, *new_file = NULL;
-        Iterator i;
+        JournalFile *new_file = NULL;
+        unsigned i, n_files;
+        const void **files;
         Object *o;
         int r;
 
         assert_return(j, -EINVAL);
         assert_return(!journal_pid_changed(j), -ECHILD);
 
-        ORDERED_HASHMAP_FOREACH(f, j->files, i) {
+        r = iterated_cache_get(j->files_cache, NULL, &files, &n_files);
+        if (r < 0)
+                return r;
+
+        for (i = 0; i < n_files; i++) {
+                JournalFile *f = (JournalFile *)files[i];
                 bool found;
 
                 r = next_beyond_location(j, f, direction);
@@ -1737,9 +1743,13 @@ static sd_journal *journal_new(int flags, const char *path) {
         }
 
         j->files = ordered_hashmap_new(&string_hash_ops);
+        if (!j->files)
+                goto fail;
+
+        j->files_cache = ordered_hashmap_iterated_cache_new(j->files);
         j->directories_by_path = hashmap_new(&string_hash_ops);
         j->mmap = mmap_cache_new();
-        if (!j->files || !j->directories_by_path || !j->mmap)
+        if (!j->files_cache || !j->directories_by_path || !j->mmap)
                 goto fail;
 
         return j;
@@ -1985,6 +1995,7 @@ _public_ void sd_journal_close(sd_journal *j) {
         sd_journal_flush_matches(j);
 
         ordered_hashmap_free_with_destructor(j->files, journal_file_close);
+        iterated_cache_free(j->files_cache);
 
         while ((d = hashmap_first(j->directories_by_path)))
                 remove_directory(j, d);
index dd9195425efd9f7e690fdbdb003e73cae5b503db..16ca27cd5fd187803d4f841aacf508e97cd2d78d 100644 (file)
@@ -80,6 +80,63 @@ static void test_string_compare_func(void) {
         assert_se(string_compare_func("fred", "fred") == 0);
 }
 
+static void compare_cache(Hashmap *map, IteratedCache *cache) {
+        const void **keys = NULL, **values = NULL;
+        unsigned num, idx;
+        Iterator iter;
+        void *k, *v;
+
+        assert_se(iterated_cache_get(cache, &keys, &values, &num) == 0);
+        assert_se(num == 0 || keys);
+        assert_se(num == 0 || values);
+
+        idx = 0;
+        HASHMAP_FOREACH_KEY(v, k, map, iter) {
+                assert_se(v == values[idx]);
+                assert_se(k == keys[idx]);
+
+                idx++;
+        }
+
+        assert_se(idx == num);
+}
+
+static void test_iterated_cache(void) {
+        Hashmap *m;
+        IteratedCache *c;
+
+        assert_se(m = hashmap_new(NULL));
+        assert_se(c = hashmap_iterated_cache_new(m));
+        compare_cache(m, c);
+
+        for (int stage = 0; stage < 100; stage++) {
+
+                for (int i = 0; i < 100; i++) {
+                        int foo = stage * 1000 + i;
+
+                        assert_se(hashmap_put(m, INT_TO_PTR(foo), INT_TO_PTR(foo + 777)) == 1);
+                }
+
+                compare_cache(m, c);
+
+                if (!(stage % 10)) {
+                        for (int i = 0; i < 100; i++) {
+                                int foo = stage * 1000 + i;
+
+                                assert_se(hashmap_remove(m, INT_TO_PTR(foo)) == INT_TO_PTR(foo + 777));
+                        }
+
+                        compare_cache(m, c);
+                }
+        }
+
+        hashmap_clear(m);
+        compare_cache(m, c);
+
+        assert_se(hashmap_free(m) == NULL);
+        assert_se(iterated_cache_free(c) == NULL);
+}
+
 int main(int argc, const char *argv[]) {
         test_hashmap_funcs();
         test_ordered_hashmap_funcs();
@@ -89,4 +146,5 @@ int main(int argc, const char *argv[]) {
         test_uint64_compare_func();
         test_trivial_compare_func();
         test_string_compare_func();
+        test_iterated_cache();
 }