emacs-devel
[Top][All Lists]
Advanced

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

Free images based on allocated memory


From: Andreas Politz
Subject: Free images based on allocated memory
Date: Tue, 29 Jan 2019 19:56:45 +0100
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/26.1 (gnu/linux)

Recently there was a discussion about how to limit the amount of memory
the pdf-tools package uses by means of the image-cache.

The package renders pages to a PNG image, each one having a size of a
couple of MB.  Thus, skimming through a large document can quickly fill
the cache with images in the GB range.

Anyway, it occurred to me, that freeing the cache via clear-image-cache
etc. is much like counting conses and triggering garbage-collection
manually.  So I wondered if there is some interest to add an automatic
eviction of the cache based on it's current size.

For this I've added a draft patch, which has some drawbacks:

1. The accounting is frame-local, which means the cache could still grow
   unbounded as a function of the number of frames.
2. The computation of memory used by an image is very simple.

Andreas

P.S.: Please CC me, since I'm currently not subscribed.

diff --git a/src/image.c b/src/image.c
index bcc61dfccd..85adaa61f8 100644
--- a/src/image.c
+++ b/src/image.c
@@ -1018,6 +1018,8 @@ free_image (struct frame *f, struct image *img)
 
       c->images[img->id] = NULL;
 
+      c->allocated_image_size -= img->allocated_size;
+      eassert (c->allocated_image_size >= 0);
 #ifdef HAVE_XRENDER
       if (img->picture)
         XRenderFreePicture (FRAME_X_DISPLAY (f), img->picture);
@@ -1464,6 +1466,7 @@ make_image_cache (void)
   c->used = c->refcount = 0;
   c->images = xmalloc (c->size * sizeof *c->images);
   c->buckets = xzalloc (IMAGE_CACHE_BUCKETS_SIZE * sizeof *c->buckets);
+  c->allocated_image_size = 0;
   return c;
 }
 
@@ -1540,6 +1543,53 @@ free_image_cache (struct frame *f)
     }
 }
 
+/* Compare images inducing an order of increasing timestamps followed
+   by NULL images . */
+static int
+compare_images_by_timestamp (const void *e0, const void* e1)
+{
+  const struct image *i0 = *((const struct image**) e0);
+  const struct image *i1 = *((const struct image**) e1);
+
+  return ! i0 ? 1 : ! i1 ? -1 : timespec_cmp (i0->timestamp, i1->timestamp);
+}
+
+static ptrdiff_t
+clear_image_cache_by_cache_size_limit (struct frame *f)
+{
+  struct image_cache *c = FRAME_IMAGE_CACHE (f);
+
+  if (! FIXNUMP (Vimage_cache_size_limit)
+      || ! c
+      || c->allocated_image_size < 2 * XFIXNUM (Vimage_cache_size_limit))
+    return 0;
+
+  USE_SAFE_ALLOCA;
+  ptrdiff_t nfreed = 0;
+  int size_limit = XFIXNUM (Vimage_cache_size_limit);
+  struct image **images;
+  int bytes_freed = 0;
+  int allocated_image_size = c->allocated_image_size;
+
+  SAFE_NALLOCA (images, sizeof *images, c->used);
+  memcpy (images, c->images, c->used * sizeof *images);
+  qsort (images, c->used, sizeof *images, compare_images_by_timestamp);
+
+  for (int i = 0;
+       i < c->used && images[i] && c->allocated_image_size > size_limit;
+       ++i)
+    {
+      bytes_freed += images[i]->allocated_size;
+      free_image (f, images[i]);
+      ++nfreed;
+    }
+  SAFE_FREE ();
+  fprintf (stderr, "[IMAGE CACHE]: %d KB - %d KB = %d KB.\n",
+           allocated_image_size / 1024,
+           bytes_freed/ 1024,
+           c->allocated_image_size / 1024);
+  return nfreed;
+}
 
 /* Clear image cache of frame F.  FILTER=t means free all images.
    FILTER=nil means clear only images that haven't been
@@ -1608,6 +1658,9 @@ clear_image_cache (struct frame *f, Lisp_Object filter)
            }
        }
 
+      if (NILP (filter))
+        nfreed += clear_image_cache_by_cache_size_limit (f);
+
       /* We may be clearing the image cache because, for example,
         Emacs was iconified for a longer period of time.  In that
         case, current matrices may still contain references to
@@ -1931,11 +1984,15 @@ lookup_image (struct frame *f, Lisp_Object spec)
   if (img == NULL)
     {
       block_input ();
+      /* Maybe free some images here so the cache does not get to
+         large.  Otherwise this is only done now and then in
+         redisplay. */
+      clear_image_cache_by_cache_size_limit (f);
       img = make_image (spec, hash);
-      cache_image (f, img);
       img->load_failed_p = ! img->type->load (f, img);
       img->frame_foreground = FRAME_FOREGROUND_PIXEL (f);
       img->frame_background = FRAME_BACKGROUND_PIXEL (f);
+      cache_image (f, img);
 
       /* If we can't load the image, and we don't have a width and
         height, use some arbitrary width and height so that we can
@@ -2046,6 +2103,16 @@ cache_image (struct frame *f, struct image *img)
     img->next->prev = img;
   img->prev = NULL;
   c->buckets[i] = img;
+
+  /* Account for it's allocated memory */
+  if (! img->load_failed_p)
+    {
+      if (img->ximg)
+        img->allocated_size += img->ximg->height * img->ximg->bytes_per_line;
+      if (img->mask_img)
+        img->allocated_size += img->mask_img->height * 
img->mask_img->bytes_per_line;
+      c->allocated_image_size += img->allocated_size;
+    }
 }
 
 
@@ -10194,6 +10261,11 @@ The value can also be nil, meaning the cache is never 
cleared.
 
 The function `clear-image-cache' disregards this variable.  */);
   Vimage_cache_eviction_delay = make_fixnum (300);
+
+  DEFVAR_LISP ("image-cache-size-limit", Vimage_cache_size_limit,
+    doc: /* Maximum size of an image cache before images are removed.*/);
+  Vimage_cache_size_limit = make_fixnum (1024 * 1024 * 128);
+
 #ifdef HAVE_IMAGEMAGICK
   DEFVAR_INT ("imagemagick-render-type", imagemagick_render_type,
     doc: /* Integer indicating which ImageMagick rendering method to use.

reply via email to

[Prev in Thread] Current Thread [Next in Thread]