Skip to content

Commit bde089f

Browse files
Abseil Teamcopybara-github
Abseil Team
authored andcommitted
Optimize absl::Duration division and modulo: Allow the compiler to inline time_internal::IDivDuration, by splitting the slow path to a separate function.
With that change, the compiler can inline the fast path. This is specially important in the context of `Duration::operator%=`, because it allows proving that the return value is unused, therefore avoiding expensive multiplies and divides (e.g. `*q = num_hi / den_hi;`). ``` name old cpu/op new cpu/op delta BM_Duration_Modulo 23.1ns ± 0% 22.5ns ± 0% -2.42% (p=0.000 n=20+16) BM_Duration_Modulo_FastPath 7.05ns ± 0% 4.85ns ± 0% -31.17% (p=0.000 n=20+20) name old time/op new time/op delta BM_Duration_Modulo 23.1ns ± 0% 22.6ns ± 0% -2.43% (p=0.000 n=20+16) BM_Duration_Modulo_FastPath 7.06ns ± 0% 4.86ns ± 0% -31.18% (p=0.000 n=20+20) name old INSTRUCTIONS/op new INSTRUCTIONS/op delta BM_Duration_Modulo 188 ± 0% 178 ± 0% -5.32% (p=0.000 n=20+20) BM_Duration_Modulo_FastPath 84.0 ± 0% 62.0 ± 0% -26.19% (p=0.000 n=20+20) name old CYCLES/op new CYCLES/op delta BM_Duration_Modulo 73.8 ± 0% 72.1 ± 0% -2.27% (p=0.000 n=19+20) BM_Duration_Modulo_FastPath 22.5 ± 0% 15.5 ± 0% -31.13% (p=0.000 n=19+20) ``` Note: We don't need to expose `absl::time_internal::IDivDuration` at all given that we have a public `absl::IDivDuration`. PiperOrigin-RevId: 610710635 Change-Id: Ief7c3d5b1c000b397d931e9249edcaef96e7151e
1 parent 90ebb6f commit bde089f

File tree

3 files changed

+73
-54
lines changed

3 files changed

+73
-54
lines changed

absl/time/duration.cc

+28-25
Original file line numberDiff line numberDiff line change
@@ -342,19 +342,10 @@ inline bool IDivFastPath(const Duration num, const Duration den, int64_t* q,
342342

343343
} // namespace
344344

345-
namespace time_internal {
345+
namespace {
346346

347-
// The 'satq' argument indicates whether the quotient should saturate at the
348-
// bounds of int64_t. If it does saturate, the difference will spill over to
349-
// the remainder. If it does not saturate, the remainder remain accurate,
350-
// but the returned quotient will over/underflow int64_t and should not be used.
351-
int64_t IDivDuration(bool satq, const Duration num, const Duration den,
347+
int64_t IDivSlowPath(bool satq, const Duration num, const Duration den,
352348
Duration* rem) {
353-
int64_t q = 0;
354-
if (IDivFastPath(num, den, &q, rem)) {
355-
return q;
356-
}
357-
358349
const bool num_neg = num < ZeroDuration();
359350
const bool den_neg = den < ZeroDuration();
360351
const bool quotient_neg = num_neg != den_neg;
@@ -391,7 +382,27 @@ int64_t IDivDuration(bool satq, const Duration num, const Duration den,
391382
return -static_cast<int64_t>(Uint128Low64(quotient128 - 1) & kint64max) - 1;
392383
}
393384

394-
} // namespace time_internal
385+
// The 'satq' argument indicates whether the quotient should saturate at the
386+
// bounds of int64_t. If it does saturate, the difference will spill over to
387+
// the remainder. If it does not saturate, the remainder remain accurate,
388+
// but the returned quotient will over/underflow int64_t and should not be used.
389+
ABSL_ATTRIBUTE_ALWAYS_INLINE inline int64_t IDivDurationImpl(bool satq,
390+
const Duration num,
391+
const Duration den,
392+
Duration* rem) {
393+
int64_t q = 0;
394+
if (IDivFastPath(num, den, &q, rem)) {
395+
return q;
396+
}
397+
return IDivSlowPath(satq, num, den, rem);
398+
}
399+
400+
} // namespace
401+
402+
int64_t IDivDuration(Duration num, Duration den, Duration* rem) {
403+
return IDivDurationImpl(true, num, den,
404+
rem); // trunc towards zero
405+
}
395406

396407
//
397408
// Additive operators.
@@ -475,7 +486,7 @@ Duration& Duration::operator/=(double r) {
475486
}
476487

477488
Duration& Duration::operator%=(Duration rhs) {
478-
time_internal::IDivDuration(false, *this, rhs, this);
489+
IDivDurationImpl(false, *this, rhs, this);
479490
return *this;
480491
}
481492

@@ -501,9 +512,7 @@ double FDivDuration(Duration num, Duration den) {
501512
// Trunc/Floor/Ceil.
502513
//
503514

504-
Duration Trunc(Duration d, Duration unit) {
505-
return d - (d % unit);
506-
}
515+
Duration Trunc(Duration d, Duration unit) { return d - (d % unit); }
507516

508517
Duration Floor(const Duration d, const Duration unit) {
509518
const absl::Duration td = Trunc(d, unit);
@@ -591,15 +600,9 @@ double ToDoubleMicroseconds(Duration d) {
591600
double ToDoubleMilliseconds(Duration d) {
592601
return FDivDuration(d, Milliseconds(1));
593602
}
594-
double ToDoubleSeconds(Duration d) {
595-
return FDivDuration(d, Seconds(1));
596-
}
597-
double ToDoubleMinutes(Duration d) {
598-
return FDivDuration(d, Minutes(1));
599-
}
600-
double ToDoubleHours(Duration d) {
601-
return FDivDuration(d, Hours(1));
602-
}
603+
double ToDoubleSeconds(Duration d) { return FDivDuration(d, Seconds(1)); }
604+
double ToDoubleMinutes(Duration d) { return FDivDuration(d, Minutes(1)); }
605+
double ToDoubleHours(Duration d) { return FDivDuration(d, Hours(1)); }
603606

604607
timespec ToTimespec(Duration d) {
605608
timespec ts;

absl/time/duration_benchmark.cc

+20
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,26 @@ void BM_Duration_IDivDuration_Hours(benchmark::State& state) {
290290
}
291291
BENCHMARK(BM_Duration_IDivDuration_Hours);
292292

293+
void BM_Duration_Modulo(benchmark::State& state) {
294+
int i = 0;
295+
while (state.KeepRunning()) {
296+
auto mod = absl::Seconds(i) % absl::Nanoseconds(12345);
297+
benchmark::DoNotOptimize(mod);
298+
++i;
299+
}
300+
}
301+
BENCHMARK(BM_Duration_Modulo);
302+
303+
void BM_Duration_Modulo_FastPath(benchmark::State& state) {
304+
int i = 0;
305+
while (state.KeepRunning()) {
306+
auto mod = absl::Seconds(i) % absl::Milliseconds(1);
307+
benchmark::DoNotOptimize(mod);
308+
++i;
309+
}
310+
}
311+
BENCHMARK(BM_Duration_Modulo_FastPath);
312+
293313
void BM_Duration_ToInt64Nanoseconds(benchmark::State& state) {
294314
absl::Duration d = absl::Seconds(100000);
295315
while (state.KeepRunning()) {

absl/time/time.h

+25-29
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ class Time; // Defined below
9898
class TimeZone; // Defined below
9999

100100
namespace time_internal {
101-
int64_t IDivDuration(bool satq, Duration num, Duration den, Duration* rem);
102101
ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixDuration(Duration d);
103102
ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration ToUnixDuration(Time t);
104103
ABSL_ATTRIBUTE_CONST_FUNCTION constexpr int64_t GetRepHi(Duration d);
@@ -338,30 +337,6 @@ ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration operator-(Duration lhs,
338337
return lhs -= rhs;
339338
}
340339

341-
// Multiplicative Operators
342-
// Integer operands must be representable as int64_t.
343-
template <typename T>
344-
ABSL_ATTRIBUTE_CONST_FUNCTION Duration operator*(Duration lhs, T rhs) {
345-
return lhs *= rhs;
346-
}
347-
template <typename T>
348-
ABSL_ATTRIBUTE_CONST_FUNCTION Duration operator*(T lhs, Duration rhs) {
349-
return rhs *= lhs;
350-
}
351-
template <typename T>
352-
ABSL_ATTRIBUTE_CONST_FUNCTION Duration operator/(Duration lhs, T rhs) {
353-
return lhs /= rhs;
354-
}
355-
ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t operator/(Duration lhs,
356-
Duration rhs) {
357-
return time_internal::IDivDuration(true, lhs, rhs,
358-
&lhs); // trunc towards zero
359-
}
360-
ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration operator%(Duration lhs,
361-
Duration rhs) {
362-
return lhs %= rhs;
363-
}
364-
365340
// IDivDuration()
366341
//
367342
// Divides a numerator `Duration` by a denominator `Duration`, returning the
@@ -390,10 +365,7 @@ ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration operator%(Duration lhs,
390365
// // Here, q would overflow int64_t, so rem accounts for the difference.
391366
// int64_t q = absl::IDivDuration(a, b, &rem);
392367
// // q == std::numeric_limits<int64_t>::max(), rem == a - b * q
393-
inline int64_t IDivDuration(Duration num, Duration den, Duration* rem) {
394-
return time_internal::IDivDuration(true, num, den,
395-
rem); // trunc towards zero
396-
}
368+
int64_t IDivDuration(Duration num, Duration den, Duration* rem);
397369

398370
// FDivDuration()
399371
//
@@ -409,6 +381,30 @@ inline int64_t IDivDuration(Duration num, Duration den, Duration* rem) {
409381
// // d == 1.5
410382
ABSL_ATTRIBUTE_CONST_FUNCTION double FDivDuration(Duration num, Duration den);
411383

384+
// Multiplicative Operators
385+
// Integer operands must be representable as int64_t.
386+
template <typename T>
387+
ABSL_ATTRIBUTE_CONST_FUNCTION Duration operator*(Duration lhs, T rhs) {
388+
return lhs *= rhs;
389+
}
390+
template <typename T>
391+
ABSL_ATTRIBUTE_CONST_FUNCTION Duration operator*(T lhs, Duration rhs) {
392+
return rhs *= lhs;
393+
}
394+
template <typename T>
395+
ABSL_ATTRIBUTE_CONST_FUNCTION Duration operator/(Duration lhs, T rhs) {
396+
return lhs /= rhs;
397+
}
398+
ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t operator/(Duration lhs,
399+
Duration rhs) {
400+
return IDivDuration(lhs, rhs,
401+
&lhs); // trunc towards zero
402+
}
403+
ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration operator%(Duration lhs,
404+
Duration rhs) {
405+
return lhs %= rhs;
406+
}
407+
412408
// ZeroDuration()
413409
//
414410
// Returns a zero-length duration. This function behaves just like the default

0 commit comments

Comments
 (0)