diff options
Diffstat (limited to 'absl/time')
-rw-r--r-- | absl/time/duration.cc | 5 | ||||
-rw-r--r-- | absl/time/duration_benchmark.cc | 78 | ||||
-rw-r--r-- | absl/time/duration_test.cc | 124 | ||||
-rw-r--r-- | absl/time/time.h | 97 |
4 files changed, 236 insertions, 68 deletions
diff --git a/absl/time/duration.cc b/absl/time/duration.cc index c13fa79b7fa3..f402137b0a65 100644 --- a/absl/time/duration.cc +++ b/absl/time/duration.cc @@ -895,13 +895,10 @@ bool ParseDuration(const std::string& dur_string, Duration* d) { *d = dur; return true; } - bool ParseFlag(const std::string& text, Duration* dst, std::string* ) { return ParseDuration(text, dst); } -std::string UnparseFlag(Duration d) { - return FormatDuration(d); -} +std::string UnparseFlag(Duration d) { return FormatDuration(d); } } // namespace absl diff --git a/absl/time/duration_benchmark.cc b/absl/time/duration_benchmark.cc index 54f89a1f000d..d5657bd576a5 100644 --- a/absl/time/duration_benchmark.cc +++ b/absl/time/duration_benchmark.cc @@ -27,47 +27,113 @@ namespace { // void BM_Duration_Factory_Nanoseconds(benchmark::State& state) { + int64_t i = 0; while (state.KeepRunning()) { - benchmark::DoNotOptimize(absl::Nanoseconds(1)); + benchmark::DoNotOptimize(absl::Nanoseconds(i)); + i += 314159; } } BENCHMARK(BM_Duration_Factory_Nanoseconds); void BM_Duration_Factory_Microseconds(benchmark::State& state) { + int64_t i = 0; while (state.KeepRunning()) { - benchmark::DoNotOptimize(absl::Microseconds(1)); + benchmark::DoNotOptimize(absl::Microseconds(i)); + i += 314; } } BENCHMARK(BM_Duration_Factory_Microseconds); void BM_Duration_Factory_Milliseconds(benchmark::State& state) { + int64_t i = 0; while (state.KeepRunning()) { - benchmark::DoNotOptimize(absl::Milliseconds(1)); + benchmark::DoNotOptimize(absl::Milliseconds(i)); + i += 1; } } BENCHMARK(BM_Duration_Factory_Milliseconds); void BM_Duration_Factory_Seconds(benchmark::State& state) { + int64_t i = 0; while (state.KeepRunning()) { - benchmark::DoNotOptimize(absl::Seconds(1)); + benchmark::DoNotOptimize(absl::Seconds(i)); + i += 1; } } BENCHMARK(BM_Duration_Factory_Seconds); void BM_Duration_Factory_Minutes(benchmark::State& state) { + int64_t i = 0; while (state.KeepRunning()) { - benchmark::DoNotOptimize(absl::Minutes(1)); + benchmark::DoNotOptimize(absl::Minutes(i)); + i += 1; } } BENCHMARK(BM_Duration_Factory_Minutes); void BM_Duration_Factory_Hours(benchmark::State& state) { + int64_t i = 0; while (state.KeepRunning()) { - benchmark::DoNotOptimize(absl::Hours(1)); + benchmark::DoNotOptimize(absl::Hours(i)); + i += 1; } } BENCHMARK(BM_Duration_Factory_Hours); +void BM_Duration_Factory_DoubleNanoseconds(benchmark::State& state) { + double d = 1; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::Nanoseconds(d)); + d = d * 1.00000001 + 1; + } +} +BENCHMARK(BM_Duration_Factory_DoubleNanoseconds); + +void BM_Duration_Factory_DoubleMicroseconds(benchmark::State& state) { + double d = 1e-3; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::Microseconds(d)); + d = d * 1.00000001 + 1e-3; + } +} +BENCHMARK(BM_Duration_Factory_DoubleMicroseconds); + +void BM_Duration_Factory_DoubleMilliseconds(benchmark::State& state) { + double d = 1e-6; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::Milliseconds(d)); + d = d * 1.00000001 + 1e-6; + } +} +BENCHMARK(BM_Duration_Factory_DoubleMilliseconds); + +void BM_Duration_Factory_DoubleSeconds(benchmark::State& state) { + double d = 1e-9; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::Seconds(d)); + d = d * 1.00000001 + 1e-9; + } +} +BENCHMARK(BM_Duration_Factory_DoubleSeconds); + +void BM_Duration_Factory_DoubleMinutes(benchmark::State& state) { + double d = 1e-9; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::Minutes(d)); + d = d * 1.00000001 + 1e-9; + } +} +BENCHMARK(BM_Duration_Factory_DoubleMinutes); + +void BM_Duration_Factory_DoubleHours(benchmark::State& state) { + double d = 1e-9; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::Hours(d)); + d = d * 1.00000001 + 1e-9; + } +} +BENCHMARK(BM_Duration_Factory_DoubleHours); + // // Arithmetic // diff --git a/absl/time/duration_test.cc b/absl/time/duration_test.cc index 704684edb34e..7ae25dc68f9a 100644 --- a/absl/time/duration_test.cc +++ b/absl/time/duration_test.cc @@ -16,7 +16,9 @@ #include <cmath> #include <cstdint> #include <ctime> +#include <iomanip> #include <limits> +#include <random> #include <string> #include "gmock/gmock.h" @@ -105,22 +107,22 @@ TEST(Duration, Factories) { } TEST(Duration, ToConversion) { -#define TEST_DURATION_CONVERSION(UNIT) \ - do { \ - const absl::Duration d = absl::UNIT(1.5); \ - const absl::Duration z = absl::ZeroDuration(); \ - const absl::Duration inf = absl::InfiniteDuration(); \ - const double dbl_inf = std::numeric_limits<double>::infinity(); \ - EXPECT_EQ(kint64min, absl::ToInt64##UNIT(-inf)); \ - EXPECT_EQ(-1, absl::ToInt64##UNIT(-d)); \ - EXPECT_EQ(0, absl::ToInt64##UNIT(z)); \ - EXPECT_EQ(1, absl::ToInt64##UNIT(d)); \ - EXPECT_EQ(kint64max, absl::ToInt64##UNIT(inf)); \ - EXPECT_EQ(-dbl_inf, absl::ToDouble##UNIT(-inf)); \ - EXPECT_EQ(-1.5, absl::ToDouble##UNIT(-d)); \ - EXPECT_EQ(0, absl::ToDouble##UNIT(z)); \ - EXPECT_EQ(1.5, absl::ToDouble##UNIT(d)); \ - EXPECT_EQ(dbl_inf, absl::ToDouble##UNIT(inf)); \ +#define TEST_DURATION_CONVERSION(UNIT) \ + do { \ + const absl::Duration d = absl::UNIT(1.5); \ + constexpr absl::Duration z = absl::ZeroDuration(); \ + constexpr absl::Duration inf = absl::InfiniteDuration(); \ + constexpr double dbl_inf = std::numeric_limits<double>::infinity(); \ + EXPECT_EQ(kint64min, absl::ToInt64##UNIT(-inf)); \ + EXPECT_EQ(-1, absl::ToInt64##UNIT(-d)); \ + EXPECT_EQ(0, absl::ToInt64##UNIT(z)); \ + EXPECT_EQ(1, absl::ToInt64##UNIT(d)); \ + EXPECT_EQ(kint64max, absl::ToInt64##UNIT(inf)); \ + EXPECT_EQ(-dbl_inf, absl::ToDouble##UNIT(-inf)); \ + EXPECT_EQ(-1.5, absl::ToDouble##UNIT(-d)); \ + EXPECT_EQ(0, absl::ToDouble##UNIT(z)); \ + EXPECT_EQ(1.5, absl::ToDouble##UNIT(d)); \ + EXPECT_EQ(dbl_inf, absl::ToDouble##UNIT(inf)); \ } while (0) TEST_DURATION_CONVERSION(Nanoseconds); @@ -1284,6 +1286,16 @@ TEST(Duration, SmallConversions) { EXPECT_EQ(absl::Nanoseconds(1), absl::Seconds(0.875e-9)); EXPECT_EQ(absl::Nanoseconds(1), absl::Seconds(1.000e-9)); + EXPECT_EQ(absl::ZeroDuration(), absl::Seconds(-0.124999999e-9)); + EXPECT_EQ(-absl::Nanoseconds(1) / 4, absl::Seconds(-0.125e-9)); + EXPECT_EQ(-absl::Nanoseconds(1) / 4, absl::Seconds(-0.250e-9)); + EXPECT_EQ(-absl::Nanoseconds(1) / 2, absl::Seconds(-0.375e-9)); + EXPECT_EQ(-absl::Nanoseconds(1) / 2, absl::Seconds(-0.500e-9)); + EXPECT_EQ(-absl::Nanoseconds(3) / 4, absl::Seconds(-0.625e-9)); + EXPECT_EQ(-absl::Nanoseconds(3) / 4, absl::Seconds(-0.750e-9)); + EXPECT_EQ(-absl::Nanoseconds(1), absl::Seconds(-0.875e-9)); + EXPECT_EQ(-absl::Nanoseconds(1), absl::Seconds(-1.000e-9)); + timespec ts; ts.tv_sec = 0; ts.tv_nsec = 0; @@ -1313,6 +1325,86 @@ TEST(Duration, SmallConversions) { EXPECT_THAT(ToTimeval(absl::Nanoseconds(2000)), TimevalMatcher(tv)); } +void VerifySameAsMul(double time_as_seconds, int* const misses) { + auto direct_seconds = absl::Seconds(time_as_seconds); + auto mul_by_one_second = time_as_seconds * absl::Seconds(1); + if (direct_seconds != mul_by_one_second) { + if (*misses > 10) return; + ASSERT_LE(++(*misses), 10) << "Too many errors, not reporting more."; + EXPECT_EQ(direct_seconds, mul_by_one_second) + << "given double time_as_seconds = " << std::setprecision(17) + << time_as_seconds; + } +} + +// For a variety of interesting durations, we find the exact point +// where one double converts to that duration, and the very next double +// converts to the next duration. For both of those points, verify that +// Seconds(point) returns the same duration as point * Seconds(1.0) +TEST(Duration, ToDoubleSecondsCheckEdgeCases) { + constexpr uint32_t kTicksPerSecond = absl::time_internal::kTicksPerSecond; + constexpr auto duration_tick = absl::time_internal::MakeDuration(0, 1u); + int misses = 0; + for (int64_t seconds = 0; seconds < 99; ++seconds) { + uint32_t tick_vals[] = {0, +999, +999999, +999999999, kTicksPerSecond - 1, + 0, 1000, 1000000, 1000000000, kTicksPerSecond, + 1, 1001, 1000001, 1000000001, kTicksPerSecond + 1, + 2, 1002, 1000002, 1000000002, kTicksPerSecond + 2, + 3, 1003, 1000003, 1000000003, kTicksPerSecond + 3, + 4, 1004, 1000004, 1000000004, kTicksPerSecond + 4, + 5, 6, 7, 8, 9}; + for (uint32_t ticks : tick_vals) { + absl::Duration s_plus_t = absl::Seconds(seconds) + ticks * duration_tick; + for (absl::Duration d : {s_plus_t, -s_plus_t}) { + absl::Duration after_d = d + duration_tick; + EXPECT_NE(d, after_d); + EXPECT_EQ(after_d - d, duration_tick); + + double low_edge = ToDoubleSeconds(d); + EXPECT_EQ(d, absl::Seconds(low_edge)); + + double high_edge = ToDoubleSeconds(after_d); + EXPECT_EQ(after_d, absl::Seconds(high_edge)); + + for (;;) { + double midpoint = low_edge + (high_edge - low_edge) / 2; + if (midpoint == low_edge || midpoint == high_edge) break; + absl::Duration mid_duration = absl::Seconds(midpoint); + if (mid_duration == d) { + low_edge = midpoint; + } else { + EXPECT_EQ(mid_duration, after_d); + high_edge = midpoint; + } + } + // Now low_edge is the highest double that converts to Duration d, + // and high_edge is the lowest double that converts to Duration after_d. + VerifySameAsMul(low_edge, &misses); + VerifySameAsMul(high_edge, &misses); + } + } + } +} + +TEST(Duration, ToDoubleSecondsCheckRandom) { + std::random_device rd; + std::seed_seq seed({rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()}); + std::mt19937_64 gen(seed); + // We want doubles distributed from 1/8ns up to 2^63, where + // as many values are tested from 1ns to 2ns as from 1sec to 2sec, + // so even distribute along a log-scale of those values, and + // exponentiate before using them. (9.223377e+18 is just slightly + // out of bounds for absl::Duration.) + std::uniform_real_distribution<double> uniform(std::log(0.125e-9), + std::log(9.223377e+18)); + int misses = 0; + for (int i = 0; i < 1000000; ++i) { + double d = std::exp(uniform(gen)); + VerifySameAsMul(d, &misses); + VerifySameAsMul(-d, &misses); + } +} + TEST(Duration, ConversionSaturation) { absl::Duration d; diff --git a/absl/time/time.h b/absl/time/time.h index 880fc783ae4a..c41cb89c5eff 100644 --- a/absl/time/time.h +++ b/absl/time/time.h @@ -81,6 +81,7 @@ constexpr int64_t GetRepHi(Duration d); constexpr uint32_t GetRepLo(Duration d); constexpr Duration MakeDuration(int64_t hi, uint32_t lo); constexpr Duration MakeDuration(int64_t hi, int64_t lo); +inline Duration MakePosDoubleDuration(double n); constexpr int64_t kTicksPerNanosecond = 4; constexpr int64_t kTicksPerSecond = 1000 * 1000 * 1000 * kTicksPerNanosecond; template <std::intmax_t N> @@ -295,6 +296,39 @@ Duration Floor(Duration d, Duration unit); // absl::Duration c = absl::Ceil(d, absl::Microseconds(1)); // 123457us Duration Ceil(Duration d, Duration unit); +// InfiniteDuration() +// +// Returns an infinite `Duration`. To get a `Duration` representing negative +// infinity, use `-InfiniteDuration()`. +// +// Duration arithmetic overflows to +/- infinity and saturates. In general, +// arithmetic with `Duration` infinities is similar to IEEE 754 infinities +// except where IEEE 754 NaN would be involved, in which case +/- +// `InfiniteDuration()` is used in place of a "nan" Duration. +// +// Examples: +// +// constexpr absl::Duration inf = absl::InfiniteDuration(); +// const absl::Duration d = ... any finite duration ... +// +// inf == inf + inf +// inf == inf + d +// inf == inf - inf +// -inf == d - inf +// +// inf == d * 1e100 +// inf == inf / 2 +// 0 == d / inf +// INT64_MAX == inf / d +// +// // Division by zero returns infinity, or INT64_MIN/MAX where appropriate. +// inf == d / 0 +// INT64_MAX == d / absl::ZeroDuration() +// +// The examples involving the `/` operator above also apply to `IDivDuration()` +// and `FDivDuration()`. +constexpr Duration InfiniteDuration(); + // Nanoseconds() // Microseconds() // Milliseconds() @@ -344,7 +378,13 @@ Duration Milliseconds(T n) { } template <typename T, time_internal::EnableIfFloat<T> = 0> Duration Seconds(T n) { - return n * Seconds(1); + if (n >= 0) { + if (n >= std::numeric_limits<int64_t>::max()) return InfiniteDuration(); + return time_internal::MakePosDoubleDuration(n); + } else { + if (n <= std::numeric_limits<int64_t>::min()) return -InfiniteDuration(); + return -time_internal::MakePosDoubleDuration(-n); + } } template <typename T, time_internal::EnableIfFloat<T> = 0> Duration Minutes(T n) { @@ -439,39 +479,6 @@ std::chrono::seconds ToChronoSeconds(Duration d); std::chrono::minutes ToChronoMinutes(Duration d); std::chrono::hours ToChronoHours(Duration d); -// InfiniteDuration() -// -// Returns an infinite `Duration`. To get a `Duration` representing negative -// infinity, use `-InfiniteDuration()`. -// -// Duration arithmetic overflows to +/- infinity and saturates. In general, -// arithmetic with `Duration` infinities is similar to IEEE 754 infinities -// except where IEEE 754 NaN would be involved, in which case +/- -// `InfiniteDuration()` is used in place of a "nan" Duration. -// -// Examples: -// -// constexpr absl::Duration inf = absl::InfiniteDuration(); -// const absl::Duration d = ... any finite duration ... -// -// inf == inf + inf -// inf == inf + d -// inf == inf - inf -// -inf == d - inf -// -// inf == d * 1e100 -// inf == inf / 2 -// 0 == d / inf -// INT64_MAX == inf / d -// -// // Division by zero returns infinity, or INT64_MIN/MAX where appropriate. -// inf == d / 0 -// INT64_MAX == d / absl::ZeroDuration() -// -// The examples involving the `/` operator above also apply to `IDivDuration()` -// and `FDivDuration()`. -constexpr Duration InfiniteDuration(); - // FormatDuration() // // Returns a std::string representing the duration in the form "72h3m0.5s". @@ -492,12 +499,9 @@ inline std::ostream& operator<<(std::ostream& os, Duration d) { // `ZeroDuration()`. Parses "inf" and "-inf" as +/- `InfiniteDuration()`. bool ParseDuration(const std::string& dur_string, Duration* d); -// ParseFlag() -// +// Support for flag values of type Duration. Duration flags must be specified +// in a format that is valid input for absl::ParseDuration(). bool ParseFlag(const std::string& text, Duration* dst, std::string* error); - -// UnparseFlag() -// std::string UnparseFlag(Duration d); // Time @@ -991,9 +995,6 @@ bool ParseTime(const std::string& format, const std::string& input, Time* time, bool ParseTime(const std::string& format, const std::string& input, TimeZone tz, Time* time, std::string* err); -// ParseFlag() -// UnparseFlag() -// // Support for flag values of type Time. Time flags must be specified in a // format that matches absl::RFC3339_full. For example: // @@ -1114,6 +1115,18 @@ constexpr Duration MakeDuration(int64_t hi, int64_t lo) { return MakeDuration(hi, static_cast<uint32_t>(lo)); } +// Make a Duration value from a floating-point number, as long as that number +// is in the range [ 0 .. numeric_limits<int64_t>::max ), that is, as long as +// it's positive and can be converted to int64_t without risk of UB. +inline Duration MakePosDoubleDuration(double n) { + const int64_t int_secs = static_cast<int64_t>(n); + const uint32_t ticks = + static_cast<uint32_t>((n - int_secs) * kTicksPerSecond + 0.5); + return ticks < kTicksPerSecond + ? MakeDuration(int_secs, ticks) + : MakeDuration(int_secs + 1, ticks - kTicksPerSecond); +} + // Creates a normalized Duration from an almost-normalized (sec,ticks) // pair. sec may be positive or negative. ticks must be in the range // -kTicksPerSecond < *ticks < kTicksPerSecond. If ticks is negative it |