strftime: format small positive integers ourselves.

A decent chunk of the logcat profile is spent formatting the timestamps
for each line, and most of that time was going to snprintf(3). We should
find all the places that could benefit from a lighter-weight "format an
integer" and share something between them, but this is easy for now.

Before:

-----------------------------------------------------------
Benchmark                 Time             CPU   Iterations
-----------------------------------------------------------
BM_time_strftime        781 ns          775 ns       893102

After:

-----------------------------------------------------------
Benchmark                 Time             CPU   Iterations
-----------------------------------------------------------
BM_time_strftime        149 ns          147 ns      4750782

Much of the remaining time is in tzset() which seems unfortunate.

Test: treehugger
Change-Id: Ie0f7ee462ff1b1abea6f87d4a9a996d768e51056
This commit is contained in:
Elliott Hughes 2021-11-18 17:57:38 -08:00
parent a9ebf6f422
commit e4d5efe231
2 changed files with 81 additions and 47 deletions

View file

@ -187,3 +187,13 @@ void BM_time_localtime_r(benchmark::State& state) {
}
}
BIONIC_BENCHMARK(BM_time_localtime_r);
void BM_time_strftime(benchmark::State& state) {
char buf[128];
time_t t = 0;
struct tm* tm = gmtime(&t);
while (state.KeepRunning()) {
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm);
}
}
BIONIC_BENCHMARK(BM_time_strftime);

View file

@ -252,8 +252,8 @@ label:
pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
continue;
case 'd':
pt = _conv(t->tm_mday, getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
continue;
pt = _conv(t->tm_mday, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
continue;
case 'E':
case 'O':
/*
@ -274,22 +274,21 @@ label:
modifier = *format;
goto label;
case 'e':
pt = _conv(t->tm_mday, getformat(modifier, "%2d", "%2d", "%d", "%02d"), pt, ptlim);
continue;
pt = _conv(t->tm_mday, getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
continue;
case 'F':
pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
continue;
case 'H':
pt = _conv(t->tm_hour, getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
continue;
pt = _conv(t->tm_hour, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
continue;
case 'I':
pt = _conv((t->tm_hour % 12) ?
(t->tm_hour % 12) : 12,
getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
continue;
pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
continue;
case 'j':
pt = _conv(t->tm_yday + 1, getformat(modifier, "%03d", "%3d", "%d", "%03d"), pt, ptlim);
continue;
pt = _conv(t->tm_yday + 1, getformat(modifier, "03", " 3", " ", "03"), pt, ptlim);
continue;
case 'k':
/*
** This used to be...
@ -301,7 +300,7 @@ label:
** "%l" have been swapped.
** (ado, 1993-05-24)
*/
pt = _conv(t->tm_hour, getformat(modifier, "%2d", "%2d", "%d", "%02d"), pt, ptlim);
pt = _conv(t->tm_hour, getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
continue;
#ifdef KITCHEN_SINK
case 'K':
@ -321,16 +320,15 @@ label:
** "%l" have been swapped.
** (ado, 1993-05-24)
*/
pt = _conv((t->tm_hour % 12) ?
(t->tm_hour % 12) : 12,
getformat(modifier, "%2d", "%2d", "%d", "%02d"), pt, ptlim);
pt = _conv((t->tm_hour % 12) ? (t->tm_hour % 12) : 12,
getformat(modifier, " 2", " 2", " ", "02"), pt, ptlim);
continue;
case 'M':
pt = _conv(t->tm_min, getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
continue;
pt = _conv(t->tm_min, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
continue;
case 'm':
pt = _conv(t->tm_mon + 1, getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
continue;
pt = _conv(t->tm_mon + 1, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
continue;
case 'n':
pt = _add("\n", pt, ptlim, modifier);
continue;
@ -348,13 +346,12 @@ label:
pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
continue;
case 'S':
pt = _conv(t->tm_sec, getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
continue;
pt = _conv(t->tm_sec, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
continue;
case 's':
{
struct tm tm;
char buf[INT_STRLEN_MAXIMUM(
time64_t) + 1];
char buf[INT_STRLEN_MAXIMUM(time64_t) + 1] __attribute__((__uninitialized__));
time64_t mkt;
tm = *t;
@ -374,10 +371,9 @@ label:
pt = _add("\t", pt, ptlim, modifier);
continue;
case 'U':
pt = _conv((t->tm_yday + DAYSPERWEEK -
t->tm_wday) / DAYSPERWEEK,
getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
continue;
pt = _conv((t->tm_yday + DAYSPERWEEK - t->tm_wday) / DAYSPERWEEK,
getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
continue;
case 'u':
/*
** From Arnold Robbins' strftime version 3.0:
@ -385,9 +381,7 @@ label:
** [1 (Monday) - 7]"
** (ado, 1993-05-24)
*/
pt = _conv((t->tm_wday == 0) ?
DAYSPERWEEK : t->tm_wday,
"%d", pt, ptlim);
pt = _conv((t->tm_wday == 0) ? DAYSPERWEEK : t->tm_wday, " ", pt, ptlim);
continue;
case 'V': /* ISO 8601 week number */
case 'G': /* ISO 8601 year (four digits) */
@ -467,8 +461,7 @@ label:
w = 53;
#endif /* defined XPG4_1994_04_09 */
if (*format == 'V')
pt = _conv(w, getformat(modifier, "%02d", "%2d", "%d", "%02d"),
pt, ptlim);
pt = _conv(w, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
else if (*format == 'g') {
*warnp = IN_ALL;
pt = _yconv(year, base,
@ -488,15 +481,14 @@ label:
pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
continue;
case 'W':
pt = _conv((t->tm_yday + DAYSPERWEEK -
(t->tm_wday ?
(t->tm_wday - 1) :
(DAYSPERWEEK - 1))) / DAYSPERWEEK,
getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
continue;
pt = _conv(
(t->tm_yday + DAYSPERWEEK - (t->tm_wday ? (t->tm_wday - 1) : (DAYSPERWEEK - 1))) /
DAYSPERWEEK,
getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
continue;
case 'w':
pt = _conv(t->tm_wday, "%d", pt, ptlim);
continue;
pt = _conv(t->tm_wday, " ", pt, ptlim);
continue;
case 'X':
pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
continue;
@ -602,7 +594,7 @@ label:
diff /= SECSPERMIN;
diff = (diff / MINSPERHOUR) * 100 +
(diff % MINSPERHOUR);
pt = _conv(diff, getformat(modifier, "%04d", "%4d", "%d", "%04d"), pt, ptlim);
pt = _conv(diff, getformat(modifier, "04", " 4", " ", "04"), pt, ptlim);
}
continue;
case '+':
@ -629,10 +621,40 @@ label:
static char *
_conv(int n, const char *format, char *pt, const char *ptlim)
{
char buf[INT_STRLEN_MAXIMUM(int) + 1];
// The original implementation used snprintf(3) here, but rolling our own is
// about 5x faster. Seems like a good trade-off for so little code, especially
// for users like logcat that have a habit of formatting 10k times all at
// once...
snprintf(buf, sizeof(buf), format, n);
return _add(buf, pt, ptlim, 0);
// Format is '0' or ' ' for the fill character, followed by a single-digit
// width or ' ' for "whatever".
// %d -> " "
// %2d -> " 2"
// %02d -> "02"
char fill = format[0];
int width = format[1] == ' ' ? 0 : format[1] - '0';
char buf[32] __attribute__((__uninitialized__));
// Terminate first, so we can walk backwards from the least-significant digit
// without having to later reverse the result.
char* p = &buf[31];
*--p = '\0';
char* end = p;
// Output digits backwards, from least-significant to most.
while (n >= 10) {
*--p = '0' + (n % 10);
n /= 10;
}
*--p = '0' + n;
// Fill if more digits are required by the format.
while ((end - p) < width) {
*--p = fill;
}
return _add(p, pt, ptlim, 0);
}
static char *
@ -704,9 +726,11 @@ _yconv(int a, int b, bool convert_top, bool convert_yy,
if (convert_top) {
if (lead == 0 && trail < 0)
pt = _add("-0", pt, ptlim, modifier);
else pt = _conv(lead, getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
else
pt = _conv(lead, getformat(modifier, "02", " 2", " ", "02"), pt, ptlim);
}
if (convert_yy)
pt = _conv(((trail < 0) ? -trail : trail), getformat(modifier, "%02d", "%2d", "%d", "%02d"), pt, ptlim);
pt = _conv(((trail < 0) ? -trail : trail), getformat(modifier, "02", " 2", " ", "02"), pt,
ptlim);
return pt;
}