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