Make assorted performance improvements in snprintf.c.

In combination, these changes make our version of snprintf as fast
or faster than most platforms' native snprintf, except for cases
involving floating-point conversion (which we still delegate to
the native sprintf).  The speed penalty for a float conversion
is down to around 10% though, much better than before.

Notable changes:

* Rather than always parsing the format twice to see if it contains
instances of %n$, do the extra scan only if we actually find a $.
This obviously wins for non-localized formats, and even when there
is use of %n$, we can avoid scanning text before the first % twice.

* Use strchrnul() if available to find the next %, and emit the
literal text between % escapes as strings rather than char-by-char.

* Create a bespoke function (dopr_outchmulti) for the common case
of emitting N copies of the same character, in place of writing
loops around dopr_outch.

* Simplify construction of the format string for invocations of sprintf
for floats.

* Const-ify some internal functions, and avoid unnecessary use of
pass-by-reference arguments.

Patch by me, reviewed by Andres Freund

Discussion: https://postgr.es/m/11787.1534530779@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2018-10-03 10:18:15 -04:00
parent 9bc9f72b28
commit abd9ca377d
5 changed files with 432 additions and 273 deletions

2
configure vendored
View File

@ -15100,7 +15100,7 @@ fi
LIBS_including_readline="$LIBS" LIBS_including_readline="$LIBS"
LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
for ac_func in cbrt clock_gettime fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate ppoll pstat pthread_is_threaded_np readlink setproctitle setproctitle_fast setsid shm_open symlink sync_file_range utime utimes wcstombs_l for ac_func in cbrt clock_gettime fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate ppoll pstat pthread_is_threaded_np readlink setproctitle setproctitle_fast setsid shm_open strchrnul symlink sync_file_range utime utimes wcstombs_l
do : do :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"

View File

@ -1571,7 +1571,7 @@ PGAC_FUNC_WCSTOMBS_L
LIBS_including_readline="$LIBS" LIBS_including_readline="$LIBS"
LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'` LIBS=`echo "$LIBS" | sed -e 's/-ledit//g' -e 's/-lreadline//g'`
AC_CHECK_FUNCS([cbrt clock_gettime fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate ppoll pstat pthread_is_threaded_np readlink setproctitle setproctitle_fast setsid shm_open symlink sync_file_range utime utimes wcstombs_l]) AC_CHECK_FUNCS([cbrt clock_gettime fdatasync getifaddrs getpeerucred getrlimit mbstowcs_l memmove poll posix_fallocate ppoll pstat pthread_is_threaded_np readlink setproctitle setproctitle_fast setsid shm_open strchrnul symlink sync_file_range utime utimes wcstombs_l])
AC_REPLACE_FUNCS(fseeko) AC_REPLACE_FUNCS(fseeko)
case $host_os in case $host_os in

View File

@ -523,6 +523,9 @@
/* Define to 1 if you have the <stdlib.h> header file. */ /* Define to 1 if you have the <stdlib.h> header file. */
#undef HAVE_STDLIB_H #undef HAVE_STDLIB_H
/* Define to 1 if you have the `strchrnul' function. */
#undef HAVE_STRCHRNUL
/* Define to 1 if you have the `strerror_r' function. */ /* Define to 1 if you have the `strerror_r' function. */
#undef HAVE_STRERROR_R #undef HAVE_STRERROR_R

View File

@ -394,6 +394,9 @@
/* Define to 1 if you have the <stdlib.h> header file. */ /* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1 #define HAVE_STDLIB_H 1
/* Define to 1 if you have the `strchrnul' function. */
/* #undef HAVE_STRCHRNUL */
/* Define to 1 if you have the `strerror_r' function. */ /* Define to 1 if you have the `strerror_r' function. */
/* #undef HAVE_STRERROR_R */ /* #undef HAVE_STRERROR_R */

View File

@ -314,7 +314,9 @@ flushbuffer(PrintfTarget *target)
} }
static void fmtstr(char *value, int leftjust, int minlen, int maxwidth, 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); int pointflag, PrintfTarget *target);
static void fmtptr(void *value, PrintfTarget *target); static void fmtptr(void *value, PrintfTarget *target);
static void fmtint(int64 value, char type, int forcesign, static void fmtint(int64 value, char type, int forcesign,
@ -326,11 +328,43 @@ static void fmtfloat(double value, char type, int forcesign,
PrintfTarget *target); PrintfTarget *target);
static void dostr(const char *str, int slen, PrintfTarget *target); static void dostr(const char *str, int slen, PrintfTarget *target);
static void dopr_outch(int c, 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 adjust_sign(int is_negative, int forcesign, int *signvalue);
static void adjust_padlen(int minlen, int vallen, int leftjust, int *padlen); static int compute_padlen(int minlen, int vallen, int leftjust);
static void leading_pad(int zpad, int *signvalue, int *padlen, static void leading_pad(int zpad, int signvalue, int *padlen,
PrintfTarget *target); PrintfTarget *target);
static void trailing_pad(int *padlen, PrintfTarget *target); static void trailing_pad(int padlen, PrintfTarget *target);
/*
* If strchrnul exists (it's a glibc-ism), it's a good bit faster than the
* equivalent manual loop. If it doesn't exist, provide a replacement.
*
* Note: glibc declares this as returning "char *", but that would require
* casting away const internally, so we don't follow that detail.
*/
#ifndef HAVE_STRCHRNUL
static inline const char *
strchrnul(const char *s, int c)
{
while (*s != '\0' && *s != c)
s++;
return s;
}
#else
/*
* glibc's <string.h> declares strchrnul only if _GNU_SOURCE is defined.
* While we typically use that on glibc platforms, configure will set
* HAVE_STRCHRNUL whether it's used or not. Fill in the missing declaration
* so that this file will compile cleanly with or without _GNU_SOURCE.
*/
#ifndef _GNU_SOURCE
extern char *strchrnul(const char *s, int c);
#endif
#endif /* HAVE_STRCHRNUL */
/* /*
@ -340,10 +374,9 @@ static void
dopr(PrintfTarget *target, const char *format, va_list args) dopr(PrintfTarget *target, const char *format, va_list args)
{ {
int save_errno = errno; int save_errno = errno;
const char *format_start = format; const char *first_pct = NULL;
int ch; int ch;
bool have_dollar; bool have_dollar;
bool have_non_dollar;
bool have_star; bool have_star;
bool afterstar; bool afterstar;
int accum; int accum;
@ -355,226 +388,49 @@ dopr(PrintfTarget *target, const char *format, va_list args)
int precision; int precision;
int zpad; int zpad;
int forcesign; int forcesign;
int last_dollar;
int fmtpos; int fmtpos;
int cvalue; int cvalue;
int64 numvalue; int64 numvalue;
double fvalue; double fvalue;
char *strvalue; char *strvalue;
int i;
PrintfArgType argtypes[PG_NL_ARGMAX + 1];
PrintfArgValue argvalues[PG_NL_ARGMAX + 1]; PrintfArgValue argvalues[PG_NL_ARGMAX + 1];
/* /*
* Parse the format string to determine whether there are %n$ format * Initially, we suppose the format string does not use %n$. The first
* specs, and identify the types and order of the format parameters. * 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 = have_non_dollar = false; have_dollar = false;
last_dollar = 0;
MemSet(argtypes, 0, sizeof(argtypes));
while ((ch = *format++) != '\0') while (*format != '\0')
{ {
if (ch != '%') /* Locate next conversion specifier */
continue; if (*format != '%')
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 '-': /* Scan to next '%' or end of string */
case '+': const char *next_pct = strchrnul(format + 1, '%');
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 > PG_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) /* Dump literal data we just scanned over */
atype = ATYPE_LONGLONG; dostr(format, next_pct - format, target);
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 'm':
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) if (target->failed)
break; break;
if (ch != '%') if (*next_pct == '\0')
{ break;
dopr_outch(ch, target); format = next_pct;
continue;
} }
/*
* 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; fieldwidth = precision = zpad = leftjust = forcesign = 0;
longflag = longlongflag = pointflag = 0; longflag = longlongflag = pointflag = 0;
fmtpos = accum = 0; fmtpos = accum = 0;
@ -618,7 +474,11 @@ nextch2:
case '*': case '*':
if (have_dollar) if (have_dollar)
{ {
/* process value after reading n$ */ /*
* 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; afterstar = true;
} }
else else
@ -649,6 +509,14 @@ nextch2:
accum = 0; accum = 0;
goto nextch2; goto nextch2;
case '$': 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) if (afterstar)
{ {
/* fetch and process star value */ /* fetch and process star value */
@ -836,6 +704,10 @@ nextch2:
dopr_outch('%', target); dopr_outch('%', target);
break; break;
} }
/* Check for failure after each conversion spec */
if (target->failed)
break;
} }
return; return;
@ -845,8 +717,236 @@ bad_format:
target->failed = true; 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[PG_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 != '%')
{
/* Unlike dopr, we can just quit if there's no more specifiers */
format = strchr(format + 1, '%');
if (format == NULL)
break;
}
/* 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 > PG_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 'm':
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 static void
fmtstr(char *value, int leftjust, int minlen, int maxwidth, fmtstr(const char *value, int leftjust, int minlen, int maxwidth,
int pointflag, PrintfTarget *target) int pointflag, PrintfTarget *target)
{ {
int padlen, int padlen,
@ -861,17 +961,17 @@ fmtstr(char *value, int leftjust, int minlen, int maxwidth,
else else
vallen = strlen(value); vallen = strlen(value);
adjust_padlen(minlen, vallen, leftjust, &padlen); padlen = compute_padlen(minlen, vallen, leftjust);
while (padlen > 0) if (padlen > 0)
{ {
dopr_outch(' ', target); dopr_outchmulti(' ', padlen, target);
--padlen; padlen = 0;
} }
dostr(value, vallen, target); dostr(value, vallen, target);
trailing_pad(&padlen, target); trailing_pad(padlen, target);
} }
static void static void
@ -899,7 +999,7 @@ fmtint(int64 value, char type, int forcesign, int leftjust,
int signvalue = 0; int signvalue = 0;
char convert[64]; char convert[64];
int vallen = 0; int vallen = 0;
int padlen = 0; /* amount to pad */ int padlen; /* amount to pad */
int zeropad; /* extra leading zeroes */ int zeropad; /* extra leading zeroes */
switch (type) switch (type)
@ -947,42 +1047,41 @@ fmtint(int64 value, char type, int forcesign, int leftjust,
do do
{ {
convert[vallen++] = cvt[uvalue % base]; convert[sizeof(convert) - (++vallen)] = cvt[uvalue % base];
uvalue = uvalue / base; uvalue = uvalue / base;
} while (uvalue); } while (uvalue);
} }
zeropad = Max(0, precision - vallen); zeropad = Max(0, precision - vallen);
adjust_padlen(minlen, vallen + zeropad, leftjust, &padlen); padlen = compute_padlen(minlen, vallen + zeropad, leftjust);
leading_pad(zpad, &signvalue, &padlen, target); leading_pad(zpad, signvalue, &padlen, target);
while (zeropad-- > 0) if (zeropad > 0)
dopr_outch('0', target); dopr_outchmulti('0', zeropad, target);
while (vallen > 0) dostr(convert + sizeof(convert) - vallen, vallen, target);
dopr_outch(convert[--vallen], target);
trailing_pad(&padlen, target); trailing_pad(padlen, target);
} }
static void static void
fmtchar(int value, int leftjust, int minlen, PrintfTarget *target) fmtchar(int value, int leftjust, int minlen, PrintfTarget *target)
{ {
int padlen = 0; /* amount to pad */ int padlen; /* amount to pad */
adjust_padlen(minlen, 1, leftjust, &padlen); padlen = compute_padlen(minlen, 1, leftjust);
while (padlen > 0) if (padlen > 0)
{ {
dopr_outch(' ', target); dopr_outchmulti(' ', padlen, target);
--padlen; padlen = 0;
} }
dopr_outch(value, target); dopr_outch(value, target);
trailing_pad(&padlen, target); trailing_pad(padlen, target);
} }
static void static void
@ -993,10 +1092,14 @@ fmtfloat(double value, char type, int forcesign, int leftjust,
int signvalue = 0; int signvalue = 0;
int prec; int prec;
int vallen; int vallen;
char fmt[32]; char fmt[8];
char convert[1024]; char convert[1024];
int zeropadlen = 0; /* amount to pad with zeroes */ int zeropadlen = 0; /* amount to pad with zeroes */
int padlen = 0; /* amount to pad with spaces */ int padlen; /* amount to pad with spaces */
/* Handle sign (NaNs have no sign) */
if (!isnan(value) && adjust_sign((value < 0), forcesign, &signvalue))
value = -value;
/* /*
* We rely on the regular C library's sprintf to do the basic conversion, * We rely on the regular C library's sprintf to do the basic conversion,
@ -1018,17 +1121,21 @@ fmtfloat(double value, char type, int forcesign, int leftjust,
if (pointflag) if (pointflag)
{ {
if (sprintf(fmt, "%%.%d%c", prec, type) < 0)
goto fail;
zeropadlen = precision - prec; zeropadlen = precision - prec;
fmt[0] = '%';
fmt[1] = '.';
fmt[2] = '*';
fmt[3] = type;
fmt[4] = '\0';
vallen = sprintf(convert, fmt, prec, value);
} }
else if (sprintf(fmt, "%%%c", type) < 0) else
goto fail; {
fmt[0] = '%';
if (!isnan(value) && adjust_sign((value < 0), forcesign, &signvalue)) fmt[1] = type;
value = -value; fmt[2] = '\0';
vallen = sprintf(convert, fmt, value); vallen = sprintf(convert, fmt, value);
}
if (vallen < 0) if (vallen < 0)
goto fail; goto fail;
@ -1036,9 +1143,9 @@ fmtfloat(double value, char type, int forcesign, int leftjust,
if (zeropadlen > 0 && !isdigit((unsigned char) convert[vallen - 1])) if (zeropadlen > 0 && !isdigit((unsigned char) convert[vallen - 1]))
zeropadlen = 0; zeropadlen = 0;
adjust_padlen(minlen, vallen + zeropadlen, leftjust, &padlen); padlen = compute_padlen(minlen, vallen + zeropadlen, leftjust);
leading_pad(zpad, &signvalue, &padlen, target); leading_pad(zpad, signvalue, &padlen, target);
if (zeropadlen > 0) if (zeropadlen > 0)
{ {
@ -1049,18 +1156,18 @@ fmtfloat(double value, char type, int forcesign, int leftjust,
epos = strrchr(convert, 'E'); epos = strrchr(convert, 'E');
if (epos) if (epos)
{ {
/* pad after exponent */ /* pad before exponent */
dostr(convert, epos - convert, target); dostr(convert, epos - convert, target);
while (zeropadlen-- > 0) if (zeropadlen > 0)
dopr_outch('0', target); dopr_outchmulti('0', zeropadlen, target);
dostr(epos, vallen - (epos - convert), target); dostr(epos, vallen - (epos - convert), target);
} }
else else
{ {
/* no exponent, pad after the digits */ /* no exponent, pad after the digits */
dostr(convert, vallen, target); dostr(convert, vallen, target);
while (zeropadlen-- > 0) if (zeropadlen > 0)
dopr_outch('0', target); dopr_outchmulti('0', zeropadlen, target);
} }
} }
else else
@ -1069,7 +1176,7 @@ fmtfloat(double value, char type, int forcesign, int leftjust,
dostr(convert, vallen, target); dostr(convert, vallen, target);
} }
trailing_pad(&padlen, target); trailing_pad(padlen, target);
return; return;
fail: fail:
@ -1079,6 +1186,13 @@ fail:
static void static void
dostr(const char *str, int slen, PrintfTarget *target) 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) while (slen > 0)
{ {
int avail; int avail;
@ -1122,6 +1236,42 @@ dopr_outch(int c, PrintfTarget *target)
*(target->bufptr++) = c; *(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 static int
adjust_sign(int is_negative, int forcesign, int *signvalue) adjust_sign(int is_negative, int forcesign, int *signvalue)
@ -1137,42 +1287,48 @@ adjust_sign(int is_negative, int forcesign, int *signvalue)
} }
static void static int
adjust_padlen(int minlen, int vallen, int leftjust, int *padlen) compute_padlen(int minlen, int vallen, int leftjust)
{ {
*padlen = minlen - vallen; int padlen;
if (*padlen < 0)
*padlen = 0; padlen = minlen - vallen;
if (padlen < 0)
padlen = 0;
if (leftjust) if (leftjust)
*padlen = -(*padlen); padlen = -padlen;
return padlen;
} }
static void static void
leading_pad(int zpad, int *signvalue, int *padlen, PrintfTarget *target) leading_pad(int zpad, int signvalue, int *padlen, PrintfTarget *target)
{ {
int maxpad;
if (*padlen > 0 && zpad) if (*padlen > 0 && zpad)
{ {
if (*signvalue) if (signvalue)
{ {
dopr_outch(*signvalue, target); dopr_outch(signvalue, target);
--(*padlen); --(*padlen);
*signvalue = 0; signvalue = 0;
} }
while (*padlen > 0) if (*padlen > 0)
{ {
dopr_outch(zpad, target); dopr_outchmulti(zpad, *padlen, target);
--(*padlen); *padlen = 0;
} }
} }
while (*padlen > (*signvalue != 0)) maxpad = (signvalue != 0);
if (*padlen > maxpad)
{ {
dopr_outch(' ', target); dopr_outchmulti(' ', *padlen - maxpad, target);
--(*padlen); *padlen = maxpad;
} }
if (*signvalue) if (signvalue)
{ {
dopr_outch(*signvalue, target); dopr_outch(signvalue, target);
if (*padlen > 0) if (*padlen > 0)
--(*padlen); --(*padlen);
else if (*padlen < 0) else if (*padlen < 0)
@ -1182,11 +1338,8 @@ leading_pad(int zpad, int *signvalue, int *padlen, PrintfTarget *target)
static void static void
trailing_pad(int *padlen, PrintfTarget *target) trailing_pad(int padlen, PrintfTarget *target)
{ {
while (*padlen < 0) if (padlen < 0)
{ dopr_outchmulti(' ', -padlen, target);
dopr_outch(' ', target);
++(*padlen);
}
} }