On 5 August 2015 at 12:51, David Rowley <david.row...@2ndquadrant.com>
wrote:

> On 29 July 2015 at 03:25, Andres Freund <and...@anarazel.de> wrote:
>
>> On 2015-07-29 03:10:41 +1200, David Rowley wrote:
>> > 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.
>>
>> That looks good.
>>
>> > 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.
>>
>> Worthwhile to finish that bit and try ;)
>>
>> > 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.
>>
>> Yes, I agreee that your's is probably going to be fast enough.
>>
>> > Have you thought about what to do when HAVE_INT64_TIMESTAMP is not
>> defined?
>>
>> I don't think it's actually important. The only difference vs float
>> timestamps is that in the latter case we set fsecs to zero BC.
>>
>> Unless we want to slow down the common case it seems not unlikely that
>> we're going to end up with a separate slow path anyway. E.g. neither
>> your version nor mine handles 5 digit years (which is why I fell back to
>> the slow path in that case in my patch).
>>
>
> It occurred to me that handling the 5 digit year is quite a simple change
> to my code:
>
> We simply just need to check if there was any 'num' left after consuming
> the given space. If there's any left then just use pg_uint2str().
> This keeps things very fast for the likely most common case where the year
> is 4 digits long.
>
> I've not thought about negative years. The whole function should perhaps
> take signed ints instead of unsigned.
>
>
I've made a few changes to this to get the fractional seconds part working
as it should.

It also now works fine with 5 digit years.

It's still in the form of the test program, but it should be simple enough
to pull out what's required from that and put into Postgres.

I've also changed my version of AppendSeconds() so that it returns a
pointer to the new end of string. This should be useful as I see some
strlen() calls to get the new end of string. It'll easy to remove those now
which will further increase performance.

timestamp_out() is the proposed new version
timestamp_out_old() is master's version
timestamp_out_af() is your version

Performance is as follows:

With Clang
david@ubuntu:~/C$ clang timestamp_out.c -o timestamp_out -O2
david@ubuntu:~/C$ ./timestamp_out
timestamp_out() = 2015-07-29 02:24:33.034 in 0.313686
timestamp_out_old() = 2015-07-29 02:24:33.034 in 5.048472
timestamp_out_af() = 2015-07-29 02:24:33.034 in 0.198147

With gcc
david@ubuntu:~/C$ gcc timestamp_out.c -o timestamp_out -O2
david@ubuntu:~/C$ ./timestamp_out
timestamp_out() = 2015-07-29 02:24:33.034 in 0.405795
timestamp_out_old() = 2015-07-29 02:24:33.034 in 4.678918
timestamp_out_af() = 2015-07-29 02:24:33.034 in 0.270557

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>
#include <string.h>
#include <stdlib.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;
};

static char *pg_uint2str(char *str, unsigned int value);
static char *pg_uint2str_padding(char *str, unsigned int value, unsigned int padding);

static char *
AppendSeconds2(char *cp, int sec, unsigned int fsec, int precision, char fillzeros)
{
	
	if (fillzeros)
		cp = pg_uint2str_padding(cp, abs(sec), 2);
	else
		cp = pg_uint2str(cp, abs(sec));
	
	if (fsec != 0)
	{
		unsigned int value = fsec;
		char *end = &cp[precision + 1];
		int gotnonzero = 0;

		*cp++ = '.';
	
		/*
		 * Append the fractional seconds part. Note that we don't want any
		 * trailing zeros here, so since we're building the number in reverse
		 * we'll skip appending any zeros, unless we've seen a non-zero.
		 */
		while (precision--)
		{
			int		remainder;
			int		oldval = value;

			value /= 10;
			remainder = oldval - value * 10;
			
			/* check if we got a non-zero */
			if (remainder)
				gotnonzero = 1;
		
			if (gotnonzero)
				cp[precision] = '0' + remainder;
			else
				end = &cp[precision];
		}
		
		/*
		 * if we have a non-zero value then precision must have not been enough
		 * to print the number, we'd better have another go. There won't be any
		 * zero padding, so we can just use pg_uint2str()
		 */
		if (value > 0)
			return pg_uint2str(cp, fsec);

		*end = '\0';
		
		return end;
	}
	else
		return cp;
}


static char *
pg_uint2str_padding(char *str, unsigned int value, unsigned int padding)
{
	char	   *start = str;
	char	   *end = &str[padding];
	unsigned int num = value;
	//Assert(padding > 0);
	
	*end = '\0';
	
	while (padding--)
	{
		str[padding] = num % 10 + '0';
		num /= 10;
	}
	
	/*
	 * if value was too big for the specified padding then rebuild the whole
	 * number again without padding. Conveniently pg_uint2str() does exactly
	 * this.
	 */
	if (num > 0)
		return pg_uint2str(str, value);

	return end;
}

static 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. */
	end = str;
	*str-- = '\0';
	
	/* 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;
}

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

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 = AppendSeconds2(str, tm->tm_sec, fsec, MAX_TIMESTAMP_PRECISION, 1);
	
	return buffer;
}



/* 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)
{
	//printf("precision = %d\n", precision);
	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;
}



#define NLOOPS 10000000
//#define NLOOPS 1

int
main(void)
{
	char buffer[100];
	clock_t starttime, endtime;
	struct pg_tm tm;
	int i;
	int fractional_seconds = 34000;
	
	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 < NLOOPS; i++)
	{
		timestamp_out(buffer, &tm, fractional_seconds);
	}
	
	endtime = clock();
	
	printf("timestamp_out() = %s in %f\n", buffer, (double)(endtime - starttime) / CLOCKS_PER_SEC);

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

	starttime = clock();
	
	for(i = 0; i < NLOOPS; i++)
	{
		timestamp_out_af(buffer, &tm, fractional_seconds);
	}
	
	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