Performance improvements for src/port/snprintf.c - Mailing list pgsql-hackers

From Tom Lane
Subject Performance improvements for src/port/snprintf.c
Date
Msg-id 11787.1534530779@sss.pgh.pa.us
Whole thread Raw
Responses Re: Performance improvements for src/port/snprintf.c
Re: Performance improvements for src/port/snprintf.c
List pgsql-hackers
Over in the what-about-%m thread, we speculated about replacing the
platform's *printf functions if they didn't support %m, which would
basically mean using src/port/snprintf.c on all non-glibc platforms,
rather than only on Windows as happens right now (ignoring some
obsolete platforms with busted snprintf's).

I've been looking into the possible performance consequences of that,
in particular comparing snprintf.c to the library versions on macOS,
FreeBSD, OpenBSD, and NetBSD.  While it held up well in simpler cases,
I noted that it was significantly slower on long format strings, which
I traced to two separate problems:

1. Our implementation always scans the format string twice, so that it
can sort out argument-ordering options (%n$).  Everybody else is bright
enough to do that only for formats that actually use %n$, and it turns
out that it doesn't really cost anything extra to do so: you can just
perform the extra scan when and if you first find a dollar specifier.
(Perhaps there's an arguable downside for this, with invalid format
strings that have non-dollar conversion specs followed by dollar ones:
with this approach we might fetch some arguments before realizing that
the format is broken.  But a wrong format can cause indefinitely bad
results already, so that seems like a pretty thin objection to me,
especially if all other implementations share the same hazard.)

2. Our implementation is shoving simple data characters in the format
out to the result buffer one at a time.  More common is to skip to the
next % as fast as possible, and then dump anything skipped over using
the string-output code path, reducing the overhead of buffer overrun
checking.

The attached patch fixes both of those things, and also does some
micro-optimization hacking to avoid loops around dopr_outch() as well
as unnecessary use of pass-by-ref arguments.  This version stacks up
pretty well against all the libraries I compared it to.  The remaining
weak spot is that floating-point conversions are consistently 30%-50%
slower than the native libraries, which is not terribly surprising
considering that our implementation involves calling the native sprintf
and then massaging the result.  Perhaps there's a way to improve that
without writing our own floating-point conversion code, but I'm not
seeing an easy way offhand.  I don't think that's a showstopper though.
This code is now faster than the native code for very many other cases,
so on average it should cause no real performance problem.

I've attached both the patch and a simple performance testbed in case
anybody wants to do their own measurements.  For reference's sake,
these are the specific test cases I looked at:

        snprintf(buffer, sizeof(buffer),
                 "%2$.*3$f %1$d\n",
                 42, 123.456, 2);

        snprintf(buffer, sizeof(buffer),
                 "%.*g", 15, 123.456);

        snprintf(buffer, sizeof(buffer),
                 "%d %d", 15, 16);

        snprintf(buffer, sizeof(buffer),
                 "%10d", 15);

        snprintf(buffer, sizeof(buffer),
                 "%s",

"0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890");

        snprintf(buffer, sizeof(buffer),
                 "%d
0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890",

        snprintf(buffer, sizeof(buffer),
                 "%1$d
0123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890012345678900123456789001234567890",
                 42);

A couple of other notes of interest:

* The skip-to-next-% searches could alternatively be implemented with
strchr(), although then you need a strlen() call if there isn't another %.
glibc's version of strchr() is fast enough to make that a win, but since
we're not contemplating using this atop glibc, that's not a case we care
about.  On other platforms the manual loop mostly seems to be faster.

* NetBSD seems to have a special fast path for the case that the format
string is exactly "%s".  I did not adopt that idea here, reasoning that
checking for it would add overhead to all other cases, making it probably
a net loss overall.  I'm prepared to listen to arguments otherwise,
though.  It is a common case, I just doubt it's common enough (and
other library authors seem to agree).

I'll add this to the upcoming CF.

            regards, tom lane

diff --git a/src/port/snprintf.c b/src/port/snprintf.c
index 851e2ae..211ff1b 100644
*** a/src/port/snprintf.c
--- b/src/port/snprintf.c
*************** flushbuffer(PrintfTarget *target)
*** 295,301 ****
  }


! static void fmtstr(char *value, int leftjust, int minlen, int maxwidth,
         int pointflag, PrintfTarget *target);
  static void fmtptr(void *value, PrintfTarget *target);
  static void fmtint(int64 value, char type, int forcesign,
--- 295,303 ----
  }


! static bool find_arguments(const char *format, va_list args,
!                PrintfArgValue *argvalues);
! static void fmtstr(const char *value, int leftjust, int minlen, int maxwidth,
         int pointflag, PrintfTarget *target);
  static void fmtptr(void *value, PrintfTarget *target);
  static void fmtint(int64 value, char type, int forcesign,
*************** static void fmtfloat(double value, char
*** 307,317 ****
           PrintfTarget *target);
  static void dostr(const char *str, int slen, PrintfTarget *target);
  static void dopr_outch(int c, PrintfTarget *target);
  static int    adjust_sign(int is_negative, int forcesign, int *signvalue);
! static void adjust_padlen(int minlen, int vallen, int leftjust, int *padlen);
! static void leading_pad(int zpad, int *signvalue, int *padlen,
              PrintfTarget *target);
! static void trailing_pad(int *padlen, PrintfTarget *target);


  /*
--- 309,320 ----
           PrintfTarget *target);
  static void dostr(const char *str, int slen, PrintfTarget *target);
  static void dopr_outch(int c, PrintfTarget *target);
+ static void dopr_outchmulti(int c, int slen, PrintfTarget *target);
  static int    adjust_sign(int is_negative, int forcesign, int *signvalue);
! static int    compute_padlen(int minlen, int vallen, int leftjust);
! static void leading_pad(int zpad, int signvalue, int *padlen,
              PrintfTarget *target);
! static void trailing_pad(int padlen, PrintfTarget *target);


  /*
*************** static void trailing_pad(int *padlen, Pr
*** 320,329 ****
  static void
  dopr(PrintfTarget *target, const char *format, va_list args)
  {
!     const char *format_start = format;
      int            ch;
      bool        have_dollar;
-     bool        have_non_dollar;
      bool        have_star;
      bool        afterstar;
      int            accum;
--- 323,331 ----
  static void
  dopr(PrintfTarget *target, const char *format, va_list args)
  {
!     const char *first_pct = NULL;
      int            ch;
      bool        have_dollar;
      bool        have_star;
      bool        afterstar;
      int            accum;
*************** dopr(PrintfTarget *target, const char *f
*** 335,559 ****
      int            precision;
      int            zpad;
      int            forcesign;
-     int            last_dollar;
      int            fmtpos;
      int            cvalue;
      int64        numvalue;
      double        fvalue;
      char       *strvalue;
-     int            i;
-     PrintfArgType argtypes[NL_ARGMAX + 1];
      PrintfArgValue argvalues[NL_ARGMAX + 1];

      /*
!      * Parse the format string to determine whether there are %n$ format
!      * specs, and identify the types and order of the format parameters.
       */
!     have_dollar = have_non_dollar = false;
!     last_dollar = 0;
!     MemSet(argtypes, 0, sizeof(argtypes));

!     while ((ch = *format++) != '\0')
      {
!         if (ch != '%')
!             continue;
!         longflag = longlongflag = pointflag = 0;
!         fmtpos = accum = 0;
!         afterstar = false;
! nextch1:
!         ch = *format++;
!         if (ch == '\0')
!             break;                /* illegal, but we don't complain */
!         switch (ch)
          {
!             case '-':
!             case '+':
!                 goto nextch1;
!             case '0':
!             case '1':
!             case '2':
!             case '3':
!             case '4':
!             case '5':
!             case '6':
!             case '7':
!             case '8':
!             case '9':
!                 accum = accum * 10 + (ch - '0');
!                 goto nextch1;
!             case '.':
!                 pointflag = 1;
!                 accum = 0;
!                 goto nextch1;
!             case '*':
!                 if (afterstar)
!                     have_non_dollar = true; /* multiple stars */
!                 afterstar = true;
!                 accum = 0;
!                 goto nextch1;
!             case '$':
!                 have_dollar = true;
!                 if (accum <= 0 || accum > NL_ARGMAX)
!                     goto bad_format;
!                 if (afterstar)
!                 {
!                     if (argtypes[accum] &&
!                         argtypes[accum] != ATYPE_INT)
!                         goto bad_format;
!                     argtypes[accum] = ATYPE_INT;
!                     last_dollar = Max(last_dollar, accum);
!                     afterstar = false;
!                 }
!                 else
!                     fmtpos = accum;
!                 accum = 0;
!                 goto nextch1;
!             case 'l':
!                 if (longflag)
!                     longlongflag = 1;
!                 else
!                     longflag = 1;
!                 goto nextch1;
!             case 'z':
! #if SIZEOF_SIZE_T == 8
! #ifdef HAVE_LONG_INT_64
!                 longflag = 1;
! #elif defined(HAVE_LONG_LONG_INT_64)
!                 longlongflag = 1;
! #else
! #error "Don't know how to print 64bit integers"
! #endif
! #else
!                 /* assume size_t is same size as int */
! #endif
!                 goto nextch1;
!             case 'h':
!             case '\'':
!                 /* ignore these */
!                 goto nextch1;
!             case 'd':
!             case 'i':
!             case 'o':
!             case 'u':
!             case 'x':
!             case 'X':
!                 if (fmtpos)
!                 {
!                     PrintfArgType atype;

!                     if (longlongflag)
!                         atype = ATYPE_LONGLONG;
!                     else if (longflag)
!                         atype = ATYPE_LONG;
!                     else
!                         atype = ATYPE_INT;
!                     if (argtypes[fmtpos] &&
!                         argtypes[fmtpos] != atype)
!                         goto bad_format;
!                     argtypes[fmtpos] = atype;
!                     last_dollar = Max(last_dollar, fmtpos);
!                 }
!                 else
!                     have_non_dollar = true;
!                 break;
!             case 'c':
!                 if (fmtpos)
!                 {
!                     if (argtypes[fmtpos] &&
!                         argtypes[fmtpos] != ATYPE_INT)
!                         goto bad_format;
!                     argtypes[fmtpos] = ATYPE_INT;
!                     last_dollar = Max(last_dollar, fmtpos);
!                 }
!                 else
!                     have_non_dollar = true;
!                 break;
!             case 's':
!             case 'p':
!                 if (fmtpos)
!                 {
!                     if (argtypes[fmtpos] &&
!                         argtypes[fmtpos] != ATYPE_CHARPTR)
!                         goto bad_format;
!                     argtypes[fmtpos] = ATYPE_CHARPTR;
!                     last_dollar = Max(last_dollar, fmtpos);
!                 }
!                 else
!                     have_non_dollar = true;
!                 break;
!             case 'e':
!             case 'E':
!             case 'f':
!             case 'g':
!             case 'G':
!                 if (fmtpos)
!                 {
!                     if (argtypes[fmtpos] &&
!                         argtypes[fmtpos] != ATYPE_DOUBLE)
!                         goto bad_format;
!                     argtypes[fmtpos] = ATYPE_DOUBLE;
!                     last_dollar = Max(last_dollar, fmtpos);
!                 }
!                 else
!                     have_non_dollar = true;
                  break;
!             case '%':
                  break;
          }

          /*
!          * If we finish the spec with afterstar still set, there's a
!          * non-dollar star in there.
           */
!         if (afterstar)
!             have_non_dollar = true;
!     }
!
!     /* Per spec, you use either all dollar or all not. */
!     if (have_dollar && have_non_dollar)
!         goto bad_format;
!
!     /*
!      * In dollar mode, collect the arguments in physical order.
!      */
!     for (i = 1; i <= last_dollar; i++)
!     {
!         switch (argtypes[i])
!         {
!             case ATYPE_NONE:
!                 goto bad_format;
!             case ATYPE_INT:
!                 argvalues[i].i = va_arg(args, int);
!                 break;
!             case ATYPE_LONG:
!                 argvalues[i].l = va_arg(args, long);
!                 break;
!             case ATYPE_LONGLONG:
!                 argvalues[i].ll = va_arg(args, int64);
!                 break;
!             case ATYPE_DOUBLE:
!                 argvalues[i].d = va_arg(args, double);
!                 break;
!             case ATYPE_CHARPTR:
!                 argvalues[i].cptr = va_arg(args, char *);
!                 break;
!         }
!     }
!
!     /*
!      * At last we can parse the format for real.
!      */
!     format = format_start;
!     while ((ch = *format++) != '\0')
!     {
!         if (target->failed)
!             break;

!         if (ch != '%')
!         {
!             dopr_outch(ch, target);
!             continue;
!         }
          fieldwidth = precision = zpad = leftjust = forcesign = 0;
          longflag = longlongflag = pointflag = 0;
          fmtpos = accum = 0;
--- 337,387 ----
      int            precision;
      int            zpad;
      int            forcesign;
      int            fmtpos;
      int            cvalue;
      int64        numvalue;
      double        fvalue;
      char       *strvalue;
      PrintfArgValue argvalues[NL_ARGMAX + 1];

      /*
!      * Initially, we suppose the format string does not use %n$.  The first
!      * time we come to a conversion spec that has that, we'll call
!      * find_arguments() to check for consistent use of %n$ and fill the
!      * argvalues array with the argument values in the correct order.
       */
!     have_dollar = false;

!     while (*format != '\0')
      {
!         /* Locate next conversion specifier */
!         if (*format != '%')
          {
!             const char *next_pct = format + 1;

!             while (*next_pct != '\0' && *next_pct != '%')
!                 next_pct++;
!
!             /* Dump literal data we just scanned over */
!             dostr(format, next_pct - format, target);
!             if (target->failed)
                  break;
!
!             if (*next_pct == '\0')
                  break;
+             format = next_pct;
          }

          /*
!          * Remember start of first conversion spec; if we find %n$, then it's
!          * sufficient for find_arguments() to start here, without rescanning
!          * earlier literal text.
           */
!         if (first_pct == NULL)
!             first_pct = format;

!         /* Process conversion spec starting at *format */
!         format++;
          fieldwidth = precision = zpad = leftjust = forcesign = 0;
          longflag = longlongflag = pointflag = 0;
          fmtpos = accum = 0;
*************** nextch2:
*** 597,603 ****
              case '*':
                  if (have_dollar)
                  {
!                     /* process value after reading n$ */
                      afterstar = true;
                  }
                  else
--- 425,435 ----
              case '*':
                  if (have_dollar)
                  {
!                     /*
!                      * We'll process value after reading n$.  Note it's OK to
!                      * assume have_dollar is set correctly, because in a valid
!                      * format string the initial % must have had n$ if * does.
!                      */
                      afterstar = true;
                  }
                  else
*************** nextch2:
*** 628,633 ****
--- 460,473 ----
                  accum = 0;
                  goto nextch2;
              case '$':
+                 /* First dollar sign? */
+                 if (!have_dollar)
+                 {
+                     /* Yup, so examine all conversion specs in format */
+                     if (!find_arguments(first_pct, args, argvalues))
+                         goto bad_format;
+                     have_dollar = true;
+                 }
                  if (afterstar)
                  {
                      /* fetch and process star value */
*************** nextch2:
*** 806,811 ****
--- 646,655 ----
                  dopr_outch('%', target);
                  break;
          }
+
+         /* Check for failure after each conversion spec */
+         if (target->failed)
+             break;
      }

      return;
*************** bad_format:
*** 815,822 ****
      target->failed = true;
  }

  static void
! fmtstr(char *value, int leftjust, int minlen, int maxwidth,
         int pointflag, PrintfTarget *target)
  {
      int            padlen,
--- 659,896 ----
      target->failed = true;
  }

+ /*
+  * find_arguments(): sort out the arguments for a format spec with %n$
+  *
+  * If format is valid, return true and fill argvalues[i] with the value
+  * for the conversion spec that has %i$ or *i$.  Else return false.
+  */
+ static bool
+ find_arguments(const char *format, va_list args,
+                PrintfArgValue *argvalues)
+ {
+     int            ch;
+     bool        afterstar;
+     int            accum;
+     int            longlongflag;
+     int            longflag;
+     int            fmtpos;
+     int            i;
+     int            last_dollar;
+     PrintfArgType argtypes[NL_ARGMAX + 1];
+
+     /* Initialize to "no dollar arguments known" */
+     last_dollar = 0;
+     MemSet(argtypes, 0, sizeof(argtypes));
+
+     /*
+      * This loop must accept the same format strings as the one in dopr().
+      * However, we don't need to analyze them to the same level of detail.
+      *
+      * Since we're only called if there's a dollar-type spec somewhere, we can
+      * fail immediately if we find a non-dollar spec.  Per the C99 standard,
+      * all argument references in the format string must be one or the other.
+      */
+     while (*format != '\0')
+     {
+         /* Locate next conversion specifier */
+         if (*format != '%')
+         {
+             const char *next_pct = format + 1;
+
+             while (*next_pct != '\0' && *next_pct != '%')
+                 next_pct++;
+             if (*next_pct == '\0')
+                 break;
+             format = next_pct;
+         }
+
+         /* Process conversion spec starting at *format */
+         format++;
+         longflag = longlongflag = 0;
+         fmtpos = accum = 0;
+         afterstar = false;
+ nextch1:
+         ch = *format++;
+         if (ch == '\0')
+             break;                /* illegal, but we don't complain */
+         switch (ch)
+         {
+             case '-':
+             case '+':
+                 goto nextch1;
+             case '0':
+             case '1':
+             case '2':
+             case '3':
+             case '4':
+             case '5':
+             case '6':
+             case '7':
+             case '8':
+             case '9':
+                 accum = accum * 10 + (ch - '0');
+                 goto nextch1;
+             case '.':
+                 accum = 0;
+                 goto nextch1;
+             case '*':
+                 if (afterstar)
+                     return false;    /* previous star missing dollar */
+                 afterstar = true;
+                 accum = 0;
+                 goto nextch1;
+             case '$':
+                 if (accum <= 0 || accum > NL_ARGMAX)
+                     return false;
+                 if (afterstar)
+                 {
+                     if (argtypes[accum] &&
+                         argtypes[accum] != ATYPE_INT)
+                         return false;
+                     argtypes[accum] = ATYPE_INT;
+                     last_dollar = Max(last_dollar, accum);
+                     afterstar = false;
+                 }
+                 else
+                     fmtpos = accum;
+                 accum = 0;
+                 goto nextch1;
+             case 'l':
+                 if (longflag)
+                     longlongflag = 1;
+                 else
+                     longflag = 1;
+                 goto nextch1;
+             case 'z':
+ #if SIZEOF_SIZE_T == 8
+ #ifdef HAVE_LONG_INT_64
+                 longflag = 1;
+ #elif defined(HAVE_LONG_LONG_INT_64)
+                 longlongflag = 1;
+ #else
+ #error "Don't know how to print 64bit integers"
+ #endif
+ #else
+                 /* assume size_t is same size as int */
+ #endif
+                 goto nextch1;
+             case 'h':
+             case '\'':
+                 /* ignore these */
+                 goto nextch1;
+             case 'd':
+             case 'i':
+             case 'o':
+             case 'u':
+             case 'x':
+             case 'X':
+                 if (fmtpos)
+                 {
+                     PrintfArgType atype;
+
+                     if (longlongflag)
+                         atype = ATYPE_LONGLONG;
+                     else if (longflag)
+                         atype = ATYPE_LONG;
+                     else
+                         atype = ATYPE_INT;
+                     if (argtypes[fmtpos] &&
+                         argtypes[fmtpos] != atype)
+                         return false;
+                     argtypes[fmtpos] = atype;
+                     last_dollar = Max(last_dollar, fmtpos);
+                 }
+                 else
+                     return false;    /* non-dollar conversion spec */
+                 break;
+             case 'c':
+                 if (fmtpos)
+                 {
+                     if (argtypes[fmtpos] &&
+                         argtypes[fmtpos] != ATYPE_INT)
+                         return false;
+                     argtypes[fmtpos] = ATYPE_INT;
+                     last_dollar = Max(last_dollar, fmtpos);
+                 }
+                 else
+                     return false;    /* non-dollar conversion spec */
+                 break;
+             case 's':
+             case 'p':
+                 if (fmtpos)
+                 {
+                     if (argtypes[fmtpos] &&
+                         argtypes[fmtpos] != ATYPE_CHARPTR)
+                         return false;
+                     argtypes[fmtpos] = ATYPE_CHARPTR;
+                     last_dollar = Max(last_dollar, fmtpos);
+                 }
+                 else
+                     return false;    /* non-dollar conversion spec */
+                 break;
+             case 'e':
+             case 'E':
+             case 'f':
+             case 'g':
+             case 'G':
+                 if (fmtpos)
+                 {
+                     if (argtypes[fmtpos] &&
+                         argtypes[fmtpos] != ATYPE_DOUBLE)
+                         return false;
+                     argtypes[fmtpos] = ATYPE_DOUBLE;
+                     last_dollar = Max(last_dollar, fmtpos);
+                 }
+                 else
+                     return false;    /* non-dollar conversion spec */
+                 break;
+             case '%':
+                 break;
+         }
+
+         /*
+          * If we finish the spec with afterstar still set, there's a
+          * non-dollar star in there.
+          */
+         if (afterstar)
+             return false;        /* non-dollar conversion spec */
+     }
+
+     /*
+      * Format appears valid so far, so collect the arguments in physical
+      * order.  (Since we rejected any non-dollar specs that would have
+      * collected arguments, we know that dopr() hasn't collected any yet.)
+      */
+     for (i = 1; i <= last_dollar; i++)
+     {
+         switch (argtypes[i])
+         {
+             case ATYPE_NONE:
+                 return false;
+             case ATYPE_INT:
+                 argvalues[i].i = va_arg(args, int);
+                 break;
+             case ATYPE_LONG:
+                 argvalues[i].l = va_arg(args, long);
+                 break;
+             case ATYPE_LONGLONG:
+                 argvalues[i].ll = va_arg(args, int64);
+                 break;
+             case ATYPE_DOUBLE:
+                 argvalues[i].d = va_arg(args, double);
+                 break;
+             case ATYPE_CHARPTR:
+                 argvalues[i].cptr = va_arg(args, char *);
+                 break;
+         }
+     }
+
+     return true;
+ }
+
  static void
! fmtstr(const char *value, int leftjust, int minlen, int maxwidth,
         int pointflag, PrintfTarget *target)
  {
      int            padlen,
*************** fmtstr(char *value, int leftjust, int mi
*** 831,847 ****
      else
          vallen = strlen(value);

!     adjust_padlen(minlen, vallen, leftjust, &padlen);

!     while (padlen > 0)
      {
!         dopr_outch(' ', target);
!         --padlen;
      }

      dostr(value, vallen, target);

!     trailing_pad(&padlen, target);
  }

  static void
--- 905,921 ----
      else
          vallen = strlen(value);

!     padlen = compute_padlen(minlen, vallen, leftjust);

!     if (padlen > 0)
      {
!         dopr_outchmulti(' ', padlen, target);
!         padlen = 0;
      }

      dostr(value, vallen, target);

!     trailing_pad(padlen, target);
  }

  static void
*************** fmtint(int64 value, char type, int force
*** 869,875 ****
      int            signvalue = 0;
      char        convert[64];
      int            vallen = 0;
!     int            padlen = 0;        /* amount to pad */
      int            zeropad;        /* extra leading zeroes */

      switch (type)
--- 943,949 ----
      int            signvalue = 0;
      char        convert[64];
      int            vallen = 0;
!     int            padlen;            /* amount to pad */
      int            zeropad;        /* extra leading zeroes */

      switch (type)
*************** fmtint(int64 value, char type, int force
*** 917,958 ****

          do
          {
!             convert[vallen++] = cvt[uvalue % base];
              uvalue = uvalue / base;
          } while (uvalue);
      }

      zeropad = Max(0, precision - vallen);

!     adjust_padlen(minlen, vallen + zeropad, leftjust, &padlen);

!     leading_pad(zpad, &signvalue, &padlen, target);

!     while (zeropad-- > 0)
!         dopr_outch('0', target);

!     while (vallen > 0)
!         dopr_outch(convert[--vallen], target);

!     trailing_pad(&padlen, target);
  }

  static void
  fmtchar(int value, int leftjust, int minlen, PrintfTarget *target)
  {
!     int            padlen = 0;        /* amount to pad */

!     adjust_padlen(minlen, 1, leftjust, &padlen);

!     while (padlen > 0)
      {
!         dopr_outch(' ', target);
!         --padlen;
      }

      dopr_outch(value, target);

!     trailing_pad(&padlen, target);
  }

  static void
--- 991,1031 ----

          do
          {
!             convert[sizeof(convert) - (++vallen)] = cvt[uvalue % base];
              uvalue = uvalue / base;
          } while (uvalue);
      }

      zeropad = Max(0, precision - vallen);

!     padlen = compute_padlen(minlen, vallen + zeropad, leftjust);

!     leading_pad(zpad, signvalue, &padlen, target);

!     if (zeropad > 0)
!         dopr_outchmulti('0', zeropad, target);

!     dostr(convert + sizeof(convert) - vallen, vallen, target);

!     trailing_pad(padlen, target);
  }

  static void
  fmtchar(int value, int leftjust, int minlen, PrintfTarget *target)
  {
!     int            padlen;            /* amount to pad */

!     padlen = compute_padlen(minlen, 1, leftjust);

!     if (padlen > 0)
      {
!         dopr_outchmulti(' ', padlen, target);
!         padlen = 0;
      }

      dopr_outch(value, target);

!     trailing_pad(padlen, target);
  }

  static void
*************** fmtfloat(double value, char type, int fo
*** 966,972 ****
      char        fmt[32];
      char        convert[1024];
      int            zeropadlen = 0; /* amount to pad with zeroes */
!     int            padlen = 0;        /* amount to pad with spaces */

      /*
       * We rely on the regular C library's sprintf to do the basic conversion,
--- 1039,1045 ----
      char        fmt[32];
      char        convert[1024];
      int            zeropadlen = 0; /* amount to pad with zeroes */
!     int            padlen;            /* amount to pad with spaces */

      /*
       * We rely on the regular C library's sprintf to do the basic conversion,
*************** fmtfloat(double value, char type, int fo
*** 1006,1014 ****
      if (zeropadlen > 0 && !isdigit((unsigned char) convert[vallen - 1]))
          zeropadlen = 0;

!     adjust_padlen(minlen, vallen + zeropadlen, leftjust, &padlen);

!     leading_pad(zpad, &signvalue, &padlen, target);

      if (zeropadlen > 0)
      {
--- 1079,1087 ----
      if (zeropadlen > 0 && !isdigit((unsigned char) convert[vallen - 1]))
          zeropadlen = 0;

!     padlen = compute_padlen(minlen, vallen + zeropadlen, leftjust);

!     leading_pad(zpad, signvalue, &padlen, target);

      if (zeropadlen > 0)
      {
*************** fmtfloat(double value, char type, int fo
*** 1021,1036 ****
          {
              /* pad after exponent */
              dostr(convert, epos - convert, target);
!             while (zeropadlen-- > 0)
!                 dopr_outch('0', target);
              dostr(epos, vallen - (epos - convert), target);
          }
          else
          {
              /* no exponent, pad after the digits */
              dostr(convert, vallen, target);
!             while (zeropadlen-- > 0)
!                 dopr_outch('0', target);
          }
      }
      else
--- 1094,1109 ----
          {
              /* pad after exponent */
              dostr(convert, epos - convert, target);
!             if (zeropadlen > 0)
!                 dopr_outchmulti('0', zeropadlen, target);
              dostr(epos, vallen - (epos - convert), target);
          }
          else
          {
              /* no exponent, pad after the digits */
              dostr(convert, vallen, target);
!             if (zeropadlen > 0)
!                 dopr_outchmulti('0', zeropadlen, target);
          }
      }
      else
*************** fmtfloat(double value, char type, int fo
*** 1039,1045 ****
          dostr(convert, vallen, target);
      }

!     trailing_pad(&padlen, target);
      return;

  fail:
--- 1112,1118 ----
          dostr(convert, vallen, target);
      }

!     trailing_pad(padlen, target);
      return;

  fail:
*************** fail:
*** 1049,1054 ****
--- 1122,1134 ----
  static void
  dostr(const char *str, int slen, PrintfTarget *target)
  {
+     /* fast path for common case of slen == 1 */
+     if (slen == 1)
+     {
+         dopr_outch(*str, target);
+         return;
+     }
+
      while (slen > 0)
      {
          int            avail;
*************** dopr_outch(int c, PrintfTarget *target)
*** 1092,1097 ****
--- 1172,1213 ----
      *(target->bufptr++) = c;
  }

+ static void
+ dopr_outchmulti(int c, int slen, PrintfTarget *target)
+ {
+     /* fast path for common case of slen == 1 */
+     if (slen == 1)
+     {
+         dopr_outch(c, target);
+         return;
+     }
+
+     while (slen > 0)
+     {
+         int            avail;
+
+         if (target->bufend != NULL)
+             avail = target->bufend - target->bufptr;
+         else
+             avail = slen;
+         if (avail <= 0)
+         {
+             /* buffer full, can we dump to stream? */
+             if (target->stream == NULL)
+             {
+                 target->nchars += slen; /* no, lose the data */
+                 return;
+             }
+             flushbuffer(target);
+             continue;
+         }
+         avail = Min(avail, slen);
+         memset(target->bufptr, c, avail);
+         target->bufptr += avail;
+         slen -= avail;
+     }
+ }
+

  static int
  adjust_sign(int is_negative, int forcesign, int *signvalue)
*************** adjust_sign(int is_negative, int forcesi
*** 1107,1148 ****
  }


! static void
! adjust_padlen(int minlen, int vallen, int leftjust, int *padlen)
  {
!     *padlen = minlen - vallen;
!     if (*padlen < 0)
!         *padlen = 0;
      if (leftjust)
!         *padlen = -(*padlen);
  }


  static void
! leading_pad(int zpad, int *signvalue, int *padlen, PrintfTarget *target)
  {
      if (*padlen > 0 && zpad)
      {
!         if (*signvalue)
          {
!             dopr_outch(*signvalue, target);
              --(*padlen);
!             *signvalue = 0;
          }
!         while (*padlen > 0)
          {
!             dopr_outch(zpad, target);
!             --(*padlen);
          }
      }
!     while (*padlen > (*signvalue != 0))
      {
!         dopr_outch(' ', target);
!         --(*padlen);
      }
!     if (*signvalue)
      {
!         dopr_outch(*signvalue, target);
          if (*padlen > 0)
              --(*padlen);
          else if (*padlen < 0)
--- 1223,1270 ----
  }


! static int
! compute_padlen(int minlen, int vallen, int leftjust)
  {
!     int            padlen;
!
!     padlen = minlen - vallen;
!     if (padlen < 0)
!         padlen = 0;
      if (leftjust)
!         padlen = -padlen;
!     return padlen;
  }


  static void
! leading_pad(int zpad, int signvalue, int *padlen, PrintfTarget *target)
  {
+     int            maxpad;
+
      if (*padlen > 0 && zpad)
      {
!         if (signvalue)
          {
!             dopr_outch(signvalue, target);
              --(*padlen);
!             signvalue = 0;
          }
!         if (*padlen > 0)
          {
!             dopr_outchmulti(zpad, *padlen, target);
!             *padlen = 0;
          }
      }
!     maxpad = (signvalue != 0);
!     if (*padlen > maxpad)
      {
!         dopr_outchmulti(' ', *padlen - maxpad, target);
!         *padlen = maxpad;
      }
!     if (signvalue)
      {
!         dopr_outch(signvalue, target);
          if (*padlen > 0)
              --(*padlen);
          else if (*padlen < 0)
*************** leading_pad(int zpad, int *signvalue, in
*** 1152,1162 ****


  static void
! trailing_pad(int *padlen, PrintfTarget *target)
  {
!     while (*padlen < 0)
!     {
!         dopr_outch(' ', target);
!         ++(*padlen);
!     }
  }
--- 1274,1281 ----


  static void
! trailing_pad(int padlen, PrintfTarget *target)
  {
!     if (padlen < 0)
!         dopr_outchmulti(' ', -padlen, target);
  }
#include "postgres_fe.h"

#include "portability/instr_time.h"

#include "snprintf.c"

int
main(int argc, char **argv)
{
    int count = 0;
    char buffer[1000];
    instr_time    start;
    instr_time    stop;
    double elapsed;
    double elapsed2;
    int i;

    if (argc > 1)
        count = atoi(argv[1]);
    if (count <= 0)
        count = 1000000;

    INSTR_TIME_SET_CURRENT(start);

    for (i = 0; i < count; i++)
    {
        snprintf(buffer, sizeof(buffer),
                 "%d %d", 15, 16);
    }

    INSTR_TIME_SET_CURRENT(stop);
    INSTR_TIME_SUBTRACT(stop, start);
    elapsed = INSTR_TIME_GET_MILLISEC(stop);

    printf("snprintf time = %g ms total, %g ms per iteration\n",
           elapsed, elapsed / count);

    INSTR_TIME_SET_CURRENT(start);

    for (i = 0; i < count; i++)
    {
        pg_snprintf(buffer, sizeof(buffer),
                    "%d %d", 15, 16);
    }

    INSTR_TIME_SET_CURRENT(stop);
    INSTR_TIME_SUBTRACT(stop, start);
    elapsed2 = INSTR_TIME_GET_MILLISEC(stop);

    printf("pg_snprintf time = %g ms total, %g ms per iteration\n",
           elapsed2, elapsed2 / count);
    printf("ratio = %.3f\n", elapsed2 / elapsed);

    return 0;
}

pgsql-hackers by date:

Previous
From: Emre Hasegeli
Date:
Subject: Re: [PATCH] Improve geometric types
Next
From: Tomas Vondra
Date:
Subject: Re: [PATCH] Improve geometric types