[PATCH v2 4/5] resolv: Optimize inet_ntop

DJ Delorie dj@redhat.com
Tue Jun 24 04:00:07 GMT 2025


Adhemerval Zanella <adhemerval.zanella@linaro.org> writes:
> The benchtests/inet_ntop_ipv4 and benchtests/inet_ntop_ipv6 profile
> shows that most of time is spent in costly sprint operations:

LGTM
Reviewed-by: DJ Delorie <dj@redhat.com>

> A new implementation is used instead:
>
>   * The printf usage is replaced with an expanded function that prints
>     either an IPv4 octet or an IPv6 quartet;
>
>   * The strcpy is replaced with a memcpy (since ABIs usually tends to
>     optimize the latter);
>
>   * For IPv6, the '::' shorthanding is done in-place instead of using
>     a temporary buffer.
>
>   * An temporary buffer is used iff the size if larger than
>     INET_ADDRSTRLEN/INET6_ADDRSTRLEN.
>
>   * Inline is used for both inet_ntop4 and inet_ntop6,
>
> The code is significand rewrote, so I take this requires a new license.

Confirmed this is a 95% rewrite.  I won't argue that the remaining 5%
means "derived work; preserve license" but I'll note it's not a 100%
rewrite.

> +static inline char *
> +put_uint8 (uint8_t word, char *tp)
>  {
> +  int s = 1;
> +  if (word >= 10)
>      {
> +      if (word >= 100)
> +	{
> +	  tp[2] = '0' + word % 10;
> +	  word /= 10;
> +	  s += 1;
> +	}
> +
> +      tp[1] = '0' + word % 10;
> +      word /= 10;
> +      s += 1;
>      }
> +  *tp = '0' + word % 10;
> +  return tp + s;
>  }

This is a bit devious and deserving of a comment; I almost complained
about the math before I realized what it was building the string right
to left...

> +static inline char *
> +put_uint16 (uint16_t word, char *tp)
>  {
> +  if (word >= 0x1000)
> +    *tp++ = _itoa_lower_digits[(word >> 12) & 0xf];
> +  if (word >= 0x100)
> +    *tp++ = _itoa_lower_digits[(word >> 8) & 0xf];
> +  if (word >= 0x10)
> +    *tp++ = _itoa_lower_digits[(word >> 4) & 0xf];
> +  *tp++ = _itoa_lower_digits[word & 0xf];
> +  return tp;
> +}

Ok.

> +static __always_inline char *
> +inet_ntop4_format (const uint8_t *src, char *dst)
> +{
> +  dst = put_uint8 (src[0], dst);
> +  *(dst++) = '.';
> +  dst = put_uint8 (src[1], dst);
> +  *(dst++) = '.';
> +  dst = put_uint8 (src[2], dst);
> +  *(dst++) = '.';
> +  dst = put_uint8 (src[3], dst);
> +  *dst++ = '\0';
> +  return dst;
> +}

Ok.

> +static __always_inline const char *
> +inet_ntop4 (const uint8_t *src, char *dst, socklen_t size)
> +{
> +  if (size >= INET_ADDRSTRLEN)
> +    {
> +      inet_ntop4_format (src, dst);
> +      return dst;
> +    }
> +
> +  char tmp[INET_ADDRSTRLEN];
> +  char *tp = inet_ntop4_format (src, tmp);
> +  socklen_t tmp_s = tp - tmp;
> +  if (tmp_s > size)
>      {
>        __set_errno (ENOSPC);
> +      return NULL;
>      }
> +  return memcpy (dst, tmp, tmp_s);
>  }

Ok.

> +struct best_t
>  {
> +  int base;
> +  int len;
> +};
>  
> +static inline uint16_t
> +in6_addr_addr16 (const struct in6_addr *src, int idx)
> +{
> +  const struct { uint16_t x; } __attribute__((__packed__)) *pptr =
> +    (typeof(pptr))(&src->s6_addr16[idx]);
> +  return ntohs (pptr->x);
> +}

Ok.

> +static __always_inline char *
> +inet_ntop6_format (const struct in6_addr *src, struct best_t best, char *dst)
> +{
> +  char *tp = dst;
> +  for (int i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++)
>      {
> +      /* Are we inside the best run of 0x00's? */
> +      if (best.base != -1 && i >= best.base && i < (best.base + best.len))
> +	{
> +	  if (i == best.base)
> +	    *tp++ = ':';
> +	  continue;
> +	}
> +      /* Are we following an initial run of 0x00s or any real hex? */
> +      if (i != 0)
> +	*tp++ = ':';
> +      /* Is this address an encapsulated IPv4? */
> +      if (i == 6 && best.base == 0
> +	  && (best.len == 6 || (best.len == 5
> +				&& in6_addr_addr16 (src, 5) == 0xffff)))
> +	{
> +	  if (!inet_ntop4 (src->s6_addr + 12, tp,
> +			   INET6_ADDRSTRLEN - (tp - dst)))
> +	    return NULL;
> +	  tp += strlen (tp);
> +	  break;
> +	}
> +      tp = put_uint16 (in6_addr_addr16 (src, i), tp);
> +    }
> +  /* Was it a trailing run of 0x00's? */
> +  if (best.base != -1 && (best.base + best.len) == (NS_IN6ADDRSZ / NS_INT16SZ))
> +    *tp++ = ':';
> +  *tp++ = '\0';
> +
> +  return tp;
> +}

Ok.

> +static inline const char *
> +inet_ntop6 (const struct in6_addr *src, char *dst, socklen_t size)
> +{
> +  struct best_t best = { -1, 0 }, cur = { -1, 0 };
> +
> +  /* ind the longest run of 0x00's in src[] for :: shorthanding.  */
> +  for (int i = 0; i < (NS_IN6ADDRSZ / NS_INT16SZ); i++)
> +    {
> +      if (in6_addr_addr16 (src, i) == 0)
>  	{
>  	  if (cur.base == -1)
>  	    cur.base = i, cur.len = 1;
> @@ -155,45 +174,37 @@ inet_ntop6 (const u_char *src, char *dst, socklen_t size)
>    if (best.base != -1 && best.len < 2)
>      best.base = -1;

Ok.

> +  if (size >= INET6_ADDRSTRLEN)
>      {
> +      inet_ntop6_format (src, best, dst);
> +      return dst;
>      }
>  
> +  char tmp[INET6_ADDRSTRLEN];
> +  char *tp = inet_ntop6_format (src, best, tmp);
> +
> +  socklen_t tmp_s = tp - tmp;
> +  if (tmp_s > size)
>      {
>        __set_errno (ENOSPC);
>        return (NULL);
>      }
> +  return memcpy (dst, tmp, tmp_s);
>  }

Ok.

> +const char *
> +__inet_ntop (int af, const void *src, char *dst, socklen_t size)
> +{
> +  switch (af)
> +    {
> +    case AF_INET:
> +      return (inet_ntop4 (src, dst, size));
> +    case AF_INET6:
> +      return (inet_ntop6 (src, dst, size));
> +    default:
> +      __set_errno (EAFNOSUPPORT);
> +      return (NULL);
> +    }
> +}
> +libc_hidden_def (__inet_ntop)
> +weak_alias (__inet_ntop, inet_ntop)

Ok.



More information about the Libc-alpha mailing list