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