[PATCH v7 2/2] Optimize bsearch() implementation for performance

DJ Delorie dj@redhat.com
Wed Sep 11 22:16:32 GMT 2024


Kuan-Wei Chiu <visitorckw@gmail.com> writes:
> Optimize the bsearch() function to improve binary search performance.
> Although the code size grew by 8 bytes, the new implementation achieves
> a 15% reduction in execution time on my x86 machine, according to the
> bench-bsearch benchmark results.

LGTM as-is but I noted something to try that might speed it up more.
Reviewed-by: DJ Delorie <dj@redhat.com>

> diff --git a/bits/stdlib-bsearch.h b/bits/stdlib-bsearch.h

>  bsearch (const void *__key, const void *__base, size_t __nmemb, size_t __size,
>  	 __compar_fn_t __compar)
>  {
> -  size_t __l, __u, __idx;
>    const void *__p;
>    int __comparison;
>  
> -  __l = 0;
> -  __u = __nmemb;
> -  while (__l < __u)
> +  while (__nmemb)
>      {
> -      __idx = (__l + __u) / 2;
> -      __p = (const void *) (((const char *) __base) + (__idx * __size));
> +      __p = (const void *) (((const char *) __base) + ((__nmemb >> 1) * __size));

We're replacing an add/shift with just a shift, but using the existing
__base and __nmemb instead of the new __l and __u.  Ok.

After this, __p points to base[__nmemb>>1]

>        __comparison = (*__compar) (__key, __p);
> -      if (__comparison < 0)
> -	__u = __idx;
> -      else if (__comparison > 0)
> -	__l = __idx + 1;
> -      else
> +      if (__comparison == 0)

It seems to me performance would be better if the more likely cases went
first - checking "__comparison > 0" would, approximately 50% of the
time, skip the "__comparison == 0" check, but not the other way around.
Have you tried this with the ">0" test before the "==0" test, or with
marking the "==0" test as unlikely?

Keeping the >0 and <0 paths close to the top of the loop might help with
cache hits too, depending on link alignment.

>  	{
>  #if __GNUC_PREREQ(4, 6)
>  # pragma GCC diagnostic push
> @@ -46,6 +38,12 @@ bsearch (const void *__key, const void *__base, size_t __nmemb, size_t __size,
>  # pragma GCC diagnostic pop
>  #endif
>  	}
> +      if (__comparison > 0)
> +	{
> +	  __base = ((const char *) __p) + __size;
> +	  --__nmemb;
> +	}
> +      __nmemb >>= 1;

So if comparison < 0 (key is before probe):
  __nmemb is set to the number of elements before __p which is base[__nmemb>>1]
  __base is unchanged
    - range is 0..__nmemb-1

if comparison is > 0 (key is after probe):
  __base is set to the element after the probe
  If (__nmemb is odd)
    probe point is at (__nmemb-1) >> 1
    same numbers before and after probe
    subtracting one is unneeded but has no effect

0 1 2 3 4 5 6 7 8  (n == 9)  (n>>1 == 4)
        P
0 1 2 3 4 5 6  (n == 7)  (n>>1 == 3)
      P

  If (__nmemb is even)
    probe point is at (__nmemb) >> 1
    even numbers before but odd number after
    subtracting one is needed

0 1 2 3 4 5 6 7  (n == 8)  (n>>1 == 4)
        P
0 1 2 3 4 5  (n == 6)  (n>>1 == 3)
      P

Ok.

>      }
>  
>    return NULL;



More information about the Libc-alpha mailing list