[Mesa-dev] [PATCH 03/88] glsl: Add initial functions to implement an on-disk cache

Eric Anholt eric at anholt.net
Sun Sep 25 20:26:02 UTC 2016


Timothy Arceri <timothy.arceri at collabora.com> writes:

> From: Carl Worth <cworth at cworth.org>
>
> This code provides for an on-disk cache of objects. Objects are stored
> and retrieved via names that are arbitrary 20-byte sequences,
> (intended to be SHA-1 hashes of something identifying for the
> content). The directory used for the cache can be specified by means
> of environment variables in the following priority order:
>
> 	$MESA_GLSL_CACHE_DIR
> 	$XDG_CACHE_HOME/mesa
> 	<user-home-directory>/.cache/mesa
>
> By default the cache will be limited to a maximum size of 1GB. The
> environment variable:
>
> 	$MESA_GLSL_CACHE_MAX_SIZE
>
> can be set (at the time of GL context creation) to choose some other
> size. This variable is a number that can optionally be followed by
> 'K', 'M', or 'G' to select a size in kilobytes, megabytes, or
> gigabytes. By default, an unadorned value will be interpreted as
> gigabytes.
>
> The cache will be entirely disabled at runtime if the variable
> MESA_GLSL_CACHE_DISABLE is set at the time of GL context creation.
>
> Many thanks to Kristian Høgsberg <krh at bitplanet.net> for the initial
> implementation of code that led to this patch. In particular, the idea
> of using an mmapped file, (indexed by a portion of the SHA-1), for the
> efficent implementation of cache_has_key was entirely his
> idea. Kristian also provided some very helpful advice in discussions
> regarding various race conditions to be avoided in this code.

General style note: A bunch of calls in this commit put a space between
function name and '(' and that should probably be fixed up for
consistency with Mesa.

> Signed-off-by: Timothy Arceri <timothy.arceri at collabora.com>
> ---
>  configure.ac                         |   3 +
>  docs/envvars.html                    |  11 +
>  src/compiler/Makefile.glsl.am        |  10 +
>  src/compiler/Makefile.sources        |   4 +
>  src/compiler/glsl/cache.c            | 709 +++++++++++++++++++++++++++++++++++
>  src/compiler/glsl/cache.h            | 172 +++++++++
>  src/compiler/glsl/tests/.gitignore   |   1 +
>  src/compiler/glsl/tests/cache_test.c | 416 ++++++++++++++++++++
>  8 files changed, 1326 insertions(+)
>  create mode 100644 src/compiler/glsl/cache.c
>  create mode 100644 src/compiler/glsl/cache.h
>  create mode 100644 src/compiler/glsl/tests/cache_test.c
>

> diff --git a/docs/envvars.html b/docs/envvars.html
> index cf57ca5..2375145 100644
> --- a/docs/envvars.html
> +++ b/docs/envvars.html
> @@ -112,6 +112,17 @@ glGetString(GL_VERSION) for OpenGL ES.
>  glGetString(GL_SHADING_LANGUAGE_VERSION). Valid values are integers, such as
>  "130".  Mesa will not really implement all the features of the given language version
>  if it's higher than what's normally reported. (for developers only)
> +<li>MESA_GLSL_CACHE_DISABLE - if set, disables the GLSL shader cache
> +<li>MESA_GLSL_CACHE_MAX_SIZE - if set, determines the maximum size of
> +the on-disk cache of compiled GLSL programs. Should be set to a number
> +optionally followed by 'K', 'M', or 'G' to specify a size in
> +kilobytes, megabytes, or gigabytes. By default, gigabytes will be
> +assumed. And if unset, a maxium size of 1GB will be used.
> +<li>MESA_GLSL_CACHE_DIR - if set, determines the directory to be used
> +for the on-disk cache of compiled GLSL programs. If this variable is
> +not set, then the cache will be stored in $XDG_CACHE_HOME/.mesa (if

Looks like that's actually "$XDG_CACHE_HOME/mesa"

> +that variable is set), or else within .cache/mesa within the user's
> +home directory.
>  <li>MESA_GLSL - <a href="shading.html#envvars">shading language compiler options</a>
>  <li>MESA_NO_MINMAX_CACHE - when set, the minmax index cache is globally disabled.
>  </ul>

> diff --git a/src/compiler/glsl/cache.c b/src/compiler/glsl/cache.c
> new file mode 100644
> index 0000000..c4c4fb7
> --- /dev/null
> +++ b/src/compiler/glsl/cache.c
> @@ -0,0 +1,709 @@
> +/*
> + * Copyright © 2014 Intel Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the "Software"),
> + * to deal in the Software without restriction, including without limitation
> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
> + * and/or sell copies of the Software, and to permit persons to whom the
> + * Software is furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice (including the next
> + * paragraph) shall be included in all copies or substantial portions of the
> + * Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
> + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
> + * IN THE SOFTWARE.
> + */
> +
> +#include <ctype.h>
> +#include <string.h>
> +#include <stdlib.h>
> +#include <stdio.h>
> +#include <sys/file.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +#include <sys/mman.h>
> +#include <unistd.h>
> +#include <fcntl.h>
> +#include <pwd.h>
> +#include <errno.h>
> +#include <dirent.h>
> +
> +#include "util/u_atomic.h"
> +#include "util/mesa-sha1.h"
> +#include "util/ralloc.h"
> +#include "main/errors.h"
> +
> +#include "cache.h"
> +
> +/* Number of bits to mask off from a cache key to get an index. */
> +#define CACHE_INDEX_KEY_BITS 16
> +
> +/* Mask for computing an index from a key. */
> +#define CACHE_INDEX_KEY_MASK ((1 << CACHE_INDEX_KEY_BITS) - 1)
> +
> +/* The number of keys that can be stored in the index. */
> +#define CACHE_INDEX_MAX_KEYS (1 << CACHE_INDEX_KEY_BITS)
> +
> +struct program_cache {
> +   /* The path to the cache directory. */
> +   char *path;
> +
> +   /* A pointer to the mmapped index file within the cache directory. */
> +   uint8_t *index_mmap;
> +   size_t index_mmap_size;
> +
> +   /* Pointer to total size of all objects in cache (within index_mmap) */
> +   uint64_t *size;
> +
> +   /* Pointer to stored keys, (within index_mmap). */
> +   uint8_t *stored_keys;
> +
> +   /* Maximum size of all cached objects (in bytes). */
> +   uint64_t max_size;
> +};
> +
> +/* Create a directory named 'path' if it does not already exist.
> + *
> + * Returns: 0 if path already exists as a directory or if created.
> + *         -1 in all other cases.
> + */
> +static int
> +mkdir_if_needed(char *path)
> +{
> +   struct stat sb;
> +
> +   /* If the path exists already, then our work is done if it's a
> +    * directory, but it's an error if it is not.
> +    */
> +   if (stat(path, &sb) == 0) {
> +      if (S_ISDIR(sb.st_mode)) {
> +         return 0;
> +      } else {
> +         _mesa_warning(NULL,
> +                       "Cannot use %s for shader cache (not a directory)"
> +                       "---disabling.\n", path);
> +         return -1;
> +      }
> +   }
> +
> +   if (mkdir(path, 0755) == 0)
> +      return 0;

This should probably be:

ret = mkdir(path, 0755)
if (ret == 0 || ret == -1 && errno == EEXIST)
     return 0;

to avoid races on first creation.  I guess you could leave the stat in
at that point to check that the path isn't a file, but I don't care
either way.

> +void
> +cache_put(struct program_cache *cache,
> +          cache_key key,
> +          const void *data,
> +          size_t size)
> +{
> +   int fd = -1, fd_final = -1, err, ret;
> +   size_t len;
> +   char *filename = NULL, *filename_tmp = NULL;
> +   const char *p = data;
> +
> +   filename = get_cache_file(cache, key);
> +   if (filename == NULL)
> +      goto done;
> +
> +   /* Write to a temporary file to allow for an atomic rename to the
> +    * final destination filename, (to prevent any readers from seeing
> +    * a partially written file).
> +    */
> +   filename_tmp = ralloc_asprintf(cache, "%s.tmp", filename);
> +   if (filename_tmp == NULL)
> +      goto done;
> +
> +   fd = open(filename_tmp, O_WRONLY | O_CLOEXEC | O_CREAT, 0644);
> +
> +   /* Make the two-character subdirectory within the cache as needed. */
> +   if (fd == -1) {
> +      if (errno != ENOENT)
> +         goto done;
> +
> +      make_cache_file_directory(cache, key);
> +
> +      fd = open(filename_tmp, O_WRONLY | O_CLOEXEC | O_CREAT, 0644);
> +      if (fd == -1)
> +         goto done;
> +   }
> +
> +   /* With the temporary file open, we take an exclusive flock on
> +    * it. If the flock fails, then another process still has the file
> +    * open with the flock held. So just let that file be responsible
> +    * for writing the file.
> +    */
> +   err = flock(fd, LOCK_EX);
> +   if (err == -1)
> +      goto done;

Given the comment, I think you want "| LOCK_NB" to return early if
someone else has it locked.

> +static void
> +test_put_key_and_get_key(void)
> +{
> +   struct program_cache *cache;
> +   bool result;
> +
> +   uint8_t key_a[20] = {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9,
> +			 10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
> +   uint8_t key_b[20] = { 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
> +			 30, 33, 32, 33, 34, 35, 36, 37, 38, 39};
> +   uint8_t key_a_collide[20] =
> +                        { 0,  1, 42, 43, 44, 45, 46, 47, 48, 49,
> +			 50, 55, 52, 53, 54, 55, 56, 57, 58, 59};
> +
> +   cache = cache_create();
> +
> +   /* First test that cache_has_key returns false before cache_put_key */
> +   result = cache_has_key(cache, key_a);
> +   expect_equal(result, 0, "cache_has_key before key added");
> +
> +   /* Then a couple of tests of cache_put_key followed by cache_has_key */
> +   cache_put_key(cache, key_a);
> +   result = cache_has_key(cache, key_a);
> +   expect_equal(result, 1, "cache_has_key after key added");
> +
> +   cache_put_key(cache, key_b);
> +   result = cache_has_key(cache, key_b);
> +   expect_equal(result, 1, "2nd cache_has_key after key added");
> +
> +   /* Test that a key with the same two bytes as an existing key
> +    * forces an eviction.
> +    */
> +   cache_put_key(cache, key_a_collide);
> +   result = cache_has_key(cache, key_a_collide);
> +   expect_equal(result, 1, "put_key of a colliding key lands in the cache");
> +
> +   result = cache_has_key(cache, key_a);
> +   expect_equal(result, 0, "put_key of a colliding key evicts from the cache");
> +
> +   /* And finally test that we can re-add the original key to re-evict
> +    * the colliding key.
> +    */
> +   cache_put_key(cache, key_a);
> +   result = cache_has_key(cache, key_a);
> +   expect_equal(result, 1, "put_key of original key lands again");
> +
> +   result = cache_has_key(cache, key_a_collide);
> +   expect_equal(result, 0, "put_key of oiginal key evicts the colliding key");

"original"

I haven't yet figured out what the purpose of
cache_put_key()/cache_has_key() are.  I suppose I'll find out later in
the series.

With the small fixes here, this patch and patch 2 are:

Reviewed-by: Eric Anholt <eric at anholt.net>

This series is unfortunately way too long to review in one go, and I'm
hoping we can incrementally land pieces.

> +
> +   cache_destroy(cache);
> +}
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 800 bytes
Desc: not available
URL: <https://lists.freedesktop.org/archives/mesa-dev/attachments/20160925/0e8f3512/attachment.sig>


More information about the mesa-dev mailing list