Re: PATCH] Improve performance of memmem

Eric Blake <[email protected]>
Newsgroups gmane.comp.lib.newlib
Message-ID <[email protected]>
On 12/18/18 11:01 AM, Wilco Dijkstra wrote:
> This patch significantly improves performance of memmem using a novel
> modified Horspool algorithm.  Needles up to size 256 use a bad-character
> table indexed by hashed pairs of characters to quickly skip past mismatches.
> Long needles use a self-adapting filtering step to avoid comparing the whole
> needle repeatedly.
> 
> By limiting the needle length to 256, the shift table only requires 8 bits
> per entry, lowering preprocessing overhead and minimizing cache effects.
> This limit also implies worst-case performance is linear.
> 
> Small needles up to size 2 use a dedicated linear search.  Very long needles
> use the Two-Way algorithm (to avoid increasing stack size inlining is now disabled).
> 
> The performance gain is 6.6 times on English text on AArch64 using random
> needles with average size 8 (this is even faster than the recently improved strstr
> algorithm, so I'll update that in the near future).
> 
> The size-optimized memmem has also been rewritten from scratch to get a
> 2.7x performance gain.
> 
> Tested against GLIBC testsuite and randomized tests.
> 
> --

> +
> +#if defined(PREFER_SIZE_OVER_SPEED) || defined(__OPTIMIZE_SIZE__)
> +
> +/* Small and efficient memmem implementation.  */

I'd strike the word 'efficient' in this comment, as...

> +void *
> +memmem (const void *haystack, size_t hs_len, const void *needle, size_t ne_len)
> +{
> +  const char *hs = haystack;
> +  const char *ne = needle;
> +
> +  if (ne_len == 0)
> +    return (void *)hs;
> +  int i;
> +  int c = ne[0];
> +  const char *end = hs + hs_len - ne_len;
> +
> +  for ( ; hs <= end; hs++)
> +  {
> +    if (hs[0] != c)
> +      continue;
> +    for (i = ne_len-1; i != 0; i--)

No spacing around the operator?

> +      if (hs[i] != ne[i])
> +	break;
> +    if (i == 0)
> +      return (void *)hs;

...this is the naive quadratic implementation.  But it does certainly 
qualify as the algorithm to use for someone wanting a compilation as 
small as possible, and who doesn't care about speed (most likely because 
their needles are unlikely to be large enough to notice the effects of 
quadratic slowdowns).


> +#else
>   
> -#if !defined(PREFER_SIZE_OVER_SPEED) && !defined(__OPTIMIZE_SIZE__)
>   # define RETURN_TYPE void *
>   # define AVAILABLE(h, h_l, j, n_l) ((j) <= (h_l) - (n_l))
>   # include "str-two-way.h"
> -#endif
>   
> +#define hash2(p) ((size_t)((p)[0]-((p)[-1]<<3)) % sizeof (shift))
> +
> +
> +/* Fast memmem algorithm with guaranteed linear-time performance.
> +   Small needles up to size 2 use a dedicated linear search.  Longer needles

Why is the super-small needle cutoff at 2, rather than 4 or 8 by taking 
advantage of vectorized searching?

> +   up to size 256 use a novel modified Horspool algorithm.  It hashes pairs
> +   of characters to quickly skip past mismatches.
> +   By limiting the needle length to 256, the shift table can be reduced to 8
> +   bits per entry, lowering preprocessing overhead and minimizing cache effects.
> +   The limit also implies worst-case performance is linear.  On long needles a
> +   self-adapting filtering step is used to quickly determine mismatches.
> +   Needles larger than 256 characters use the linear-time Two-Way algorithm.  */

I can't promise that I tried to completely understand the algorithm, but 
the approach makes sense.

> +  /* Use Two-Way algorithm for very long needles.  */
> +  if (__builtin_expect (ne_len > 256, 0))
> +    return two_way_long_needle (hs, hs_len, ne, ne_len);
>   
> -  /* Larger code size, but guaranteed linear performance.  */
> +  uint8_t shift[256];
> +  size_t tmp, shift1;
> +  size_t m1 = ne_len - 1;
> +  size_t offset = 0;
>   
> -  /* Sanity check, otherwise the loop might search through the whole
> -     memory.  */
> -  if (haystack_len < needle_len)
> -    return NULL;
> +  /* Initialize bad character shift hash table.  */
> +  memset (shift, 0, sizeof (shift));

I don't know if we have a preference for 'sizeof shift' rather than 
'sizeof (shift)'.


-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.