Commit 9858e542 by Abseil Team Committed by Copybara-Service

Get rid of tail padding within `absl::Duration`. This reduces memory usage needs…

Get rid of tail padding within `absl::Duration`. This reduces memory usage needs when storing duration in containers (e.g. `vector<absl::Duration>` uses 25% less memory), and allows classes with `absl::Duration` fields to fit other stuff in memory previously used by tail padding (e.g. `std::optional<absl::Duration>` is now 16 bytes instead of 24).

PiperOrigin-RevId: 506543503
Change-Id: Ifeb2397c953a5d3da317a70ab49a3ebb85042344
parent 7c7cafef
...@@ -407,16 +407,18 @@ int64_t IDivDuration(bool satq, const Duration num, const Duration den, ...@@ -407,16 +407,18 @@ int64_t IDivDuration(bool satq, const Duration num, const Duration den,
Duration& Duration::operator+=(Duration rhs) { Duration& Duration::operator+=(Duration rhs) {
if (time_internal::IsInfiniteDuration(*this)) return *this; if (time_internal::IsInfiniteDuration(*this)) return *this;
if (time_internal::IsInfiniteDuration(rhs)) return *this = rhs; if (time_internal::IsInfiniteDuration(rhs)) return *this = rhs;
const int64_t orig_rep_hi = rep_hi_; const int64_t orig_rep_hi = rep_hi_.Get();
rep_hi_ = rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_.Get()) +
DecodeTwosComp(EncodeTwosComp(rep_hi_) + EncodeTwosComp(rhs.rep_hi_)); EncodeTwosComp(rhs.rep_hi_.Get()));
if (rep_lo_ >= kTicksPerSecond - rhs.rep_lo_) { if (rep_lo_ >= kTicksPerSecond - rhs.rep_lo_) {
rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_) + 1); rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_.Get()) + 1);
rep_lo_ -= kTicksPerSecond; rep_lo_ -= kTicksPerSecond;
} }
rep_lo_ += rhs.rep_lo_; rep_lo_ += rhs.rep_lo_;
if (rhs.rep_hi_ < 0 ? rep_hi_ > orig_rep_hi : rep_hi_ < orig_rep_hi) { if (rhs.rep_hi_.Get() < 0 ? rep_hi_.Get() > orig_rep_hi
return *this = rhs.rep_hi_ < 0 ? -InfiniteDuration() : InfiniteDuration(); : rep_hi_.Get() < orig_rep_hi) {
return *this =
rhs.rep_hi_.Get() < 0 ? -InfiniteDuration() : InfiniteDuration();
} }
return *this; return *this;
} }
...@@ -424,18 +426,21 @@ Duration& Duration::operator+=(Duration rhs) { ...@@ -424,18 +426,21 @@ Duration& Duration::operator+=(Duration rhs) {
Duration& Duration::operator-=(Duration rhs) { Duration& Duration::operator-=(Duration rhs) {
if (time_internal::IsInfiniteDuration(*this)) return *this; if (time_internal::IsInfiniteDuration(*this)) return *this;
if (time_internal::IsInfiniteDuration(rhs)) { if (time_internal::IsInfiniteDuration(rhs)) {
return *this = rhs.rep_hi_ >= 0 ? -InfiniteDuration() : InfiniteDuration(); return *this = rhs.rep_hi_.Get() >= 0 ? -InfiniteDuration()
: InfiniteDuration();
} }
const int64_t orig_rep_hi = rep_hi_; const int64_t orig_rep_hi = rep_hi_.Get();
rep_hi_ = rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_.Get()) -
DecodeTwosComp(EncodeTwosComp(rep_hi_) - EncodeTwosComp(rhs.rep_hi_)); EncodeTwosComp(rhs.rep_hi_.Get()));
if (rep_lo_ < rhs.rep_lo_) { if (rep_lo_ < rhs.rep_lo_) {
rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_) - 1); rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_.Get()) - 1);
rep_lo_ += kTicksPerSecond; rep_lo_ += kTicksPerSecond;
} }
rep_lo_ -= rhs.rep_lo_; rep_lo_ -= rhs.rep_lo_;
if (rhs.rep_hi_ < 0 ? rep_hi_ < orig_rep_hi : rep_hi_ > orig_rep_hi) { if (rhs.rep_hi_.Get() < 0 ? rep_hi_.Get() < orig_rep_hi
return *this = rhs.rep_hi_ >= 0 ? -InfiniteDuration() : InfiniteDuration(); : rep_hi_.Get() > orig_rep_hi) {
return *this = rhs.rep_hi_.Get() >= 0 ? -InfiniteDuration()
: InfiniteDuration();
} }
return *this; return *this;
} }
...@@ -446,7 +451,7 @@ Duration& Duration::operator-=(Duration rhs) { ...@@ -446,7 +451,7 @@ Duration& Duration::operator-=(Duration rhs) {
Duration& Duration::operator*=(int64_t r) { Duration& Duration::operator*=(int64_t r) {
if (time_internal::IsInfiniteDuration(*this)) { if (time_internal::IsInfiniteDuration(*this)) {
const bool is_neg = (r < 0) != (rep_hi_ < 0); const bool is_neg = (r < 0) != (rep_hi_.Get() < 0);
return *this = is_neg ? -InfiniteDuration() : InfiniteDuration(); return *this = is_neg ? -InfiniteDuration() : InfiniteDuration();
} }
return *this = ScaleFixed<SafeMultiply>(*this, r); return *this = ScaleFixed<SafeMultiply>(*this, r);
...@@ -454,7 +459,7 @@ Duration& Duration::operator*=(int64_t r) { ...@@ -454,7 +459,7 @@ Duration& Duration::operator*=(int64_t r) {
Duration& Duration::operator*=(double r) { Duration& Duration::operator*=(double r) {
if (time_internal::IsInfiniteDuration(*this) || !IsFinite(r)) { if (time_internal::IsInfiniteDuration(*this) || !IsFinite(r)) {
const bool is_neg = (std::signbit(r) != 0) != (rep_hi_ < 0); const bool is_neg = std::signbit(r) != (rep_hi_.Get() < 0);
return *this = is_neg ? -InfiniteDuration() : InfiniteDuration(); return *this = is_neg ? -InfiniteDuration() : InfiniteDuration();
} }
return *this = ScaleDouble<std::multiplies>(*this, r); return *this = ScaleDouble<std::multiplies>(*this, r);
...@@ -462,7 +467,7 @@ Duration& Duration::operator*=(double r) { ...@@ -462,7 +467,7 @@ Duration& Duration::operator*=(double r) {
Duration& Duration::operator/=(int64_t r) { Duration& Duration::operator/=(int64_t r) {
if (time_internal::IsInfiniteDuration(*this) || r == 0) { if (time_internal::IsInfiniteDuration(*this) || r == 0) {
const bool is_neg = (r < 0) != (rep_hi_ < 0); const bool is_neg = (r < 0) != (rep_hi_.Get() < 0);
return *this = is_neg ? -InfiniteDuration() : InfiniteDuration(); return *this = is_neg ? -InfiniteDuration() : InfiniteDuration();
} }
return *this = ScaleFixed<std::divides>(*this, r); return *this = ScaleFixed<std::divides>(*this, r);
...@@ -470,7 +475,7 @@ Duration& Duration::operator/=(int64_t r) { ...@@ -470,7 +475,7 @@ Duration& Duration::operator/=(int64_t r) {
Duration& Duration::operator/=(double r) { Duration& Duration::operator/=(double r) {
if (time_internal::IsInfiniteDuration(*this) || !IsValidDivisor(r)) { if (time_internal::IsInfiniteDuration(*this) || !IsValidDivisor(r)) {
const bool is_neg = (std::signbit(r) != 0) != (rep_hi_ < 0); const bool is_neg = std::signbit(r) != (rep_hi_.Get() < 0);
return *this = is_neg ? -InfiniteDuration() : InfiniteDuration(); return *this = is_neg ? -InfiniteDuration() : InfiniteDuration();
} }
return *this = ScaleDouble<std::divides>(*this, r); return *this = ScaleDouble<std::divides>(*this, r);
......
...@@ -16,8 +16,9 @@ ...@@ -16,8 +16,9 @@
#include <winsock2.h> // for timeval #include <winsock2.h> // for timeval
#endif #endif
#include <chrono> // NOLINT(build/c++11) #include <array>
#include <cfloat> #include <cfloat>
#include <chrono> // NOLINT(build/c++11)
#include <cmath> #include <cmath>
#include <cstdint> #include <cstdint>
#include <ctime> #include <ctime>
...@@ -1853,4 +1854,11 @@ TEST(Duration, FormatParseRoundTrip) { ...@@ -1853,4 +1854,11 @@ TEST(Duration, FormatParseRoundTrip) {
#undef TEST_PARSE_ROUNDTRIP #undef TEST_PARSE_ROUNDTRIP
} }
TEST(Duration, NoPadding) {
// Should match the size of a struct with uint32_t alignment and no padding.
using NoPadding = std::array<uint32_t, 3>;
EXPECT_EQ(sizeof(NoPadding), sizeof(absl::Duration));
EXPECT_EQ(alignof(NoPadding), alignof(absl::Duration));
}
} // namespace } // namespace
...@@ -84,6 +84,7 @@ struct timeval; ...@@ -84,6 +84,7 @@ struct timeval;
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
#include "absl/base/config.h"
#include "absl/base/macros.h" #include "absl/base/macros.h"
#include "absl/strings/string_view.h" #include "absl/strings/string_view.h"
#include "absl/time/civil_time.h" #include "absl/time/civil_time.h"
...@@ -214,7 +215,7 @@ class Duration { ...@@ -214,7 +215,7 @@ class Duration {
template <typename H> template <typename H>
friend H AbslHashValue(H h, Duration d) { friend H AbslHashValue(H h, Duration d) {
return H::combine(std::move(h), d.rep_hi_, d.rep_lo_); return H::combine(std::move(h), d.rep_hi_.Get(), d.rep_lo_);
} }
private: private:
...@@ -223,7 +224,73 @@ class Duration { ...@@ -223,7 +224,73 @@ class Duration {
friend constexpr Duration time_internal::MakeDuration(int64_t hi, friend constexpr Duration time_internal::MakeDuration(int64_t hi,
uint32_t lo); uint32_t lo);
constexpr Duration(int64_t hi, uint32_t lo) : rep_hi_(hi), rep_lo_(lo) {} constexpr Duration(int64_t hi, uint32_t lo) : rep_hi_(hi), rep_lo_(lo) {}
int64_t rep_hi_;
// We store `hi_rep_` 4-byte rather than 8-byte aligned to avoid 4 bytes of
// tail padding.
class HiRep {
public:
// Default constructor default-initializes `hi_`, which has the same
// semantics as default-initializing an `int64_t` (undetermined value).
HiRep() = default;
HiRep(const HiRep&) = default;
HiRep& operator=(const HiRep&) = default;
explicit constexpr HiRep(const int64_t value)
: // C++17 forbids default-initialization in constexpr contexts. We can
// remove this in C++20.
lo_(0),
hi_(0) {
*this = value;
}
constexpr int64_t Get() const {
const uint64_t unsigned_value =
(static_cast<uint64_t>(hi_) << 32) | static_cast<uint64_t>(lo_);
// `static_cast<int64_t>(unsigned_value)` is implementation-defined
// before c++20. On all supported platforms the behaviour is that mandated
// by c++20, i.e. "If the destination type is signed, [...] the result is
// the unique value of the destination type equal to the source value
// modulo 2^n, where n is the number of bits used to represent the
// destination type."
static_assert(
(static_cast<int64_t>((std::numeric_limits<uint64_t>::max)()) ==
int64_t{-1}) &&
(static_cast<int64_t>(static_cast<uint64_t>(
(std::numeric_limits<int64_t>::max)()) +
1) ==
(std::numeric_limits<int64_t>::min)()),
"static_cast<int64_t>(uint64_t) does not have c++20 semantics");
return static_cast<int64_t>(unsigned_value);
}
constexpr HiRep& operator=(const int64_t value) {
// "If the destination type is unsigned, the resulting value is the
// smallest unsigned value equal to the source value modulo 2^n
// where `n` is the number of bits used to represent the destination
// type".
const auto unsigned_value = static_cast<uint64_t>(value);
hi_ = static_cast<uint32_t>(unsigned_value >> 32);
lo_ = static_cast<uint32_t>(unsigned_value);
return *this;
}
private:
// Notes:
// - Ideally we would use a `char[]` and `std::bitcast`, but the latter
// does not exist (and is not constexpr in `absl`) before c++20.
// - Order is optimized depending on endianness so that the compiler can
// turn `Get()` (resp. `operator=()`) into a single 8-byte load (resp.
// store).
#if defined(ABSL_IS_BIG_ENDIAN) && ABSL_IS_BIG_ENDIAN
uint32_t hi_;
uint32_t lo_;
#else
uint32_t lo_;
uint32_t hi_;
#endif
};
HiRep rep_hi_;
uint32_t rep_lo_; uint32_t rep_lo_;
}; };
...@@ -1491,7 +1558,7 @@ ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration MakeNormalizedDuration( ...@@ -1491,7 +1558,7 @@ ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration MakeNormalizedDuration(
// Provide access to the Duration representation. // Provide access to the Duration representation.
ABSL_ATTRIBUTE_CONST_FUNCTION constexpr int64_t GetRepHi(Duration d) { ABSL_ATTRIBUTE_CONST_FUNCTION constexpr int64_t GetRepHi(Duration d) {
return d.rep_hi_; return d.rep_hi_.Get();
} }
ABSL_ATTRIBUTE_CONST_FUNCTION constexpr uint32_t GetRepLo(Duration d) { ABSL_ATTRIBUTE_CONST_FUNCTION constexpr uint32_t GetRepLo(Duration d) {
return d.rep_lo_; return d.rep_lo_;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment