On Tue, Jun 21, 2011 at 12:37 AM, Nicola Pero
<nicola.p...@meta-innovation.com> wrote:
> This patch speeds up the C/C++/ObjC/ObjC++ compiler a little bit by optimizing
> lookup_attribute() and is_attribute_p().  The main change is that these 
> functions
> are now inline.

I don't think this is a good idea.  And I doubt that inlining the loops
is what makes it fast - so, what's the common case that makes
inlining these so much faster?

Btw, the tree.c diff is quite unreadable (stupid diff tool ...), can you
instead attach a context diff?

> Benchmarking the C compiler (--enable-checking=release) compiling postgresql 
> from
> source shows that total compilation times are reduced by about 0.4% with this 
> patch
> (saving about 1 second over an average compilation time of 167 seconds).  
> Benchmarking
> the C++ compiler compiling gold from source shows a similar speedup (about 
> 0.5%).
> Not a huge speedup, but a real one.
>
> The original version of the patch was meant to speed up the Objective-C 
> compiler
> and used preprocessor macros and some hacks to get a similar performance 
> benefit
> in an uglier way.  I prefer this new version because inline functions make 
> the code
> neat and easy to read/understand, while providing similar performance 
> benefits.
>
> The patch contains the following changes:
>
>  * a tiny tweak in attribs.c to avoid looking up the attribute specs
>   for the "naked" attribute for each and every function.  If the
>   function has no attributes whatsoever, the lookup is pointless.

This change is ok.

>  * a tiny tweak in tree.c to speed up attribute_list_equal() which is
>   almost always called with two identical pointers.

As said, the diff is quite unreadable - I'll have a look at a context
diff if you post it.  The change as described sounds reasonable.

>  * inling of lookup_attribute() and is_attribute_p().  Most of the speedup
>   (at least for the C/ObjC compiler, I haven't really studied the C++ one;
>   it's probably the same) comes from the inling of lookup_attribute().
>   The reason inlining these functions is a winner is not just because we save
>   a function call each time they are used, but also because the
>   inlining allows further optimizations to be applied; in particular,
>   the first argument (the attribute name) is almost always a fixed
>   string (eg, lookup_attribute ("visibility", attrs)) and inlining
>   allows the compiler to optimize the strlen() of the first argument.

So, why not for example have lookup_attribute_with_length ()?

>   In the case of lookup_attribute(), the attribute list argument is
>   also almost always NULL; before this patch, even with a NULL attribute
>   argument, you'd still perform at least the function call to 
> lookup_attribute()
>   and then the strlen() of the first argument.  With this patch, if
>   the attribute list argument is NULL and the first argument is a string 
> constant,
>   which is the most likely case, nothing (expensive) should usually happen.

For this case I'd factor out the NULL attribute list check into an
inline function and keeping the non-NULL attribute list pieces out-of-line.

>
>  * changes to lookup_attribute(), is_attribute_p() and remove_attribute()
>   to require the first const char* argument to be in the form "text",
>   disallowing the form "__text__" (the form "__text__" is still allowed
>   in the attribute list; changing that is another simplification I'd like
>   to make, but requires another wave of work).  The only place in the compiler
>   where the form "__text__" was required was inside tree.c, precisely inside
>   functions that are comparing/merging attribute lists.  There I
>   replaced lookup_attribute() with a new static lookup_ident_attribute()
>   which closely matches what is required there. I couldn't find any other 
> place in the
>   compiler where the form "__text__" would be required for the first argument,
>   so it seemed pointless to allow it (particularly as, with the inlining, it
>   would now bloat the code).  I did document this change, and added asserts to
>   catch cases that may have been missed (and, of course, it all still
>   bootstraps with checking enabled, and works fine for me after the change).  
> I
>   also added an assert in attribs.c where attribute specs are registered
>   to make sure that the names do not start with "_".

Does it work for all languages?  And yes, I agree it would be nice to
canonicalize to the form without _s even in the attribute lists itself.
Or go one step further - do not store an identifier but an enum as we
in fact only ever insert known attributes into the list (which should
be a VEC instead, possibly even sorted to allow binary search).

>  * some tidyups in tree.c (in particular, the removal of 
> is_attribute_with_length_p(),
>   and the addition of lookup_ident_attribute(), which are internal details, 
> consequences
>   of the changes above).

Thanks,
Richard.

> OK to commit ?
>
> Thanks
>
> PS: The next steps would be:
>
>  - move all the "attribute list" functions from tree.h/tree.c (ie, 
> lookup_attribute(), remove_attribute(),
>   merge_attributes(), etc) into a separate .h/.c file ?  Presumably 
> attribs.h/attribs.c ?
>
>  - see if we can manage to normalize attributes (eg, from '__text__' to 
> 'text') when storing them in attribute
>   lists; this would simplify/speedup the lookup_attribute() inline call.  I 
> expect that would result in faster
>   compilation, but obviously would need to benchmark.
>
> I'll submit these as separate patches if I work on them; this one is big 
> enough.
>
> PS2: While doing benchmarks, I accidentally benchmarked an older trunk and 
> couldn't but notice that compiling
>     gold with the C++ compiler regressed, in terms of performance, by 1.5% 
> from 2011-05-19 to 2011-06-20.
>
>
> Index: ChangeLog
> ===================================================================
> --- ChangeLog   (revision 173917)
> +++ ChangeLog   (working copy)
> @@ -1,3 +1,24 @@
> +2011-06-19  Nicola Pero  <nicola.p...@meta-innovation.com>
> +
> +       * attribs.c (register_attribute): Added assert to check that all
> +       attribute specs are registered with a name that is not empty and
> +       does not start with '_'.
> +       (decl_attributes): Avoid the lookup of the "naked" attribute spec
> +       if the function has no attributes.
> +       * tree.c (is_attribute_with_length_p): Removed.
> +       (is_attribute_p): Removed.
> +       (remove_attribute): Use is_attribute_p instead of
> +       is_attribute_with_length_p.
> +       (merge_dllimport_decl_attributes): Likewise.
> +       (lookup_ident_attribute): New.
> +       (merge_attributes): Use lookup_ident_attributes instead of
> +       lookup_attribute.
> +       (attribute_list_contained): Likewise.
> +       (attribute_list_equal): Immediately return 1 if the arguments are
> +       identical pointers.
> +       * tree.h (is_attribute_p, lookup_attribute): New inline functions.
> +       Updated comments.
> +
>  2011-05-19  Joseph Myers  <jos...@codesourcery.com>
>
>        * config/arm/arm-fpus.def: New.
> Index: attribs.c
> ===================================================================
> --- attribs.c   (revision 173917)
> +++ attribs.c   (working copy)
> @@ -198,6 +198,11 @@ register_attribute (const struct attribute_spec *a
>
>   str.str = attr->name;
>   str.length = strlen (str.str);
> +
> +  /* Attribute names in the table must be in the form 'text' and not
> +     in the form '__text__'.  */
> +  gcc_assert (str.length > 0 && str.str[0] != '_');
> +
>   slot = htab_find_slot_with_hash (attribute_hash, &str,
>                                   substring_hash (str.str, str.length),
>                                   INSERT);
> @@ -279,6 +284,7 @@ decl_attributes (tree *node, tree attributes, int
>   /* A "naked" function attribute implies "noinline" and "noclone" for
>      those targets that support it.  */
>   if (TREE_CODE (*node) == FUNCTION_DECL
> +      && attributes
>       && lookup_attribute_spec (get_identifier ("naked"))
>       && lookup_attribute ("naked", attributes) != NULL)
>     {
> Index: tree.c
> ===================================================================
> --- tree.c      (revision 173917)
> +++ tree.c      (working copy)
> @@ -5235,101 +5235,80 @@ struct simple_ipa_opt_pass pass_ipa_free_lang_data
>  }
>  };
>
> -/* Return nonzero if IDENT is a valid name for attribute ATTR,
> -   or zero if not.
> +/* Remove any instances of attribute ATTR_NAME in LIST and return the
> +   modified list.  */
>
> -   We try both `text' and `__text__', ATTR may be either one.  */
> -/* ??? It might be a reasonable simplification to require ATTR to be only
> -   `text'.  One might then also require attribute lists to be stored in
> -   their canonicalized form.  */
> -
> -static int
> -is_attribute_with_length_p (const char *attr, int attr_len, const_tree ident)
> +tree
> +remove_attribute (const char *attr_name, tree list)
>  {
> -  int ident_len;
> -  const char *p;
> +  gcc_checking_assert (attr_name[0] != '_');
>
> -  if (TREE_CODE (ident) != IDENTIFIER_NODE)
> -    return 0;
> -
> -  p = IDENTIFIER_POINTER (ident);
> -  ident_len = IDENTIFIER_LENGTH (ident);
> -
> -  if (ident_len == attr_len
> -      && strcmp (attr, p) == 0)
> -    return 1;
> -
> -  /* If ATTR is `__text__', IDENT must be `text'; and vice versa.  */
> -  if (attr[0] == '_')
> +  if (list != NULL_TREE)
>     {
> -      gcc_assert (attr[1] == '_');
> -      gcc_assert (attr[attr_len - 2] == '_');
> -      gcc_assert (attr[attr_len - 1] == '_');
> -      if (ident_len == attr_len - 4
> -         && strncmp (attr + 2, p, attr_len - 4) == 0)
> -       return 1;
> +      tree *p;
> +
> +      for (p = &list; *p; )
> +       {
> +         if (is_attribute_p (attr_name, TREE_PURPOSE (*p)))
> +           *p = TREE_CHAIN (*p);
> +         else
> +           p = &TREE_CHAIN (*p);
> +       }
> +      return list;
>     }
> -  else
> -    {
> -      if (ident_len == attr_len + 4
> -         && p[0] == '_' && p[1] == '_'
> -         && p[ident_len - 2] == '_' && p[ident_len - 1] == '_'
> -         && strncmp (attr, p + 2, attr_len) == 0)
> -       return 1;
> -    }
> -
> -  return 0;
> +  return NULL_TREE;
>  }
>
> -/* Return nonzero if IDENT is a valid name for attribute ATTR,
> -   or zero if not.
> +/* A variant of lookup_attribute() that can be used with an identifier
> +   as the first argument, and where the identifier can be either
> +   'text' or '__text__'.
>
> -   We try both `text' and `__text__', ATTR may be either one.  */
> -
> -int
> -is_attribute_p (const char *attr, const_tree ident)
> +   Given an attribute ATTR_IDENTIFIER, and a list of attributes LIST,
> +   return a pointer to the attribute's list element if the attribute
> +   is part of the list, or NULL_TREE if not found.  If the attribute
> +   appears more than once, this only returns the first occurrence; the
> +   TREE_CHAIN of the return value should be passed back in if further
> +   occurrences are wanted.  ATTR_IDENTIFIER must be an identifier but
> +   can be in the form 'text' or '__text__'.  */
> +static tree
> +lookup_ident_attribute (tree attr_identifier, tree list)
>  {
> -  return is_attribute_with_length_p (attr, strlen (attr), ident);
> -}
> +  gcc_checking_assert (TREE_CODE (attr_identifier) == IDENTIFIER_NODE);
>
> -/* Given an attribute name and a list of attributes, return a pointer to the
> -   attribute's list element if the attribute is part of the list, or 
> NULL_TREE
> -   if not found.  If the attribute appears more than once, this only
> -   returns the first occurrence; the TREE_CHAIN of the return value should
> -   be passed back in if further occurrences are wanted.  */
> -
> -tree
> -lookup_attribute (const char *attr_name, tree list)
> -{
> -  tree l;
> -  size_t attr_len = strlen (attr_name);
> -
> -  for (l = list; l; l = TREE_CHAIN (l))
> +  while (list)
>     {
> -      gcc_assert (TREE_CODE (TREE_PURPOSE (l)) == IDENTIFIER_NODE);
> -      if (is_attribute_with_length_p (attr_name, attr_len, TREE_PURPOSE (l)))
> -       return l;
> -    }
> -  return NULL_TREE;
> -}
> +      gcc_checking_assert (TREE_CODE (TREE_PURPOSE (list)) == 
> IDENTIFIER_NODE);
>
> -/* Remove any instances of attribute ATTR_NAME in LIST and return the
> -   modified list.  */
> +      /* Identifiers can be compared directly for equality.  */
> +      if (attr_identifier == TREE_PURPOSE (list))
> +       break;
>
> -tree
> -remove_attribute (const char *attr_name, tree list)
> -{
> -  tree *p;
> -  size_t attr_len = strlen (attr_name);
> +      /* If they are not equal, they may still be one in the form
> +        'text' while the other one is in the form '__text__'.  */
> +      {
> +       size_t attr_len = IDENTIFIER_LENGTH (attr_identifier);
> +       size_t ident_len = IDENTIFIER_LENGTH (TREE_PURPOSE (list));
>
> -  for (p = &list; *p; )
> -    {
> -      tree l = *p;
> -      gcc_assert (TREE_CODE (TREE_PURPOSE (l)) == IDENTIFIER_NODE);
> -      if (is_attribute_with_length_p (attr_name, attr_len, TREE_PURPOSE (l)))
> -       *p = TREE_CHAIN (l);
> -      else
> -       p = &TREE_CHAIN (l);
> +       if (ident_len == attr_len + 4)
> +         {
> +           const char *p = IDENTIFIER_POINTER (TREE_PURPOSE (list));
> +           const char *q = IDENTIFIER_POINTER (attr_identifier);
> +           if (p[0] == '_' && p[1] == '_'
> +               && p[ident_len - 2] == '_' && p[ident_len - 1] == '_'
> +               && strncmp (q, p + 2, attr_len) == 0)
> +             break;
> +         }
> +       else if (ident_len + 4 == attr_len)
> +         {
> +           const char *p = IDENTIFIER_POINTER (TREE_PURPOSE (list));
> +           const char *q = IDENTIFIER_POINTER (attr_identifier);
> +           if (q[0] == '_' && q[1] == '_'
> +               && q[attr_len - 2] == '_' && q[attr_len - 1] == '_'
> +               && strncmp (q + 2, p, ident_len) == 0)
> +             break;
> +         }
> +      }
> +      list = TREE_CHAIN (list);
>     }
>
>   return list;
> @@ -5363,11 +5342,9 @@ merge_attributes (tree a1, tree a2)
>          for (; a2 != 0; a2 = TREE_CHAIN (a2))
>            {
>              tree a;
> -             for (a = lookup_attribute (IDENTIFIER_POINTER (TREE_PURPOSE 
> (a2)),
> -                                        attributes);
> +             for (a = lookup_ident_attribute (TREE_PURPOSE (a2), attributes);
>                   a != NULL_TREE && !attribute_value_equal (a, a2);
> -                  a = lookup_attribute (IDENTIFIER_POINTER (TREE_PURPOSE 
> (a2)),
> -                                        TREE_CHAIN (a)))
> +                  a = lookup_ident_attribute (TREE_PURPOSE (a2), TREE_CHAIN 
> (a)))
>                ;
>              if (a == NULL_TREE)
>                {
> @@ -5468,13 +5445,11 @@ merge_dllimport_decl_attributes (tree old, tree ne
>   if (delete_dllimport_p)
>     {
>       tree prev, t;
> -      const size_t attr_len = strlen ("dllimport");
>
>       /* Scan the list for dllimport and delete it.  */
>       for (prev = NULL_TREE, t = a; t; prev = t, t = TREE_CHAIN (t))
>        {
> -         if (is_attribute_with_length_p ("dllimport", attr_len,
> -                                         TREE_PURPOSE (t)))
> +         if (is_attribute_p ("dllimport", TREE_PURPOSE (t)))
>            {
>              if (prev == NULL_TREE)
>                a = TREE_CHAIN (a);
> @@ -6271,6 +6246,9 @@ attribute_hash_list (const_tree list, hashval_t ha
>  int
>  attribute_list_equal (const_tree l1, const_tree l2)
>  {
> +  if (l1 == l2)
> +    return 1;
> +
>   return attribute_list_contained (l1, l2)
>         && attribute_list_contained (l2, l1);
>  }
> @@ -6309,11 +6287,9 @@ attribute_list_contained (const_tree l1, const_tre
>       /* This CONST_CAST is okay because lookup_attribute does not
>         modify its argument and the return value is assigned to a
>         const_tree.  */
> -      for (attr = lookup_attribute (IDENTIFIER_POINTER (TREE_PURPOSE (t2)),
> -                                   CONST_CAST_TREE(l1));
> +      for (attr = lookup_ident_attribute (TREE_PURPOSE (t2), 
> CONST_CAST_TREE(l1));
>           attr != NULL_TREE && !attribute_value_equal (t2, attr);
> -          attr = lookup_attribute (IDENTIFIER_POINTER (TREE_PURPOSE (t2)),
> -                                   TREE_CHAIN (attr)))
> +          attr = lookup_ident_attribute (TREE_PURPOSE (t2), TREE_CHAIN 
> (attr)))
>        ;
>
>       if (attr == NULL_TREE)
> Index: tree.h
> ===================================================================
> --- tree.h      (revision 173917)
> +++ tree.h      (working copy)
> @@ -4485,18 +4485,79 @@ enum attribute_flags
>  extern tree merge_decl_attributes (tree, tree);
>  extern tree merge_type_attributes (tree, tree);
>
> -/* Given a tree node and a string, return nonzero if the tree node is
> -   a valid attribute name for the string.  */
> +/* Given an attribute name ATTR_NAME and a list of attributes LIST,
> +   return a pointer to the attribute's list element if the attribute
> +   is part of the list, or NULL_TREE if not found.  If the attribute
> +   appears more than once, this only returns the first occurrence; the
> +   TREE_CHAIN of the return value should be passed back in if further
> +   occurrences are wanted.  ATTR_NAME must be in the form 'text' (not
> +   '__text__').  */
>
> -extern int is_attribute_p (const char *, const_tree);
> +static inline tree
> +lookup_attribute (const char *attr_name, tree list)
> +{
> +  size_t attr_len = strlen (attr_name);
> +  gcc_checking_assert (attr_name[0] != '_');
>
> -/* Given an attribute name and a list of attributes, return the list element
> -   of the attribute or NULL_TREE if not found.  */
> +  while (list)
> +    {
> +      size_t ident_len = IDENTIFIER_LENGTH (TREE_PURPOSE (list));
>
> -extern tree lookup_attribute (const char *, tree);
> +      if (ident_len == attr_len)
> +       {
> +         if (strcmp (attr_name, IDENTIFIER_POINTER (TREE_PURPOSE (list))) == 
> 0)
> +           break;
> +       }
> +      /* TODO: If we made sure that attributes were stored in the
> +        canonical form without '__...__' (ie, as in 'text' as opposed
> +        to '__text__') then we could avoid the following case.  */
> +      else if (ident_len == attr_len + 4)
> +       {
> +         const char *p = IDENTIFIER_POINTER (TREE_PURPOSE (list));
> +         if (p[0] == '_' && p[1] == '_'
> +             && p[ident_len - 2] == '_' && p[ident_len - 1] == '_'
> +             && strncmp (attr_name, p + 2, attr_len) == 0)
> +           break;
> +       }
> +      list = TREE_CHAIN (list);
> +    }
>
> +  return list;
> +}
> +
> +/* Given an identifier node IDENT and a string ATTR_NAME, return true
> +   if the identifier node is a valid attribute name for the string.
> +   ATTR_NAME must be in the form 'text' (not '__text__').  IDENT could
> +   be the identifier for 'text' or for '__text__'.  */
> +static inline bool
> +is_attribute_p (const char *attr_name, const_tree ident)
> +{
> +  size_t attr_len = strlen (attr_name);
> +  size_t ident_len = IDENTIFIER_LENGTH (ident);
> +  gcc_checking_assert (attr_name[0] != '_');
> +
> +  if (ident_len == attr_len)
> +    {
> +      if (strcmp (attr_name, IDENTIFIER_POINTER (ident)) == 0)
> +       return true;
> +    }
> +  else if (ident_len == attr_len + 4)
> +    {
> +      /* There is the possibility that ATTR is 'text' and IDENT is
> +        '__text__'.  */
> +      const char *p = IDENTIFIER_POINTER (ident);
> +      if (p[0] == '_' && p[1] == '_'
> +         && p[ident_len - 2] == '_' && p[ident_len - 1] == '_'
> +         && strncmp (attr_name, p + 2, attr_len) == 0)
> +       return true;
> +    }
> +
> +  return false;
> +}
> +
>  /* Remove any instances of attribute ATTR_NAME in LIST and return the
> -   modified list.  */
> +   modified list.  ATTR_NAME must be in the form 'text' (not
> +   '__text__').  */
>
>  extern tree remove_attribute (const char *, tree);
>
>
>
>
>
>
>
>

Reply via email to