diff options
Diffstat (limited to 'src/kmk/strcache.c')
-rw-r--r-- | src/kmk/strcache.c | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/src/kmk/strcache.c b/src/kmk/strcache.c new file mode 100644 index 0000000..6f5c500 --- /dev/null +++ b/src/kmk/strcache.c @@ -0,0 +1,368 @@ +/* Constant string caching for GNU Make. +Copyright (C) 2006-2016 Free Software Foundation, Inc. +This file is part of GNU Make. + +GNU Make is free software; you can redistribute it and/or modify it under the +terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +GNU Make is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "makeint.h" +#ifndef CONFIG_WITH_STRCACHE2 + +#include <stddef.h> +#include <assert.h> + +#include "hash.h" + +/* A string cached here will never be freed, so we don't need to worry about + reference counting. We just store the string, and then remember it in a + hash so it can be looked up again. */ + +typedef unsigned short int sc_buflen_t; + +struct strcache { + struct strcache *next; /* The next block of strings. Must be first! */ + sc_buflen_t end; /* Offset to the beginning of free space. */ + sc_buflen_t bytesfree; /* Free space left in this buffer. */ + sc_buflen_t count; /* # of strings in this buffer (for stats). */ + char buffer[1]; /* The buffer comes after this. */ +}; + +/* The size (in bytes) of each cache buffer. + Try to pick something that will map well into the heap. + This must be able to be represented by a short int (<=65535). */ +#define CACHE_BUFFER_BASE (8192) +#define CACHE_BUFFER_ALLOC(_s) ((_s) - (2 * sizeof (size_t))) +#define CACHE_BUFFER_OFFSET (offsetof (struct strcache, buffer)) +#define CACHE_BUFFER_SIZE(_s) (CACHE_BUFFER_ALLOC(_s) - CACHE_BUFFER_OFFSET) +#define BUFSIZE CACHE_BUFFER_SIZE (CACHE_BUFFER_BASE) + +static struct strcache *strcache = NULL; +static struct strcache *fullcache = NULL; + +static unsigned long total_buffers = 0; +static unsigned long total_strings = 0; +static unsigned long total_size = 0; + +/* Add a new buffer to the cache. Add it at the front to reduce search time. + This can also increase the overhead, since it's less likely that older + buffers will be filled in. However, GNU make has so many smaller strings + that this doesn't seem to be much of an issue in practice. + */ +static struct strcache * +new_cache (struct strcache **head, sc_buflen_t buflen) +{ + struct strcache *new = xmalloc (buflen + CACHE_BUFFER_OFFSET); + new->end = 0; + new->count = 0; + new->bytesfree = buflen; + + new->next = *head; + *head = new; + + ++total_buffers; + return new; +} + +static const char * +copy_string (struct strcache *sp, const char *str, unsigned int len) +{ + /* Add the string to this cache. */ + char *res = &sp->buffer[sp->end]; + + memmove (res, str, len); + res[len++] = '\0'; + sp->end += len; + sp->bytesfree -= len; + ++sp->count; + + return res; +} + +static const char * +add_string (const char *str, unsigned int len) +{ + const char *res; + struct strcache *sp; + struct strcache **spp = &strcache; + /* We need space for the nul char. */ + unsigned int sz = len + 1; + + ++total_strings; + total_size += sz; + + /* If the string we want is too large to fit into a single buffer, then + no existing cache is large enough. Add it directly to the fullcache. */ + if (sz > BUFSIZE) + { + sp = new_cache (&fullcache, sz); + return copy_string (sp, str, len); + } + + /* Find the first cache with enough free space. */ + for (; *spp != NULL; spp = &(*spp)->next) + if ((*spp)->bytesfree > sz) + break; + sp = *spp; + + /* If nothing is big enough, make a new cache at the front. */ + if (sp == NULL) + { + sp = new_cache (&strcache, BUFSIZE); + spp = &strcache; + } + + /* Add the string to this cache. */ + res = copy_string (sp, str, len); + + /* If the amount free in this cache is less than the average string size, + consider it full and move it to the full list. */ + if (total_strings > 20 && sp->bytesfree < (total_size / total_strings) + 1) + { + *spp = sp->next; + sp->next = fullcache; + fullcache = sp; + } + + return res; +} + +/* For strings too large for the strcache, we just save them in a list. */ +struct hugestring { + struct hugestring *next; /* The next string. */ + char buffer[1]; /* The string. */ +}; + +static struct hugestring *hugestrings = NULL; + +static const char * +add_hugestring (const char *str, unsigned int len) +{ + struct hugestring *new = xmalloc (sizeof (struct hugestring) + len); + memcpy (new->buffer, str, len); + new->buffer[len] = '\0'; + + new->next = hugestrings; + hugestrings = new; + + return new->buffer; +} + +/* Hash table of strings in the cache. */ + +static unsigned long +str_hash_1 (const void *key) +{ + return_ISTRING_HASH_1 ((const char *) key); +} + +static unsigned long +str_hash_2 (const void *key) +{ + return_ISTRING_HASH_2 ((const char *) key); +} + +static int +str_hash_cmp (const void *x, const void *y) +{ + return_ISTRING_COMPARE ((const char *) x, (const char *) y); +} + +static struct hash_table strings; +static unsigned long total_adds = 0; + +static const char * +add_hash (const char *str, unsigned int len) +{ + char *const *slot; + const char *key; + + /* If it's too large for the string cache, just copy it. + We don't bother trying to match these. */ + if (len > USHRT_MAX - 1) + return add_hugestring (str, len); + + /* Look up the string in the hash. If it's there, return it. */ + slot = (char *const *) hash_find_slot (&strings, str); + key = *slot; + + /* Count the total number of add operations we performed. */ + ++total_adds; + + if (!HASH_VACANT (key)) + return key; + + /* Not there yet so add it to a buffer, then into the hash table. */ + key = add_string (str, len); + hash_insert_at (&strings, key, slot); + return key; +} + +/* Returns true if the string is in the cache; false if not. */ +int +strcache_iscached (const char *str) +{ + struct strcache *sp; + + for (sp = strcache; sp != 0; sp = sp->next) + if (str >= sp->buffer && str < sp->buffer + sp->end) + return 1; + for (sp = fullcache; sp != 0; sp = sp->next) + if (str >= sp->buffer && str < sp->buffer + sp->end) + return 1; + + { + struct hugestring *hp; + for (hp = hugestrings; hp != 0; hp = hp->next) + if (str == hp->buffer) + return 1; + } + + return 0; +} + +/* If the string is already in the cache, return a pointer to the cached + version. If not, add it then return a pointer to the cached version. + Note we do NOT take control of the string passed in. */ +const char * +strcache_add (const char *str) +{ + return add_hash (str, strlen (str)); +} + +const char * +strcache_add_len (const char *str, unsigned int len) +{ + /* If we're not given a nul-terminated string we have to create one, because + the hashing functions expect it. */ + if (str[len] != '\0') + { + char *key = alloca (len + 1); + memcpy (key, str, len); + key[len] = '\0'; + str = key; + } + + return add_hash (str, len); +} + +void +strcache_init (void) +{ + hash_init (&strings, 8000, str_hash_1, str_hash_2, str_hash_cmp); +} + + +/* Generate some stats output. */ + +void +strcache_print_stats (const char *prefix) +{ + const struct strcache *sp; + unsigned long numbuffs = 0, fullbuffs = 0; + unsigned long totfree = 0, maxfree = 0, minfree = BUFSIZE; + + if (! strcache) + { + printf (_("\n%s No strcache buffers\n"), prefix); + return; + } + + /* Count the first buffer separately since it's not full. */ + for (sp = strcache->next; sp != NULL; sp = sp->next) + { + sc_buflen_t bf = sp->bytesfree; + + totfree += bf; + maxfree = (bf > maxfree ? bf : maxfree); + minfree = (bf < minfree ? bf : minfree); + + ++numbuffs; + } + for (sp = fullcache; sp != NULL; sp = sp->next) + { + sc_buflen_t bf = sp->bytesfree; + + totfree += bf; + maxfree = (bf > maxfree ? bf : maxfree); + minfree = (bf < minfree ? bf : minfree); + + ++numbuffs; + ++fullbuffs; + } + + /* Make sure we didn't lose any buffers. */ + assert (total_buffers == numbuffs + 1); + + printf (_("\n%s strcache buffers: %lu (%lu) / strings = %lu / storage = %lu B / avg = %lu B\n"), + prefix, numbuffs + 1, fullbuffs, total_strings, total_size, + (total_size / total_strings)); + + printf (_("%s current buf: size = %hu B / used = %hu B / count = %hu / avg = %hu B\n"), + prefix, (sc_buflen_t)BUFSIZE, strcache->end, strcache->count, + (strcache->end / strcache->count)); + + if (numbuffs) + { + /* Show information about non-current buffers. */ + unsigned long sz = total_size - strcache->end; + unsigned long cnt = total_strings - strcache->count; + sc_buflen_t avgfree = totfree / numbuffs; + + printf (_("%s other used: total = %lu B / count = %lu / avg = %lu B\n"), + prefix, sz, cnt, sz / cnt); + + printf (_("%s other free: total = %lu B / max = %lu B / min = %lu B / avg = %hu B\n"), + prefix, totfree, maxfree, minfree, avgfree); + } + + printf (_("\n%s strcache performance: lookups = %lu / hit rate = %lu%%\n"), + prefix, total_adds, (long unsigned)(100.0 * (total_adds - total_strings) / total_adds)); + fputs (_("# hash-table stats:\n# "), stdout); + hash_print_stats (&strings, stdout); +} + +#else /* CONFIG_WITH_STRCACHE2 */ + +#include "strcache2.h" + +const char *suffixes_strcached; + +/* The file string cache. */ +struct strcache2 file_strcache; + +void strcache_init (void) +{ + strcache2_init(&file_strcache, + "file", /* name */ + 131072, /* hash size */ + 0, /* default segment size*/ +#ifdef HAVE_CASE_INSENSITIVE_FS + 1, /* case insensitive */ +#else + 0, /* case insensitive */ +#endif + 0); /* thread safe */ + + /* .SUFFIXES is referenced in several loops, keep the added pointer in a + global var so these can be optimized. */ + + suffixes_strcached = strcache_add_len (".SUFFIXES", sizeof (".SUFFIXES")-1); +} + +void +strcache_print_stats (const char *prefix) +{ + strcache2_print_stats (&file_strcache, prefix); +} + +#endif /* CONFIG_WITH_STRCACHE2 */ + |