[gegl] buffer: don't count cloned tiles towards cache_total more than once



commit 67b58c73912a83ba2a1e07cae289eff70aa862a8
Author: Ell <ell_se yahoo com>
Date:   Tue Dec 12 12:21:40 2017 -0500

    buffer: don't count cloned tiles towards cache_total more than once
    
    In GeglTileHandlerCache, we currently count the size of all tiles
    individually towards the cache's total size, even when some of the
    tiles are clones of each other, and hence share the same data.
    
    Improve this by counting the size of groups of mutually cloned
    tiles only once, to reflect their actual memory consumption.  The
    implementation uses a lock-free shared counter, similar to the way
    we do COW, and has low overhead.

 gegl/buffer/gegl-buffer-private.h     |   11 +++-
 gegl/buffer/gegl-tile-handler-cache.c |  117 ++++++++++++++++++++-------------
 gegl/buffer/gegl-tile-handler-cache.h |   19 +++--
 gegl/buffer/gegl-tile-handler-empty.c |    7 ++
 gegl/buffer/gegl-tile.c               |   53 +++++++++++----
 5 files changed, 136 insertions(+), 71 deletions(-)
---
diff --git a/gegl/buffer/gegl-buffer-private.h b/gegl/buffer/gegl-buffer-private.h
index e08e9c3..f40b8d6 100644
--- a/gegl/buffer/gegl-buffer-private.h
+++ b/gegl/buffer/gegl-buffer-private.h
@@ -168,8 +168,12 @@ struct _GeglTile
   gint             is_zero_tile:1;
 
   gint             clone_state; /* tile clone/unclone state & spinlock */
-  gint            *n_clones;    /* shared atomic counter among all tiles
-                                 * sharing the same data
+  gint            *n_clones;    /* an array of two atomic counters, shared
+                                 * among all tiles sharing the same data.
+                                 * the first counter indicates the number of
+                                 * tiles sharing the data, and the second
+                                 * counter indicates how many of those tiles
+                                 * are in the cache.
                                  */
 
   /* called when the tile is about to be destroyed */
@@ -200,6 +204,9 @@ gboolean gegl_buffer_scan_compatible (GeglBuffer *bufferA,
 #define gegl_tile_get_data(tile)  ((tile)->data)
 #endif
 
+#define gegl_tile_n_clones(tile)         (&(tile)->n_clones[0])
+#define gegl_tile_n_cached_clones(tile)  (&(tile)->n_clones[1])
+
 /* computes the positive integer remainder (also for negative dividends)
  */
 #define GEGL_REMAINDER(dividend, divisor) \
diff --git a/gegl/buffer/gegl-tile-handler-cache.c b/gegl/buffer/gegl-tile-handler-cache.c
index becb7e3..3bc0132 100644
--- a/gegl/buffer/gegl-tile-handler-cache.c
+++ b/gegl/buffer/gegl-tile-handler-cache.c
@@ -87,13 +87,13 @@ static void       gegl_tile_handler_cache_invalidate (GeglTileHandlerCache *cach
                                                       gint                  z);
 
 
-static GMutex       mutex                 = { 0, };
-static GQueue      *cache_queue           = NULL;
-static gint         cache_wash_percentage = 20;
-static guint64      cache_total           = 0; /* approximate amount of bytes stored */
+static GMutex             mutex                 = { 0, };
+static GQueue            *cache_queue           = NULL;
+static gint               cache_wash_percentage = 20;
+static volatile guintptr  cache_total           = 0; /* approximate amount of bytes stored */
 #ifdef GEGL_DEBUG_CACHE_HITS
-static gint         cache_hits            = 0;
-static gint         cache_misses          = 0;
+static gint               cache_hits            = 0;
+static gint               cache_misses          = 0;
 #endif
 
 
@@ -117,6 +117,31 @@ gegl_tile_handler_cache_init (GeglTileHandlerCache *cache)
 }
 
 static void
+drop_hot_tile (GeglTile *tile)
+{
+  GeglTileStorage *storage = tile->tile_storage;
+
+  if (storage)
+    {
+#if 0 /* the storage's mutex should already be locked at this point */
+      if (gegl_config_threads()>1)
+        g_rec_mutex_lock (&storage->mutex);
+#endif
+
+      if (storage->hot_tile == tile)
+        {
+          gegl_tile_unref (storage->hot_tile);
+          storage->hot_tile = NULL;
+        }
+
+#if 0
+      if (gegl_config_threads()>1)
+        g_rec_mutex_unlock (&storage->mutex);
+#endif
+    }
+}
+
+static void
 gegl_tile_handler_cache_reinit (GeglTileHandlerCache *cache)
 {
   CacheItem            *item;
@@ -140,8 +165,11 @@ gegl_tile_handler_cache_reinit (GeglTileHandlerCache *cache)
       item = (CacheItem *) value;
       if (item->tile)
         {
-          cache_total -= item->tile->size;
-          gegl_tile_mark_as_stored (item->tile); // to avoid saving 
+          if (g_atomic_int_dec_and_test (gegl_tile_n_cached_clones (item->tile)))
+            g_atomic_pointer_add (&cache_total, -item->tile->size);
+          drop_hot_tile (item->tile);
+          gegl_tile_mark_as_stored (item->tile); // to avoid saving
+          item->tile->tile_storage = NULL;
           gegl_tile_unref (item->tile);
           cache->count--;
         }
@@ -304,11 +332,10 @@ gegl_tile_handler_cache_wash (GeglTileHandlerCache *cache)
       CacheItem *item = LINK_GET_ITEM (link);
       GeglTile  *tile = item->tile;
 
-      if (!gegl_tile_is_stored (tile))
+      if (tile->tile_storage && ! gegl_tile_is_stored (tile))
         {
           last_dirty = tile;
-          if (last_dirty->tile_storage)
-            g_object_ref (last_dirty->tile_storage);
+          g_object_ref (last_dirty->tile_storage);
           gegl_tile_ref (last_dirty);
 
           break;
@@ -320,7 +347,7 @@ gegl_tile_handler_cache_wash (GeglTileHandlerCache *cache)
   if (last_dirty != NULL)
     {
       gegl_tile_store (last_dirty);
-      g_clear_object (&last_dirty->tile_storage);
+      g_object_unref (last_dirty->tile_storage);
       gegl_tile_unref (last_dirty);
       return TRUE;
     }
@@ -397,31 +424,6 @@ gegl_tile_handler_cache_has_tile (GeglTileHandlerCache *cache,
   return FALSE;
 }
 
-static void
-drop_hot_tile (GeglTile *tile)
-{
-  GeglTileStorage *storage = tile->tile_storage;
-
-  if (storage)
-    {
-#if 0 /* the storage's mutex should already be locked at this point */
-      if (gegl_config_threads()>1)
-        g_rec_mutex_lock (&storage->mutex);
-#endif
-
-      if (storage->hot_tile == tile)
-        {
-          gegl_tile_unref (storage->hot_tile);
-          storage->hot_tile = NULL;
-        }
-
-#if 0
-      if (gegl_config_threads()>1)
-        g_rec_mutex_unlock (&storage->mutex);
-#endif
-    }
-}
-
 static gboolean
 gegl_tile_handler_cache_trim (GeglTileHandlerCache *cache)
 {
@@ -429,7 +431,8 @@ gegl_tile_handler_cache_trim (GeglTileHandlerCache *cache)
 
   link = g_queue_peek_tail_link (cache_queue);
 
-  while (cache_total > gegl_config()->tile_cache_size)
+  while ((guintptr) g_atomic_pointer_get (&cache_total) >
+         gegl_config ()->tile_cache_size)
     {
       CacheItem       *last_writable;
       GeglTile        *tile;
@@ -480,13 +483,16 @@ gegl_tile_handler_cache_trim (GeglTileHandlerCache *cache)
       prev_link = g_list_previous (link);
       g_queue_unlink (cache_queue, link);
       g_hash_table_remove (last_writable->handler->items, last_writable);
-      cache_total -= tile->size;
+      if (g_atomic_int_dec_and_test (gegl_tile_n_cached_clones (tile)))
+        g_atomic_pointer_add (&cache_total, -tile->size);
       last_writable->handler->count--;
       /* drop_hot_tile (tile); */ /* XXX:  no use in trying to drop the hot
                                    * tile, since this tile can't be it --
                                    * the hot tile will have a ref-count of
                                    * at least two.
                                    */
+      gegl_tile_store (tile);
+      tile->tile_storage = NULL;
       gegl_tile_unref (tile);
 
       if (dirty && gegl_config_threads()>1)
@@ -511,7 +517,8 @@ gegl_tile_handler_cache_invalidate (GeglTileHandlerCache *cache,
   item = cache_lookup (cache, x, y, z);
   if (item)
     {
-      cache_total -= item->tile->size;
+      if (g_atomic_int_dec_and_test (gegl_tile_n_cached_clones (item->tile)))
+        g_atomic_pointer_add (&cache_total, -item->tile->size);
       cache->count--;
 
       g_queue_unlink (cache_queue, &item->link);
@@ -520,8 +527,8 @@ gegl_tile_handler_cache_invalidate (GeglTileHandlerCache *cache,
       g_mutex_unlock (&mutex);
 
       drop_hot_tile (item->tile);
-      item->tile->tile_storage = NULL;
       gegl_tile_mark_as_stored (item->tile); /* to cheat it out of being stored */
+      item->tile->tile_storage = NULL;
       gegl_tile_unref (item->tile);
 
       g_slice_free (CacheItem, item);
@@ -543,7 +550,8 @@ gegl_tile_handler_cache_void (GeglTileHandlerCache *cache,
   item = cache_lookup (cache, x, y, z);
   if (item)
     {
-      cache_total -= item->tile->size;
+      if (g_atomic_int_dec_and_test (gegl_tile_n_cached_clones (item->tile)))
+        g_atomic_pointer_add (&cache_total, -item->tile->size);
       g_queue_unlink (cache_queue, &item->link);
       g_hash_table_remove (cache->items, item);
       cache->count--;
@@ -554,6 +562,7 @@ gegl_tile_handler_cache_void (GeglTileHandlerCache *cache,
     {
       drop_hot_tile (item->tile);
       gegl_tile_void (item->tile);
+      item->tile->tile_storage = NULL;
       gegl_tile_unref (item->tile);
     }
 
@@ -578,18 +587,19 @@ gegl_tile_handler_cache_insert (GeglTileHandlerCache *cache,
   item->y         = y;
   item->z         = z;
 
+  // XXX : remove entry if it already exists
+  gegl_tile_handler_cache_void (cache, x, y, z);
+
   tile->x = x;
   tile->y = y;
   tile->z = z;
   tile->tile_storage = cache->tile_storage;
 
-  // XXX : remove entry if it already exists
-  gegl_tile_handler_cache_void (cache, x, y, z);
-
   /* XXX: this is a window when the tile is a zero tile during update */
 
   g_mutex_lock (&mutex);
-  cache_total  += item->tile->size;
+  if (g_atomic_int_add (gegl_tile_n_cached_clones (tile), 1) == 0)
+    g_atomic_pointer_add (&cache_total, tile->size);
   g_queue_push_head_link (cache_queue, &item->link);
 
   cache->count ++;
@@ -601,6 +611,21 @@ gegl_tile_handler_cache_insert (GeglTileHandlerCache *cache,
   g_mutex_unlock (&mutex);
 }
 
+void
+gegl_tile_handler_cache_tile_uncloned (GeglTileHandlerCache *cache,
+                                       GeglTile             *tile)
+{
+  if ((guintptr) g_atomic_pointer_add (&cache_total, tile->size) + tile->size >
+      gegl_config ()->tile_cache_size)
+    {
+      g_mutex_lock (&mutex);
+
+      gegl_tile_handler_cache_trim (cache);
+
+      g_mutex_unlock (&mutex);
+    }
+}
+
 GeglTileHandler *
 gegl_tile_handler_cache_new (void)
 {
diff --git a/gegl/buffer/gegl-tile-handler-cache.h b/gegl/buffer/gegl-tile-handler-cache.h
index 5ed4dda..00a69f1 100644
--- a/gegl/buffer/gegl-tile-handler-cache.h
+++ b/gegl/buffer/gegl-tile-handler-cache.h
@@ -49,13 +49,16 @@ struct _GeglTileHandlerCacheClass
   GeglTileHandlerClass parent_class;
 };
 
-GType             gegl_tile_handler_cache_get_type (void) G_GNUC_CONST;
-
-GeglTileHandler * gegl_tile_handler_cache_new      (void);
-void              gegl_tile_handler_cache_insert   (GeglTileHandlerCache *cache,
-                                                    GeglTile             *tile,
-                                                    gint                  x,
-                                                    gint                  y,
-                                                    gint                  z);
+GType             gegl_tile_handler_cache_get_type      (void) G_GNUC_CONST;
+
+GeglTileHandler * gegl_tile_handler_cache_new           (void);
+
+void              gegl_tile_handler_cache_insert        (GeglTileHandlerCache *cache,
+                                                         GeglTile             *tile,
+                                                         gint                  x,
+                                                         gint                  y,
+                                                         gint                  z);
+void              gegl_tile_handler_cache_tile_uncloned (GeglTileHandlerCache *cache,
+                                                         GeglTile             *tile);
 
 #endif
diff --git a/gegl/buffer/gegl-tile-handler-empty.c b/gegl/buffer/gegl-tile-handler-empty.c
index 8bec7f6..5ce06a1 100644
--- a/gegl/buffer/gegl-tile-handler-empty.c
+++ b/gegl/buffer/gegl-tile-handler-empty.c
@@ -72,6 +72,13 @@ _new_empty_tile (const gint tile_size)
           allocated_tile->size           = common_empty_size;
           allocated_tile->is_zero_tile   = 1;
 
+          /* avoid counting duplicates of the empty tile towards the total
+           * cache size, both since this is unnecessary, and since they may
+           * have different sizes, which is inconsistent with the duplicate-
+           * tracking cache logic.
+           */
+          (*gegl_tile_n_cached_clones (allocated_tile))++;
+
           g_once_init_leave (&common_tile, allocated_tile);
         }
 
diff --git a/gegl/buffer/gegl-tile.c b/gegl/buffer/gegl-tile.c
index bce28e6..eef7c16 100644
--- a/gegl/buffer/gegl-tile.c
+++ b/gegl/buffer/gegl-tile.c
@@ -34,6 +34,7 @@
 #include "gegl-buffer-private.h"
 #include "gegl-tile-source.h"
 #include "gegl-tile-storage.h"
+#include "gegl-tile-handler-cache.h"
 #include "gegl-config.h"
 
 /* the offset at which the tile data begins, when it shares the same buffer as
@@ -41,7 +42,7 @@
  * that the tile data is similarly aligned.
  */
 #define INLINE_N_ELEMENTS_DATA_OFFSET 16
-G_STATIC_ASSERT (INLINE_N_ELEMENTS_DATA_OFFSET >= sizeof (gint));
+G_STATIC_ASSERT (INLINE_N_ELEMENTS_DATA_OFFSET >= 2 * sizeof (gint));
 
 enum
 {
@@ -70,7 +71,7 @@ void gegl_tile_unref (GeglTile *tile)
    */
   gegl_tile_store (tile);
 
-  if (g_atomic_int_dec_and_test (tile->n_clones))
+  if (g_atomic_int_dec_and_test (gegl_tile_n_clones (tile)))
     { /* no clones */
       if (tile->destroy_notify == (void*)&free_n_clones_directly)
         {
@@ -93,7 +94,7 @@ void gegl_tile_unref (GeglTile *tile)
                 tile->destroy_notify (tile->destroy_notify_data);
             }
 
-          g_slice_free (gint, tile->n_clones);
+          g_slice_free1 (2 * sizeof (gint), tile->n_clones);
         }
     }
 
@@ -120,8 +121,9 @@ gegl_tile_new_bare (void)
 {
   GeglTile *tile = gegl_tile_new_bare_internal ();
 
-  tile->n_clones  = g_slice_new (gint);
-  *tile->n_clones = 1;
+  tile->n_clones                    = g_slice_alloc (2 * sizeof (gint));
+  *gegl_tile_n_clones (tile)        = 1;
+  *gegl_tile_n_cached_clones (tile) = 0;
 
   tile->destroy_notify = (void*)&free_data_directly;
   tile->destroy_notify_data = NULL;
@@ -138,7 +140,6 @@ gegl_tile_dup (GeglTile *src)
 
   src->clone_state   = CLONE_STATE_CLONED;
 
-  tile->tile_storage = src->tile_storage;
   tile->data         = src->data;
   tile->size         = src->size;
   tile->is_zero_tile = src->is_zero_tile;
@@ -148,7 +149,7 @@ gegl_tile_dup (GeglTile *src)
   tile->destroy_notify      = src->destroy_notify;
   tile->destroy_notify_data = src->destroy_notify_data;
 
-  g_atomic_int_inc (tile->n_clones);
+  g_atomic_int_inc (gegl_tile_n_clones (tile));
 
   return tile;
 }
@@ -159,8 +160,9 @@ gegl_tile_new (gint size)
   GeglTile *tile = gegl_tile_new_bare_internal ();
 
   /* allocate a single buffer for both tile->n_clones and tile->data */
-  tile->n_clones  = gegl_malloc (INLINE_N_ELEMENTS_DATA_OFFSET + size);
-  *tile->n_clones = 1;
+  tile->n_clones                    = gegl_malloc (INLINE_N_ELEMENTS_DATA_OFFSET + size);
+  *gegl_tile_n_clones (tile)        = 1;
+  *gegl_tile_n_cached_clones (tile) = 0;
 
   tile->data      = (guchar *) tile->n_clones + INLINE_N_ELEMENTS_DATA_OFFSET;
   tile->size      = size;
@@ -174,19 +176,33 @@ gegl_tile_new (gint size)
 static void
 gegl_tile_unclone (GeglTile *tile)
 {
-  if (*tile->n_clones > 1)
+  if (*gegl_tile_n_clones (tile) > 1)
     {
+      GeglTileHandlerCache *notify_cache = NULL;
+      gboolean              cached;
+
+      cached = tile->tile_storage && tile->tile_storage->cache;
+
+      if (cached)
+        {
+          if (! g_atomic_int_dec_and_test (gegl_tile_n_cached_clones (tile)))
+            notify_cache = tile->tile_storage->cache;
+        }
+
       /* the tile data is shared with other tiles,
        * create a local copy
        */
       if (tile->is_zero_tile)
         {
-          if (g_atomic_int_dec_and_test (tile->n_clones))
+          if (g_atomic_int_dec_and_test (gegl_tile_n_clones (tile)))
             {
               /* someone else uncloned the tile in the meantime, and we're now
                * the last copy; bail.
                */
-              *tile->n_clones = 1;
+              *gegl_tile_n_clones (tile)        = 1;
+              *gegl_tile_n_cached_clones (tile) = cached;
+              if (notify_cache)
+                gegl_tile_handler_cache_tile_uncloned (notify_cache, tile);
               return;
             }
 
@@ -201,25 +217,32 @@ gegl_tile_unclone (GeglTile *tile)
           buf = gegl_malloc (INLINE_N_ELEMENTS_DATA_OFFSET + tile->size);
           memcpy (buf + INLINE_N_ELEMENTS_DATA_OFFSET, tile->data, tile->size);
 
-          if (g_atomic_int_dec_and_test (tile->n_clones))
+          if (g_atomic_int_dec_and_test (gegl_tile_n_clones (tile)))
             {
               /* someone else uncloned the tile in the meantime, and we're now
                * the last copy; bail.
                */
               gegl_free (buf);
-              *tile->n_clones = 1;
+              *gegl_tile_n_clones (tile)        = 1;
+              *gegl_tile_n_cached_clones (tile) = cached;
+              if (notify_cache)
+                gegl_tile_handler_cache_tile_uncloned (notify_cache, tile);
               return;
             }
 
           tile->n_clones = (gint *) buf;
         }
 
-      *tile->n_clones = 1;
+      *gegl_tile_n_clones (tile)        = 1;
+      *gegl_tile_n_cached_clones (tile) = cached;
       tile->data      = (guchar *) tile->n_clones +
                         INLINE_N_ELEMENTS_DATA_OFFSET;
 
       tile->destroy_notify      = (void*)&free_n_clones_directly;
       tile->destroy_notify_data = NULL;
+
+      if (notify_cache)
+        gegl_tile_handler_cache_tile_uncloned (notify_cache, tile);
     }
 }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]