[RFC] symbol lookup cache

Message ID yjt2y4qrlqwa.fsf@ruffy.mtv.corp.google.com
State New, archived
Headers

Commit Message

Doug Evans Dec. 1, 2014, 8:23 a.m. UTC
  Hi.

Holidays are nice for having the time to take on
projects one is interested in but doesn't have
time to otherwise do.

I looked at a couple of areas for improvement
in gdb's symbol handling over the last couple
of days.  One is .gdb_index support for tab
completion, which is the topic for another mail.
This email is about a symbol lookup cache.

..gdb_index helps speed things up for one objfile,
but when there are 100s (or 1000s) of shared
libraries, symbol lookup can still take awhile,
even with .gdb_index.
[I'm setting aside a planned change to basic
symbol lookup to use the index better.
We'll still, I think, want a cache even
with that change.]

This patch still needs more testing
and I need to collect more perf data,
but it's a start at caching symbol lookups.
This first pass is just for the case
where we iterate over all objfiles for
lookups in the global or static blocks.
Low hanging fruit.
At the moment it's just RFC.

Joel: I noticed ada-lang.c has its own cache.
I haven't studied it too closely (yet).
I can imagine wanting to keep some variant of both.
This is just a "heads up".

btw, another area that needs caching
is pc lookups (e.g., find_pc_sect_compunit_symtab).
That's the topic of yet another email.
  

Comments

Doug Evans Dec. 3, 2014, 12:50 a.m. UTC | #1
On Mon, Dec 1, 2014 at 12:23 AM, Doug Evans <dje@google.com> wrote:
>
> Hi.
>
> Holidays are nice for having the time to take on
> projects one is interested in but doesn't have
> time to otherwise do.
>
> I looked at a couple of areas for improvement
> in gdb's symbol handling over the last couple
> of days.  One is .gdb_index support for tab
> completion, which is the topic for another mail.
> This email is about a symbol lookup cache.
>
> ..gdb_index helps speed things up for one objfile,
> but when there are 100s (or 1000s) of shared
> libraries, symbol lookup can still take awhile,
> even with .gdb_index.
> [I'm setting aside a planned change to basic
> symbol lookup to use the index better.
> We'll still, I think, want a cache even
> with that change.]
>
> This patch still needs more testing
> and I need to collect more perf data,
> but it's a start at caching symbol lookups.
> This first pass is just for the case
> where we iterate over all objfiles for
> lookups in the global or static blocks.
> Low hanging fruit.
> At the moment it's just RFC.

[bleah, sorry for the repeat Joel]

One thing this patch is missing is the ability to turn the cache off,
both for performance measurements and for debugging.
  
Joel Brobecker Dec. 3, 2014, 2:46 a.m. UTC | #2
> Joel: I noticed ada-lang.c has its own cache.
> I haven't studied it too closely (yet).
> I can imagine wanting to keep some variant of both.
> This is just a "heads up".

A priori, I will eventually try to migrate Ada to the standard cache
if we can. If symbol lookup has taught me anything, it's that it is
really costly to do things your way...
  
Pedro Alves Dec. 4, 2014, 4:38 p.m. UTC | #3
On 12/01/2014 08:23 AM, Doug Evans wrote:
> Hi.
> 
> Holidays are nice for having the time to take on
> projects one is interested in but doesn't have
> time to otherwise do.
> 
> I looked at a couple of areas for improvement
> in gdb's symbol handling over the last couple
> of days.  One is .gdb_index support for tab
> completion, which is the topic for another mail.
> This email is about a symbol lookup cache.
> 
> ..gdb_index helps speed things up for one objfile,
> but when there are 100s (or 1000s) of shared
> libraries, symbol lookup can still take awhile,
> even with .gdb_index.
> [I'm setting aside a planned change to basic
> symbol lookup to use the index better.
> We'll still, I think, want a cache even
> with that change.]
> 
> This patch still needs more testing
> and I need to collect more perf data,
> but it's a start at caching symbol lookups.
> This first pass is just for the case
> where we iterate over all objfiles for
> lookups in the global or static blocks.
> Low hanging fruit.
> At the moment it's just RFC.

I don't have particular comments on the patch, but a cache in
principle sounds like a great idea to me.

You've probably thought this through already, but, my only worry
is whether this could affect which symbol is found.  E.g., if
there are multiple instances of a static global symbol in the
program.  ISTR expressions find the instance in the current
module (or CU?) first.  I may be thinking of solib_global_lookup.
Could the cache hit the symbol in another module first?

Thanks,
Pedro Alves
  
Jan Kratochvil Dec. 4, 2014, 7:15 p.m. UTC | #4
On Mon, 01 Dec 2014 09:23:49 +0100, Doug Evans wrote:
> ..gdb_index helps speed things up for one objfile,
> but when there are 100s (or 1000s) of shared
> libraries, symbol lookup can still take awhile,
> even with .gdb_index.
> [I'm setting aside a planned change to basic
> symbol lookup to use the index better.
> We'll still, I think, want a cache even
> with that change.]

While I have no numbers personally I do not think GDB would still need a cache
after that change.

"Accelerate lookup_symbol_aux_objfile 85x" is so fast in a fraction of second
because it traverses 146426 symtabs.  So 146426 is OK but 1979341 was not (it
was in some way more than 1979341 before you refactored symtabs).

I was considering in the past to have some merged .gdb_index for all the
shared libraries used by some app on disk (not just in memory).  But that
makes sense for HDD but less for SSD with the regular ~100 libraries.  While
it make more sense for Google 1000+ libraries I am not sure if Google always
uses the same set of 1000+ libraries for a given app.


Jan
  
Doug Evans Dec. 4, 2014, 7:17 p.m. UTC | #5
On Thu, Dec 4, 2014 at 8:38 AM, Pedro Alves <palves@redhat.com> wrote:
>
> On 12/01/2014 08:23 AM, Doug Evans wrote:
> > Hi.
> >
> > Holidays are nice for having the time to take on
> > projects one is interested in but doesn't have
> > time to otherwise do.
> >
> > I looked at a couple of areas for improvement
> > in gdb's symbol handling over the last couple
> > of days.  One is .gdb_index support for tab
> > completion, which is the topic for another mail.
> > This email is about a symbol lookup cache.
> >
> > ..gdb_index helps speed things up for one objfile,
> > but when there are 100s (or 1000s) of shared
> > libraries, symbol lookup can still take awhile,
> > even with .gdb_index.
> > [I'm setting aside a planned change to basic
> > symbol lookup to use the index better.
> > We'll still, I think, want a cache even
> > with that change.]
> >
> > This patch still needs more testing
> > and I need to collect more perf data,
> > but it's a start at caching symbol lookups.
> > This first pass is just for the case
> > where we iterate over all objfiles for
> > lookups in the global or static blocks.
> > Low hanging fruit.
> > At the moment it's just RFC.
>
> I don't have particular comments on the patch, but a cache in
> principle sounds like a great idea to me.
>
> You've probably thought this through already, but, my only worry
> is whether this could affect which symbol is found.  E.g., if
> there are multiple instances of a static global symbol in the
> program.  ISTR expressions find the instance in the current
> module (or CU?) first.  I may be thinking of solib_global_lookup.
> Could the cache hit the symbol in another module first?
>
> Thanks,
> Pedro Alves
>


Oh curse you gmail.  Sorry for the resend.

The intent is to perform the caching only from lookups after all
context-specific searches have been done.
E.g, we specifically do lookups in the current STATIC_BLOCK (via
lookup_symbol_in_static_block) and then call the
"loop-over-all-static-blocks" routine (lookup_static_symbol), and it's
only the latter that employs caching.
context-specific searches are generally pretty quick and
don't(/shouldn't) need caching.

I put the caching checks after the call to solib_global_lookup in
lookup_global_symbol to avoid problems there.
Alas, I'm guessing it's windows's global search routine
(windows_iterate_over_objfiles_in_search_order) that is going to throw
a wrench into the works here.
We'll need to refactor that.
Seems like we have two solutions (solib_global_lookup and
gdbarch_iterate_over_objfiles_in_search_order) for what is essentially
the same problem.
  
Doug Evans Dec. 4, 2014, 7:24 p.m. UTC | #6
On Thu, Dec 4, 2014 at 11:15 AM, Jan Kratochvil
<jan.kratochvil@redhat.com> wrote:
> On Mon, 01 Dec 2014 09:23:49 +0100, Doug Evans wrote:
>> ..gdb_index helps speed things up for one objfile,
>> but when there are 100s (or 1000s) of shared
>> libraries, symbol lookup can still take awhile,
>> even with .gdb_index.
>> [I'm setting aside a planned change to basic
>> symbol lookup to use the index better.
>> We'll still, I think, want a cache even
>> with that change.]
>
> While I have no numbers personally I do not think GDB would still need a cache
> after that change.
>
> "Accelerate lookup_symbol_aux_objfile 85x" is so fast in a fraction of second
> because it traverses 146426 symtabs.  So 146426 is OK but 1979341 was not (it
> was in some way more than 1979341 before you refactored symtabs).

Just to make sure I understand correctly, the 85x is dwz-specific, right?
  
Jan Kratochvil Dec. 4, 2014, 7:28 p.m. UTC | #7
On Thu, 04 Dec 2014 20:24:28 +0100, Doug Evans wrote:
> On Thu, Dec 4, 2014 at 11:15 AM, Jan Kratochvil <jan.kratochvil@redhat.com> wrote:
> > "Accelerate lookup_symbol_aux_objfile 85x" is so fast in a fraction of second
> > because it traverses 146426 symtabs.  So 146426 is OK but 1979341 was not (it
> > was in some way more than 1979341 before you refactored symtabs).
> 
> Just to make sure I understand correctly, the 85x is dwz-specific, right?

Yes, all my numbers are DWZ specific, I have not tested what/if effect it has
for non-DWZ inferiors.


Jan
  

Patch

diff --git a/gdb/symtab.c b/gdb/symtab.c
index 345c20d..f9ef72f 100644
--- a/gdb/symtab.c
+++ b/gdb/symtab.c
@@ -104,9 +104,78 @@  struct main_info
   enum language language_of_main;
 };
 
+/* Program space key for finding its symbol cache.  */
+
+static const struct program_space_data *symbol_cache_key;
+
+/* The symbol cache size.
+   TODO(dje): One can imagine making this dynamic based on the size of the
+   program, but OTOH this cache consumes minimal space, and there is no extra
+   cpu cost for large N (except when flushing the cache, which is rare).
+   The value here is just a first attempt.  */
+#define SYMBOL_CACHE_SIZE 127
+
+/* symbol_cache_lookup returns this if a previous lookup failed to find the
+   symbol in any objfile.  */
+#define SYMBOL_LOOKUP_FAILED ((struct symbol *) 1)
+
+/* Recording lookups that don't find the symbol is just as important, if not
+   more so, than recording found symbols.  */
+
+enum symbol_cache_slot_state
+{
+  SYMBOL_SLOT_UNUSED,
+  SYMBOL_SLOT_NOT_FOUND,
+  SYMBOL_SLOT_FOUND
+};
+
+/* Symbols don't specify global vs static block.
+   So keep them in separate caches.  */
+
+struct block_symbol_cache
+{
+  unsigned int hits;
+  unsigned int misses;
+
+  struct symbol_cache_slot
+  {
+    enum symbol_cache_slot_state state;
+    union
+    {
+      struct symbol *found;
+      struct
+      {
+	char *name;
+	domain_enum domain;
+      } not_found;
+    } value;
+  } symbols[SYMBOL_CACHE_SIZE];
+};
+
+/* The symbol cache.
+
+   Searching for symbols in the static and global blocks over multiple objfiles
+   again and again can be slow, as can searching very big objfiles.  This is a
+   simple cache to improve symbol lookup performance, which is critical to
+   overall gdb performance.
+
+   Symbols are hashed on the name, its domain, and block.
+   They are also hashed on their objfile for objfile-specific lookups.
+
+   TODO(dje): Collect some numbers to guide the choices here.  */
+
+struct symbol_cache
+{
+  struct block_symbol_cache global_symbols;
+  struct block_symbol_cache static_symbols;
+};
+
 /* When non-zero, print debugging messages related to symtab creation.  */
 unsigned int symtab_create_debug = 0;
 
+/* When non-zero, print debugging messages related to symbol cache lookup.  */
+static unsigned int symbol_cache_debug = 0;
+
 /* Non-zero if a file may be known by two different basenames.
    This is the uncommon case, and significantly slows down gdb.
    Default set to "off" to not slow down the common case.  */
@@ -1089,6 +1158,309 @@  expand_symtab_containing_pc (CORE_ADDR pc, struct obj_section *section)
   }
 }
 
+/* Hash function for the symbol cache.  */
+
+static unsigned int
+hash_symbol_entry (const char *name, domain_enum domain)
+{
+  unsigned int hash = 0;
+
+  if (name != NULL)
+    hash += htab_hash_string (name);
+
+  /* TODO(dje): Too simplistic?  */
+  hash += domain;
+
+  return hash;
+}
+
+/* Equality function for the symbol cache.  */
+
+static int
+eq_symbol_entry (const struct symbol_cache_slot *slot,
+		 const char *name, domain_enum domain)
+{
+  const char *slot_name;
+  domain_enum slot_domain;
+
+  if (slot->state == SYMBOL_SLOT_UNUSED)
+    return 0;
+
+  if (slot->state == SYMBOL_SLOT_NOT_FOUND)
+    {
+      slot_name = slot->value.not_found.name;
+      slot_domain = slot->value.not_found.domain;
+    }
+  else
+    {
+      slot_name = SYMBOL_LINKAGE_NAME (slot->value.found);
+      slot_domain = SYMBOL_DOMAIN (slot->value.found);
+    }
+
+  /* NULL names match.  */
+  if (slot_name == NULL && name == NULL)
+    ;
+  else if (slot_name != NULL && name != NULL)
+    {
+      if (strcmp (slot_name, name) != 0)
+	return 0;
+    }
+  else
+    {
+      /* Only one name is NULL.  */
+      return 0;
+    }
+
+  if (slot_domain != domain)
+    return 0;
+
+  return 1;
+}
+
+/* Return the symbol cache of PSPACE.
+   Create one if it doesn't exist yet.  */
+
+static struct symbol_cache *
+get_symbol_cache (struct program_space *pspace)
+{
+  struct symbol_cache *cache = program_space_data (pspace, symbol_cache_key);
+
+  if (cache == NULL)
+    {
+      cache = XCNEW (struct symbol_cache);
+      set_program_space_data (pspace, symbol_cache_key, cache);
+    }
+
+  return cache;
+}
+
+/* Delete the symbol cache of PSPACE.
+   Called when PSPACE is destroyed.  */
+
+static void
+symbol_cache_cleanup (struct program_space *pspace, void *data)
+{
+  struct symbol_cache *cache = data;
+
+  xfree (cache);
+}
+
+/* Lookup symbol NAME,DOMAIN in BLOCK in the symbol cache of PSPACE.
+   The result is the symbol if found, SYMBOL_LOOKUP_FAILED if a previous lookup
+   failed (and thus this one will too), or NULL if the symbol is not present
+   in the cache.
+   *SLOT is set to the cache slot of the symbol, whether found or not
+   found.  */
+
+static struct symbol *
+symbol_cache_lookup (struct symbol_cache *cache, int block,
+		     const char *name, domain_enum domain,
+		     struct symbol_cache_slot **slot_ptr)
+{
+  struct block_symbol_cache *bsc;
+  unsigned int hash;
+  struct symbol_cache_slot *slot;
+
+  if (block == GLOBAL_BLOCK)
+    bsc = &cache->global_symbols;
+  else
+    bsc = &cache->static_symbols;
+  hash = hash_symbol_entry (name, domain);
+  slot = bsc->symbols + hash % SYMBOL_CACHE_SIZE;
+  *slot_ptr = slot;
+  if (eq_symbol_entry (slot, name, domain))
+    {
+      if (symbol_cache_debug)
+	fprintf_unfiltered (gdb_stdlog,
+			    "%s block symbol cache hit%s for %s, %d\n",
+			    block == GLOBAL_BLOCK ? "Global" : "Static",
+			    slot->state == SYMBOL_SLOT_NOT_FOUND
+			    ? " (not found)" : "",
+			    name, domain);
+      ++bsc->hits;
+      if (slot->state == SYMBOL_SLOT_NOT_FOUND)
+	return SYMBOL_LOOKUP_FAILED;
+      return slot->value.found;
+    }
+  if (symbol_cache_debug)
+    {
+      fprintf_unfiltered (gdb_stdlog,
+			  "%s block symbol cache miss for %s, %d\n",
+			  block == GLOBAL_BLOCK ? "Global" : "Static",
+			  name, domain);
+    }
+  ++bsc->misses;
+  return NULL;
+}
+
+/* Clear out SLOT.  */
+
+static void
+symbol_cache_clear_slot (struct symbol_cache_slot *slot)
+{
+  if (slot->state == SYMBOL_SLOT_NOT_FOUND)
+    xfree (slot->value.not_found.name);
+  slot->state = SYMBOL_SLOT_UNUSED;
+}
+
+/* Mark SYMBOL as found in SLOT.  */
+
+static void
+symbol_cache_mark_found (struct symbol_cache_slot *slot, struct symbol *symbol)
+{
+  symbol_cache_clear_slot (slot);
+  slot->state = SYMBOL_SLOT_FOUND;
+  slot->value.found = symbol;
+}
+
+/* Mark symbol NAME, DOMAIN as not found in SLOT.  */
+
+static void
+symbol_cache_mark_not_found (struct symbol_cache_slot *slot,
+			     const char *name, domain_enum domain)
+{
+  symbol_cache_clear_slot (slot);
+  slot->state = SYMBOL_SLOT_NOT_FOUND;
+  slot->value.not_found.name = xstrdup (name);
+  slot->value.not_found.domain = domain;
+}
+
+/* Flush the symbol cache of PSPACE.  */
+
+static void
+symbol_cache_flush (struct program_space *pspace)
+{
+  struct symbol_cache *cache = get_symbol_cache (pspace);
+  int pass;
+
+  for (pass = 0; pass < 2; ++pass)
+    {
+      struct block_symbol_cache *bsc
+	= pass == 0 ? &cache->global_symbols : &cache->static_symbols;
+      int i;
+
+      for (i = 0; i < SYMBOL_CACHE_SIZE; ++i)
+	symbol_cache_clear_slot (&bsc->symbols[i]);
+    }
+
+  memset (&cache->global_symbols, 0, sizeof (cache->global_symbols));
+  memset (&cache->static_symbols, 0, sizeof (cache->static_symbols));
+}
+
+/* Dump CACHE.  */
+
+static void
+symbol_cache_dump (const struct symbol_cache *cache)
+{
+  int pass;
+
+  for (pass = 0; pass < 2; ++pass)
+    {
+      const struct block_symbol_cache *bsc
+	= pass == 0 ? &cache->global_symbols : &cache->static_symbols;
+      int i;
+
+      if (pass == 0)
+	printf_filtered ("Global symbols:\n");
+      else
+	printf_filtered ("Static symbols:\n");
+
+      for (i = 0; i < SYMBOL_CACHE_SIZE; ++i)
+	{
+	  switch (bsc->symbols[i].state)
+	    {
+	    case SYMBOL_SLOT_UNUSED:
+	      break;
+	    case SYMBOL_SLOT_NOT_FOUND:
+	      printf_filtered ("  [%-2d] = %s\n", i, /* TODO(dje): "not found" */
+			       bsc->symbols[i].value.not_found.name);
+	      break;
+	    case SYMBOL_SLOT_FOUND:
+	      printf_filtered ("  [%-2d] = %s\n", i,
+			       SYMBOL_PRINT_NAME (bsc->symbols[i].value.found));
+	      break;
+	    }
+	}
+    }
+}
+
+/* The "mt print symbol-cache" command.  */
+
+static void
+maintenance_print_symbol_cache (char *args, int from_tty)
+{
+  struct program_space *pspace;
+
+  ALL_PSPACES (pspace)
+    {
+      struct symbol_cache *cache = get_symbol_cache (pspace);
+
+      QUIT;
+      symbol_cache_dump (cache);
+    }
+}
+
+/* Print usage statistics of CACHE.  */
+
+static void
+symbol_cache_stats (struct symbol_cache *cache)
+{
+  int pass;
+
+  for (pass = 0; pass < 2; ++pass)
+    {
+      const struct block_symbol_cache *bsc
+	= pass == 0 ? &cache->global_symbols : &cache->static_symbols;
+
+      if (pass == 0)
+	printf_filtered ("Global block cache stats:\n");
+      else
+	printf_filtered ("Static block cache stats:\n");
+
+      printf_filtered ("  size:   %u\n", SYMBOL_CACHE_SIZE);
+      printf_filtered ("  hits:   %u\n", bsc->hits);
+      printf_filtered ("  misses: %u\n", bsc->misses);
+    }
+}
+
+/* The "mt print symbol-cache-statistics" command.  */
+
+static void
+maintenance_print_symbol_cache_statistics (char *args, int from_tty)
+{
+  struct program_space *pspace;
+
+  ALL_PSPACES (pspace)
+    {
+      struct symbol_cache *cache = get_symbol_cache (pspace);
+
+      QUIT;
+      printf_filtered (_("Symbol cache statistics for pspace %d, '%s':\n"),
+		       pspace->num,
+		       pspace->symfile_object_file != NULL
+		       ? objfile_name (pspace->symfile_object_file)
+		       : "NULL");
+      symbol_cache_stats (cache);
+    }
+}
+
+/* This module's 'new_objfile' observer.  */
+
+static void
+symtab_new_objfile_observer (struct objfile *objfile)
+{
+  /* Ideally we'd use OBJFILE->pspace, but OBJFILE may be NULL.  */
+  symbol_cache_flush (current_program_space);
+}
+
+/* This module's 'free_objfile' observer.  */
+
+static void
+symtab_free_objfile_observer (struct objfile *objfile)
+{
+  symbol_cache_flush (objfile->pspace);
+}
+
 /* Debug symbols usually don't have section information.  We need to dig that
    out of the minimal symbols and stash that in the debug symbol.  */
 
@@ -1818,16 +2190,30 @@  lookup_symbol_in_objfile (struct objfile *objfile, int block_index,
 struct symbol *
 lookup_static_symbol (const char *name, const domain_enum domain)
 {
+  struct symbol_cache *cache = get_symbol_cache (current_program_space);
   struct objfile *objfile;
   struct symbol *result;
+  struct symbol_cache_slot *slot;
+
+  result = symbol_cache_lookup (cache, STATIC_BLOCK, name, domain, &slot);
+  if (result != NULL)
+    {
+      if (result == SYMBOL_LOOKUP_FAILED)
+	return NULL;
+      return result;
+    }
 
   ALL_OBJFILES (objfile)
     {
       result = lookup_symbol_in_objfile (objfile, STATIC_BLOCK, name, domain);
       if (result != NULL)
-	return result;
+	{
+	  symbol_cache_mark_found (slot, result);
+	  return result;
+	}
     }
 
+  symbol_cache_mark_not_found (slot, name, domain);
   return NULL;
 }
 
@@ -1875,9 +2261,11 @@  lookup_global_symbol (const char *name,
 		      const struct block *block,
 		      const domain_enum domain)
 {
+  struct symbol_cache *cache = get_symbol_cache (current_program_space);
   struct symbol *sym = NULL;
   struct objfile *objfile = NULL;
   struct global_sym_lookup_data lookup_data;
+  struct symbol_cache_slot *slot;
 
   /* Call library-specific lookup procedure.  */
   objfile = lookup_objfile_from_block (block);
@@ -1886,6 +2274,14 @@  lookup_global_symbol (const char *name,
   if (sym != NULL)
     return sym;
 
+  sym = symbol_cache_lookup (cache, GLOBAL_BLOCK, name, domain, &slot);
+  if (sym != NULL)
+    {
+      if (sym == SYMBOL_LOOKUP_FAILED)
+	return NULL;
+      return sym;
+    }
+
   memset (&lookup_data, 0, sizeof (lookup_data));
   lookup_data.name = name;
   lookup_data.domain = domain;
@@ -1893,6 +2289,11 @@  lookup_global_symbol (const char *name,
     (objfile != NULL ? get_objfile_arch (objfile) : target_gdbarch (),
      lookup_symbol_global_iterator_cb, &lookup_data, objfile);
 
+  if (lookup_data.result != NULL)
+    symbol_cache_mark_found (slot, lookup_data.result);
+  else
+    symbol_cache_mark_not_found (slot, name, domain);
+
   return lookup_data.result;
 }
 
@@ -5235,6 +5636,9 @@  _initialize_symtab (void)
   main_progspace_key
     = register_program_space_data_with_cleanup (NULL, main_info_cleanup);
 
+  symbol_cache_key
+    = register_program_space_data_with_cleanup (NULL, symbol_cache_cleanup);
+
   add_info ("variables", variables_info, _("\
 All global and static variable names, or those matching REGEXP."));
   if (dbx_commands)
@@ -5302,5 +5706,25 @@  A value greater than 1 provides more verbose information."),
 			     NULL,
 			     &setdebuglist, &showdebuglist);
 
+  add_setshow_zuinteger_cmd ("symbol-cache", no_class, &symbol_cache_debug,
+			     _("Set debugging of symbol cache usage."),
+			     _("Show debugging of symbol cache usage."), _("\
+When enabled (non-zero), debugging messages are printed when looking up\n\
+symbols in the symbol cache."),
+			     NULL,
+			     NULL,
+			     &setdebuglist, &showdebuglist);
+
+  add_cmd ("symbol-cache", class_maintenance, maintenance_print_symbol_cache,
+	   _("Dump the symbol cache for each program space."),
+	   &maintenanceprintlist);
+
+  add_cmd ("symbol-cache-statistics", class_maintenance,
+	   maintenance_print_symbol_cache_statistics,
+	   _("Print symbol cache statistics for each program space."),
+	   &maintenanceprintlist);
+
   observer_attach_executable_changed (symtab_observer_executable_changed);
+  observer_attach_new_objfile (symtab_new_objfile_observer);
+  observer_attach_free_objfile (symtab_free_objfile_observer);
 }