On 28 July 2015 at 19:10, Andres Freund <and...@anarazel.de> wrote:

> On 2015-07-28 10:59:15 +1200, David Rowley wrote:
> > It won't be quite as fast as what you've written, but I think it will be
> > much neater and more likely to be used in other places if we invent a
> > function like pg_ltoa() which returns a pointer to the new end of string.
> >
> > Also if we're specifying padding with zeros then we can skip the reverse
> > part that's in pg_ltoa(), (normally needed since the numeric string is
> > build in reverse)
> >
> > The code could then be written as:
> >
> > str = pg_int2str_pad(str, year, 4);
> > *str++ = '-';
> > str = pg_int2str_pad(str, tm->tm_mon, 2);
> > *str++ = '-';
> > str = pg_int2str_pad(str, tm->tm_mday, 2);
> >
> > etc
> >
> > I've used this method before and found it to be about 10 times faster
> than
> > snprintf(), but I was reversing the string, so quite likely it be more
> than
> > 10 times.
>
> Yes, that might be worthwhile to try. Certainly would look less
> ugly. Willing to give it a try?
>
>
I had a quick try at this and ended up just writing a small test program to
see what's faster.

Please excuse the mess of the file, I just hacked it together as quickly as
I could with the sole intention of just to get an idea of which is faster
and by how much.

For me the output is as follows:

timestamp_out() = 2015-07-29 02:24:33.34 in 3.506000
timestamp_out_old() = 2015-07-29 02:24:33.034 in 64.518000
timestamp_out_af() = 2015-07-29 02:24:33.034 in 2.981000

timestamp_out_old is master's version, the timestamp_out_af() is yours, and
timestamp_out() is my one. times are in seconds to perform 100 million
calls.

So it appears your version is a bit faster than mine, but we're both about
20 times faster than the current one.
Also mine needs fixed up as the fractional part is not padded the same as
yours, but I doubt that'll affect the performance by much.

My view: It's probably not worth going quite as far as you've gone for a
handful of nanoseconds per call, but perhaps something along the lines of
mine can be fixed up.

Have you thought about what to do when HAVE_INT64_TIMESTAMP is not defined?

Regards

David Rowley

--
 David Rowley                   http://www.2ndQuadrant.com/
<http://www.2ndquadrant.com/>
 PostgreSQL Development, 24x7 Support, Training & Services
#include <stdio.h>
#include <time.h>

struct pg_tm
{
	int			tm_sec;
	int			tm_min;
	int			tm_hour;
	int			tm_mday;
	int			tm_mon;			/* origin 0, not 1 */
	int			tm_year;		/* relative to 1900 */
	int			tm_wday;
	int			tm_yday;
	int			tm_isdst;
	long int	tm_gmtoff;
	const char *tm_zone;
};


char *
pg_uint2str_padding(char *str, unsigned int value, unsigned int padding)
{
	char	   *start = str;
	char	   *end = &str[padding];
	//Assert(padding > 0);
	
	*end = '\0';
	
	while (padding--)
	{
		str[padding] = value % 10 + '0';
		value /= 10;
	}
	
	return end;
}

char *
pg_uint2str(char *str, unsigned int value)
{
	char *start = str;
	char *end;
	/* Compute the result string backwards. */
	do
	{
		int		remainder;
		int		oldval = value;

		value /= 10;
		remainder = oldval - value * 10;
		*str++ = '0' + remainder;
	} while (value != 0);


	/* Add trailing NUL byte, and back up 'str' to the last character. */
	*str-- = '\0';
	end = str;
	
	/* Reverse string. */
	while (start < str)
	{
		char		swap = *start;

		*start++ = *str;
		*str-- = swap;
	}
	return end;
}


char *
timestamp_out_af(char *buffer, struct pg_tm *tm, unsigned int fsec)
{
	char *str = buffer;

	*str++ = (tm->tm_year / 1000) + '0';
	*str++ = (tm->tm_year / 100) % 10 + '0';
	*str++ = (tm->tm_year / 10) % 10 + '0';
	*str++ = tm->tm_year % 10 + '0';
	*str++ = '-';
	*str++ = (tm->tm_mon / 10) + '0';
	*str++ = tm->tm_mon % 10 + '0';
	*str++ = '-';
	*str++ = (tm->tm_mday / 10) + '0';
	*str++ = tm->tm_mday % 10 + '0';
	*str++ = ' ';
	*str++ = (tm->tm_hour / 10) + '0';
	*str++ = tm->tm_hour % 10 + '0';
	*str++ = ':';
	*str++ = (tm->tm_min / 10) + '0';
	*str++ = tm->tm_min % 10 + '0';
	*str++ = ':';
	*str++ = (tm->tm_sec / 10) + '0';
	*str++ = tm->tm_sec % 10 + '0';

	/*
	 * Yes, this is darned ugly and would look nicer in a loop,
	 * but some versions of gcc can't convert the divisions into
	 * more efficient instructions unless manually unrolled.
	 */
	if (fsec != 0)
	{
		int fseca = abs(fsec);

		*str++ = '.';

		if (fseca % 1000000 != 0)
		{
			*str++ = (fseca / 100000) + '0';

			if (fseca % 100000 != 0)
			{
				*str++ = ((fseca / 10000) % 10) + '0';

				if (fseca % 10000 != 0)
				{
					*str++ = ((fseca / 1000) % 10) + '0';

					if (fseca % 1000 != 0)
					{
						*str++ = ((fseca / 100) % 10) + '0';

						if (fseca % 100 != 0)
						{
							*str++ = ((fseca / 10) % 10) + '0';

							if (fseca % 10 != 0)
							{
								*str++ = (fseca % 10) + '0';
							}
						}
					}
				}
			}
		}
	}
	
	return buffer;
}

char *
timestamp_out(char *buffer, struct pg_tm *tm, unsigned int fsec)
{
	char *str = buffer;
	
	str = pg_uint2str_padding(str, tm->tm_year, 4);
	*str++ = '-';
	str = pg_uint2str_padding(str, tm->tm_mon, 2);
	*str++ = '-';
	str = pg_uint2str_padding(str, tm->tm_mday, 2);
	*str++ = ' ';
	str = pg_uint2str_padding(str, tm->tm_hour, 2);
	*str++ = ':';
	str = pg_uint2str_padding(str, tm->tm_min, 2);
	*str++ = ':';
	str = pg_uint2str_padding(str, tm->tm_sec, 2);
	
	if (fsec != 0)
	{
		while (fsec % 10 == 0 && fsec > 0)
			fsec /= 10;
			
		*str++ = '.';
		str = pg_uint2str(str, fsec);
	}
	
	return buffer;
}

#define MAX_TIMESTAMP_PRECISION 6
#define HAVE_INT64_TIMESTAMP
#define Abs(x)			((x) >= 0 ? (x) : -(x))

/* TrimTrailingZeros()
 * ... resulting from printing numbers with full precision.
 *
 * Before Postgres 8.4, this always left at least 2 fractional digits,
 * but conversations on the lists suggest this isn't desired
 * since showing '0.10' is misleading with values of precision(1).
 */
static void
TrimTrailingZeros(char *str)
{
	int			len = strlen(str);

	while (len > 1 && *(str + len - 1) == '0' && *(str + len - 2) != '.')
	{
		len--;
		*(str + len) = '\0';
	}
}

/*
 * Append sections and fractional seconds (if any) at *cp.
 * precision is the max number of fraction digits, fillzeros says to
 * pad to two integral-seconds digits.
 * Note that any sign is stripped from the input seconds values.
 */
static void
AppendSeconds(char *cp, int sec, unsigned int fsec, int precision, char fillzeros)
{
	if (fsec == 0)
	{
		if (fillzeros)
			sprintf(cp, "%02d", abs(sec));
		else
			sprintf(cp, "%d", abs(sec));
	}
	else
	{
#ifdef HAVE_INT64_TIMESTAMP
		if (fillzeros)
			sprintf(cp, "%02d.%0*d", abs(sec), precision, (int) Abs(fsec));
		else
			sprintf(cp, "%d.%0*d", abs(sec), precision, (int) Abs(fsec));
#else
		if (fillzeros)
			sprintf(cp, "%0*.*f", precision + 3, precision, fabs(sec + fsec));
		else
			sprintf(cp, "%.*f", precision, fabs(sec + fsec));
#endif
		TrimTrailingZeros(cp);
	}
}


/* Variant of above that's specialized to timestamp case */
static void
AppendTimestampSeconds(char *cp, struct pg_tm * tm, unsigned int fsec)
{
	/*
	 * In float mode, don't print fractional seconds before 1 AD, since it's
	 * unlikely there's any precision left ...
	 */
#ifndef HAVE_INT64_TIMESTAMP
	if (tm->tm_year <= 0)
		fsec = 0;
#endif
	AppendSeconds(cp, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, 1);
}

char *
timestamp_out_old(char *buffer, struct pg_tm *tm, unsigned int fsec)
{
	sprintf(buffer, "%04d-%02d-%02d %02d:%02d:",
			(tm->tm_year > 0) ? tm->tm_year : -(tm->tm_year - 1),
			tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min);
	AppendTimestampSeconds(buffer + strlen(buffer), tm, fsec);
	return buffer;
}


int
main(void)
{
	char buffer[100];
	clock_t starttime, endtime;
	struct pg_tm tm;
	int i;
	
	tm.tm_year = 2015;
	tm.tm_mon = 7;
	tm.tm_mday = 29;
	tm.tm_hour = 2;
	tm.tm_min = 24;
	tm.tm_sec = 33;
	
	starttime = clock();
	
	for(i = 0; i < 100000000; i++)
	{
		timestamp_out(buffer, &tm, 34000);
	}
	
	endtime = clock();
	
	printf("timestamp_out() = %s in %f\n", buffer, (double)(endtime - starttime) / CLOCKS_PER_SEC);

	starttime = clock();
	
	for(i = 0; i < 100000000; i++)
	{
		timestamp_out_old(buffer, &tm, 34000);
	}
	
	endtime = clock();
	
	printf("timestamp_out_old() = %s in %f\n", buffer, (double)(endtime - starttime) / CLOCKS_PER_SEC);

	starttime = clock();
	
	for(i = 0; i < 100000000; i++)
	{
		timestamp_out_af(buffer, &tm, 34000);
	}
	
	endtime = clock();
	
	printf("timestamp_out_af() = %s in %f\n", buffer, (double)(endtime - starttime) / CLOCKS_PER_SEC);
	
}
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to