diff options
22 files changed, 1804 insertions, 278 deletions
diff --git a/absl/base/exception_safety_testing_test.cc b/absl/base/exception_safety_testing_test.cc index a8b82b73daf1..97c8d6f83189 100644 --- a/absl/base/exception_safety_testing_test.cc +++ b/absl/base/exception_safety_testing_test.cc @@ -168,8 +168,58 @@ TEST(ThrowingValueTest, ThrowingCompoundAssignmentOps) { TEST(ThrowingValueTest, ThrowingStreamOps) { ThrowingValue<> bomb; - TestOp([&]() { std::cin >> bomb; }); - TestOp([&]() { std::cout << bomb; }); + TestOp([&]() { + std::istringstream stream; + stream >> bomb; + }); + TestOp([&]() { + std::stringstream stream; + stream << bomb; + }); +} + +// Tests the operator<< of ThrowingValue by forcing ConstructorTracker to emit +// a nonfatal failure that contains the std::string representation of the Thrower +TEST(ThrowingValueTest, StreamOpsOutput) { + using ::testing::TypeSpec; + exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown); + + // Test default spec list (kEverythingThrows) + EXPECT_NONFATAL_FAILURE( + { + using Thrower = ThrowingValue<TypeSpec{}>; + auto thrower = Thrower(123); + thrower.~Thrower(); + }, + "ThrowingValue<>(123)"); + + // Test with one item in spec list (kNoThrowCopy) + EXPECT_NONFATAL_FAILURE( + { + using Thrower = ThrowingValue<TypeSpec::kNoThrowCopy>; + auto thrower = Thrower(234); + thrower.~Thrower(); + }, + "ThrowingValue<kNoThrowCopy>(234)"); + + // Test with multiple items in spec list (kNoThrowMove, kNoThrowNew) + EXPECT_NONFATAL_FAILURE( + { + using Thrower = + ThrowingValue<TypeSpec::kNoThrowMove | TypeSpec::kNoThrowNew>; + auto thrower = Thrower(345); + thrower.~Thrower(); + }, + "ThrowingValue<kNoThrowMove | kNoThrowNew>(345)"); + + // Test with all items in spec list (kNoThrowCopy, kNoThrowMove, kNoThrowNew) + EXPECT_NONFATAL_FAILURE( + { + using Thrower = ThrowingValue<static_cast<TypeSpec>(-1)>; + auto thrower = Thrower(456); + thrower.~Thrower(); + }, + "ThrowingValue<kNoThrowCopy | kNoThrowMove | kNoThrowNew>(456)"); } template <typename F> @@ -653,20 +703,20 @@ struct BasicGuaranteeWithExtraInvariants : public NonNegative { }; constexpr int BasicGuaranteeWithExtraInvariants::kExceptionSentinel; -TEST(ExceptionCheckTest, BasicGuaranteeWithInvariants) { +TEST(ExceptionCheckTest, BasicGuaranteeWithExtraInvariants) { auto tester_with_val = tester.WithInitialValue(BasicGuaranteeWithExtraInvariants{}); EXPECT_TRUE(tester_with_val.Test()); EXPECT_TRUE( tester_with_val - .WithInvariants([](BasicGuaranteeWithExtraInvariants* w) { - if (w->i == BasicGuaranteeWithExtraInvariants::kExceptionSentinel) { + .WithInvariants([](BasicGuaranteeWithExtraInvariants* o) { + if (o->i == BasicGuaranteeWithExtraInvariants::kExceptionSentinel) { return testing::AssertionSuccess(); } return testing::AssertionFailure() << "i should be " << BasicGuaranteeWithExtraInvariants::kExceptionSentinel - << ", but is " << w->i; + << ", but is " << o->i; }) .Test()); } @@ -846,29 +896,28 @@ TEST(ConstructorTrackerTest, NotDestroyedAfter) { new (&storage) Tracked; }, "not destroyed"); - - // Manual destruction of the Tracked instance is not required because - // ~ConstructorTracker() handles that automatically when a leak is found } TEST(ConstructorTrackerTest, DestroyedTwice) { + exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown); EXPECT_NONFATAL_FAILURE( { Tracked t; t.~Tracked(); }, - "destroyed improperly"); + "re-destroyed"); } TEST(ConstructorTrackerTest, ConstructedTwice) { + exceptions_internal::ConstructorTracker ct(exceptions_internal::countdown); absl::aligned_storage_t<sizeof(Tracked), alignof(Tracked)> storage; EXPECT_NONFATAL_FAILURE( { new (&storage) Tracked; new (&storage) Tracked; + reinterpret_cast<Tracked*>(&storage)->~Tracked(); }, "re-constructed"); - reinterpret_cast<Tracked*>(&storage)->~Tracked(); } TEST(ThrowingValueTraitsTest, RelationalOperators) { diff --git a/absl/base/internal/exception_safety_testing.cc b/absl/base/internal/exception_safety_testing.cc index d3e94074b822..f1d081f7e50d 100644 --- a/absl/base/internal/exception_safety_testing.cc +++ b/absl/base/internal/exception_safety_testing.cc @@ -21,16 +21,14 @@ namespace testing { exceptions_internal::NoThrowTag nothrow_ctor; -bool nothrow_guarantee(const void*) { - return ::testing::AssertionFailure() - << "Exception thrown violating NoThrow Guarantee"; -} exceptions_internal::StrongGuaranteeTagType strong_guarantee; namespace exceptions_internal { int countdown = -1; +ConstructorTracker* ConstructorTracker::current_tracker_instance_ = nullptr; + void MaybeThrow(absl::string_view msg, bool throw_bad_alloc) { if (countdown-- == 0) { if (throw_bad_alloc) throw TestBadAllocException(msg); @@ -43,6 +41,31 @@ testing::AssertionResult FailureMessage(const TestException& e, return testing::AssertionFailure() << "Exception thrown from " << e.what(); } +std::string GetSpecString(TypeSpec spec) { + std::string out; + absl::string_view sep; + const auto append = [&](absl::string_view s) { + absl::StrAppend(&out, sep, s); + sep = " | "; + }; + if (static_cast<bool>(TypeSpec::kNoThrowCopy & spec)) { + append("kNoThrowCopy"); + } + if (static_cast<bool>(TypeSpec::kNoThrowMove & spec)) { + append("kNoThrowMove"); + } + if (static_cast<bool>(TypeSpec::kNoThrowNew & spec)) { + append("kNoThrowNew"); + } + return out; +} + +std::string GetSpecString(AllocSpec spec) { + return static_cast<bool>(AllocSpec::kNoThrowAllocate & spec) + ? "kNoThrowAllocate" + : ""; +} + } // namespace exceptions_internal } // namespace testing diff --git a/absl/base/internal/exception_safety_testing.h b/absl/base/internal/exception_safety_testing.h index c3ff34c50abc..8c2f5093fc4d 100644 --- a/absl/base/internal/exception_safety_testing.h +++ b/absl/base/internal/exception_safety_testing.h @@ -62,6 +62,9 @@ constexpr AllocSpec operator&(AllocSpec a, AllocSpec b) { namespace exceptions_internal { +std::string GetSpecString(TypeSpec); +std::string GetSpecString(AllocSpec); + struct NoThrowTag {}; struct StrongGuaranteeTagType {}; @@ -101,70 +104,96 @@ void MaybeThrow(absl::string_view msg, bool throw_bad_alloc = false); testing::AssertionResult FailureMessage(const TestException& e, int countdown) noexcept; -class ConstructorTracker; +struct TrackedAddress { + bool is_alive; + std::string description; +}; -class TrackedObject { +// Inspects the constructions and destructions of anything inheriting from +// TrackedObject. This allows us to safely "leak" TrackedObjects, as +// ConstructorTracker will destroy everything left over in its destructor. +class ConstructorTracker { public: - TrackedObject(const TrackedObject&) = delete; - TrackedObject(TrackedObject&&) = delete; + explicit ConstructorTracker(int count) : countdown_(count) { + assert(current_tracker_instance_ == nullptr); + current_tracker_instance_ = this; + } - protected: - explicit TrackedObject(const char* child_ctor) { - if (!GetInstanceMap().emplace(this, child_ctor).second) { - ADD_FAILURE() << "Object at address " << static_cast<void*>(this) - << " re-constructed in ctor " << child_ctor; + ~ConstructorTracker() { + assert(current_tracker_instance_ == this); + current_tracker_instance_ = nullptr; + + for (auto& it : address_map_) { + void* address = it.first; + TrackedAddress& tracked_address = it.second; + if (tracked_address.is_alive) { + ADD_FAILURE() << "Object at address " << address + << " with countdown of " << countdown_ + << " was not destroyed [" << tracked_address.description + << "]"; + } } } - ~TrackedObject() noexcept { - if (GetInstanceMap().erase(this) == 0) { - ADD_FAILURE() << "Object at address " << static_cast<void*>(this) - << " destroyed improperly"; + static void ObjectConstructed(void* address, std::string description) { + if (!CurrentlyTracking()) return; + + TrackedAddress& tracked_address = + current_tracker_instance_->address_map_[address]; + if (tracked_address.is_alive) { + ADD_FAILURE() << "Object at address " << address << " with countdown of " + << current_tracker_instance_->countdown_ + << " was re-constructed. Previously: [" + << tracked_address.description << "] Now: [" << description + << "]"; } + tracked_address = {true, std::move(description)}; + } + + static void ObjectDestructed(void* address) { + if (!CurrentlyTracking()) return; + + auto it = current_tracker_instance_->address_map_.find(address); + // Not tracked. Ignore. + if (it == current_tracker_instance_->address_map_.end()) return; + + TrackedAddress& tracked_address = it->second; + if (!tracked_address.is_alive) { + ADD_FAILURE() << "Object at address " << address << " with countdown of " + << current_tracker_instance_->countdown_ + << " was re-destroyed or created prior to construction " + << "tracking [" << tracked_address.description << "]"; + } + tracked_address.is_alive = false; } private: - using InstanceMap = std::unordered_map<TrackedObject*, absl::string_view>; - static InstanceMap& GetInstanceMap() { - static auto* instance_map = new InstanceMap(); - return *instance_map; + static bool CurrentlyTracking() { + return current_tracker_instance_ != nullptr; } - friend class ConstructorTracker; + std::unordered_map<void*, TrackedAddress> address_map_; + int countdown_; + + static ConstructorTracker* current_tracker_instance_; }; -// Inspects the constructions and destructions of anything inheriting from -// TrackedObject. This allows us to safely "leak" TrackedObjects, as -// ConstructorTracker will destroy everything left over in its destructor. -class ConstructorTracker { +class TrackedObject { public: - explicit ConstructorTracker(int c) - : init_count_(c), init_instances_(TrackedObject::GetInstanceMap()) {} - ~ConstructorTracker() { - auto& cur_instances = TrackedObject::GetInstanceMap(); - for (auto it = cur_instances.begin(); it != cur_instances.end();) { - if (init_instances_.count(it->first) == 0) { - ADD_FAILURE() << "Object at address " << static_cast<void*>(it->first) - << " constructed from " << it->second - << " where the exception countdown was set to " - << init_count_ << " was not destroyed"; - // Erasing an item inside an unordered_map invalidates the existing - // iterator. A new one is returned for iteration to continue. - it = cur_instances.erase(it); - } else { - ++it; - } - } + TrackedObject(const TrackedObject&) = delete; + TrackedObject(TrackedObject&&) = delete; + + protected: + explicit TrackedObject(std::string description) { + ConstructorTracker::ObjectConstructed(this, std::move(description)); } - private: - int init_count_; - TrackedObject::InstanceMap init_instances_; + ~TrackedObject() noexcept { ConstructorTracker::ObjectDestructed(this); } }; template <typename Factory, typename Operation, typename Invariant> absl::optional<testing::AssertionResult> TestSingleInvariantAtCountdownImpl( - const Factory& factory, Operation operation, int count, + const Factory& factory, const Operation& operation, int count, const Invariant& invariant) { auto t_ptr = factory(); absl::optional<testing::AssertionResult> current_res; @@ -229,7 +258,6 @@ inline absl::optional<testing::AssertionResult> TestAllInvariantsAtCountdown( extern exceptions_internal::NoThrowTag nothrow_ctor; -bool nothrow_guarantee(const void*); extern exceptions_internal::StrongGuaranteeTagType strong_guarantee; // A test class which is convertible to bool. The conversion can be @@ -283,17 +311,18 @@ class ThrowingValue : private exceptions_internal::TrackedObject { return static_cast<bool>(Spec & spec); } + static constexpr int kDefaultValue = 0; static constexpr int kBadValue = 938550620; public: - ThrowingValue() : TrackedObject(ABSL_PRETTY_FUNCTION) { + ThrowingValue() : TrackedObject(GetInstanceString(kDefaultValue)) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - dummy_ = 0; + dummy_ = kDefaultValue; } ThrowingValue(const ThrowingValue& other) noexcept( IsSpecified(TypeSpec::kNoThrowCopy)) - : TrackedObject(ABSL_PRETTY_FUNCTION) { + : TrackedObject(GetInstanceString(other.dummy_)) { if (!IsSpecified(TypeSpec::kNoThrowCopy)) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); } @@ -302,20 +331,20 @@ class ThrowingValue : private exceptions_internal::TrackedObject { ThrowingValue(ThrowingValue&& other) noexcept( IsSpecified(TypeSpec::kNoThrowMove)) - : TrackedObject(ABSL_PRETTY_FUNCTION) { + : TrackedObject(GetInstanceString(other.dummy_)) { if (!IsSpecified(TypeSpec::kNoThrowMove)) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); } dummy_ = other.dummy_; } - explicit ThrowingValue(int i) : TrackedObject(ABSL_PRETTY_FUNCTION) { + explicit ThrowingValue(int i) : TrackedObject(GetInstanceString(i)) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); dummy_ = i; } ThrowingValue(int i, exceptions_internal::NoThrowTag) noexcept - : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(i) {} + : TrackedObject(GetInstanceString(i)), dummy_(i) {} // absl expects nothrow destructors ~ThrowingValue() noexcept = default; @@ -548,9 +577,9 @@ class ThrowingValue : private exceptions_internal::TrackedObject { void operator&() const = delete; // NOLINT(runtime/operator) // Stream operators - friend std::ostream& operator<<(std::ostream& os, const ThrowingValue&) { + friend std::ostream& operator<<(std::ostream& os, const ThrowingValue& tv) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); - return os; + return os << GetInstanceString(tv.dummy_); } friend std::istream& operator>>(std::istream& is, const ThrowingValue&) { @@ -606,6 +635,12 @@ class ThrowingValue : private exceptions_internal::TrackedObject { const int& Get() const noexcept { return dummy_; } private: + static std::string GetInstanceString(int dummy) { + return absl::StrCat("ThrowingValue<", + exceptions_internal::GetSpecString(Spec), ">(", dummy, + ")"); + } + int dummy_; }; // While not having to do with exceptions, explicitly delete comma operator, to @@ -658,26 +693,30 @@ class ThrowingAllocator : private exceptions_internal::TrackedObject { using propagate_on_container_swap = std::true_type; using is_always_equal = std::false_type; - ThrowingAllocator() : TrackedObject(ABSL_PRETTY_FUNCTION) { + ThrowingAllocator() : TrackedObject(GetInstanceString(next_id_)) { exceptions_internal::MaybeThrow(ABSL_PRETTY_FUNCTION); dummy_ = std::make_shared<const int>(next_id_++); } template <typename U> ThrowingAllocator(const ThrowingAllocator<U, Spec>& other) noexcept // NOLINT - : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(other.State()) {} + : TrackedObject(GetInstanceString(*other.State())), + dummy_(other.State()) {} // According to C++11 standard [17.6.3.5], Table 28, the move/copy ctors of // allocator shall not exit via an exception, thus they are marked noexcept. ThrowingAllocator(const ThrowingAllocator& other) noexcept - : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(other.State()) {} + : TrackedObject(GetInstanceString(*other.State())), + dummy_(other.State()) {} template <typename U> ThrowingAllocator(ThrowingAllocator<U, Spec>&& other) noexcept // NOLINT - : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(std::move(other.State())) {} + : TrackedObject(GetInstanceString(*other.State())), + dummy_(std::move(other.State())) {} ThrowingAllocator(ThrowingAllocator&& other) noexcept - : TrackedObject(ABSL_PRETTY_FUNCTION), dummy_(std::move(other.State())) {} + : TrackedObject(GetInstanceString(*other.State())), + dummy_(std::move(other.State())) {} ~ThrowingAllocator() noexcept = default; @@ -758,6 +797,12 @@ class ThrowingAllocator : private exceptions_internal::TrackedObject { friend class ThrowingAllocator; private: + static std::string GetInstanceString(int dummy) { + return absl::StrCat("ThrowingAllocator<", + exceptions_internal::GetSpecString(Spec), ">(", dummy, + ")"); + } + const std::shared_ptr<const int>& State() const { return dummy_; } std::shared_ptr<const int>& State() { return dummy_; } @@ -801,6 +846,29 @@ void TestThrowingCtor(Args&&... args) { } } +// Tests the nothrow guarantee of the provided nullary operation. If the an +// exception is thrown, the result will be AssertionFailure(). Otherwise, it +// will be AssertionSuccess(). +template <typename Operation> +testing::AssertionResult TestNothrowOp(const Operation& operation) { + struct Cleanup { + Cleanup() { exceptions_internal::SetCountdown(); } + ~Cleanup() { exceptions_internal::UnsetCountdown(); } + } c; + try { + operation(); + return testing::AssertionSuccess(); + } catch (exceptions_internal::TestException) { + return testing::AssertionFailure() + << "TestException thrown during call to operation() when nothrow " + "guarantee was expected."; + } catch (...) { + return testing::AssertionFailure() + << "Unknown exception thrown during call to operation() when " + "nothrow guarantee was expected."; + } +} + namespace exceptions_internal { // Dummy struct for ExceptionSafetyTester<> partial state. diff --git a/absl/base/internal/spinlock.cc b/absl/base/internal/spinlock.cc index 28a2059f3287..1b97efbccc58 100644 --- a/absl/base/internal/spinlock.cc +++ b/absl/base/internal/spinlock.cc @@ -18,10 +18,12 @@ #include <atomic> #include <limits> +#include "absl/base/attributes.h" #include "absl/base/internal/atomic_hook.h" #include "absl/base/internal/cycleclock.h" #include "absl/base/internal/spinlock_wait.h" #include "absl/base/internal/sysinfo.h" /* For NumCPUs() */ +#include "absl/base/call_once.h" // Description of lock-word: // 31..00: [............................3][2][1][0] @@ -54,30 +56,10 @@ namespace absl { namespace base_internal { -static int adaptive_spin_count = 0; - -namespace { -struct SpinLock_InitHelper { - SpinLock_InitHelper() { - // On multi-cpu machines, spin for longer before yielding - // the processor or sleeping. Reduces idle time significantly. - if (base_internal::NumCPUs() > 1) { - adaptive_spin_count = 1000; - } - } -}; - -// Hook into global constructor execution: -// We do not do adaptive spinning before that, -// but nothing lock-intensive should be going on at that time. -static SpinLock_InitHelper init_helper; - ABSL_CONST_INIT static base_internal::AtomicHook<void (*)(const void *lock, int64_t wait_cycles)> submit_profile_data; -} // namespace - void RegisterSpinLockProfiler(void (*fn)(const void *contendedlock, int64_t wait_cycles)) { submit_profile_data.Store(fn); @@ -120,6 +102,14 @@ void SpinLock::InitLinkerInitializedAndCooperative() { // from the lock is returned from the method. uint32_t SpinLock::SpinLoop(int64_t initial_wait_timestamp, uint32_t *wait_cycles) { + // We are already in the slow path of SpinLock, initialize the + // adaptive_spin_count here. + ABSL_CONST_INIT static absl::once_flag init_adaptive_spin_count; + ABSL_CONST_INIT static int adaptive_spin_count = 0; + base_internal::LowLevelCallOnce(&init_adaptive_spin_count, []() { + adaptive_spin_count = base_internal::NumCPUs() > 1 ? 1000 : 1; + }); + int c = adaptive_spin_count; uint32_t lock_value; do { diff --git a/absl/base/optimization.h b/absl/base/optimization.h index aaaffa495a9a..2fddfc800c1d 100644 --- a/absl/base/optimization.h +++ b/absl/base/optimization.h @@ -158,8 +158,8 @@ #define ABSL_PREDICT_FALSE(x) (__builtin_expect(x, 0)) #define ABSL_PREDICT_TRUE(x) (__builtin_expect(!!(x), 1)) #else -#define ABSL_PREDICT_FALSE(x) x -#define ABSL_PREDICT_TRUE(x) x +#define ABSL_PREDICT_FALSE(x) (x) +#define ABSL_PREDICT_TRUE(x) (x) #endif #endif // ABSL_BASE_OPTIMIZATION_H_ diff --git a/absl/numeric/BUILD.bazel b/absl/numeric/BUILD.bazel index 0791145d0f89..f49571ebb3e1 100644 --- a/absl/numeric/BUILD.bazel +++ b/absl/numeric/BUILD.bazel @@ -53,3 +53,15 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "int128_benchmark", + srcs = ["int128_benchmark.cc"], + copts = ABSL_TEST_COPTS, + tags = ["benchmark"], + deps = [ + ":int128", + "//absl/base:config", + "@com_github_google_benchmark//:benchmark_main", + ], +) diff --git a/absl/numeric/int128_benchmark.cc b/absl/numeric/int128_benchmark.cc new file mode 100644 index 000000000000..1cb7d0ed87e0 --- /dev/null +++ b/absl/numeric/int128_benchmark.cc @@ -0,0 +1,221 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/numeric/int128.h" + +#include <algorithm> +#include <cstdint> +#include <random> +#include <vector> + +#include "benchmark/benchmark.h" +#include "absl/base/config.h" + +namespace { + +constexpr size_t kSampleSize = 1000000; + +std::mt19937 MakeRandomEngine() { + std::random_device r; + std::seed_seq seed({r(), r(), r(), r(), r(), r(), r(), r()}); + return std::mt19937(seed); +} + +std::vector<std::pair<absl::uint128, absl::uint128>> +GetRandomClass128SampleUniformDivisor() { + std::vector<std::pair<absl::uint128, absl::uint128>> values; + std::mt19937 random = MakeRandomEngine(); + std::uniform_int_distribution<uint64_t> uniform_uint64; + values.reserve(kSampleSize); + for (size_t i = 0; i < kSampleSize; ++i) { + absl::uint128 a = + absl::MakeUint128(uniform_uint64(random), uniform_uint64(random)); + absl::uint128 b = + absl::MakeUint128(uniform_uint64(random), uniform_uint64(random)); + values.emplace_back(std::max(a, b), + std::max(absl::uint128(2), std::min(a, b))); + } + return values; +} + +void BM_DivideClass128UniformDivisor(benchmark::State& state) { + auto values = GetRandomClass128SampleUniformDivisor(); + while (state.KeepRunningBatch(values.size())) { + for (const auto& pair : values) { + benchmark::DoNotOptimize(pair.first / pair.second); + } + } +} +BENCHMARK(BM_DivideClass128UniformDivisor); + +std::vector<std::pair<absl::uint128, uint64_t>> +GetRandomClass128SampleSmallDivisor() { + std::vector<std::pair<absl::uint128, uint64_t>> values; + std::mt19937 random = MakeRandomEngine(); + std::uniform_int_distribution<uint64_t> uniform_uint64; + values.reserve(kSampleSize); + for (size_t i = 0; i < kSampleSize; ++i) { + absl::uint128 a = + absl::MakeUint128(uniform_uint64(random), uniform_uint64(random)); + uint64_t b = std::max(uint64_t{2}, uniform_uint64(random)); + values.emplace_back(std::max(a, absl::uint128(b)), b); + } + return values; +} + +void BM_DivideClass128SmallDivisor(benchmark::State& state) { + auto values = GetRandomClass128SampleSmallDivisor(); + while (state.KeepRunningBatch(values.size())) { + for (const auto& pair : values) { + benchmark::DoNotOptimize(pair.first / pair.second); + } + } +} +BENCHMARK(BM_DivideClass128SmallDivisor); + +std::vector<std::pair<absl::uint128, absl::uint128>> GetRandomClass128Sample() { + std::vector<std::pair<absl::uint128, absl::uint128>> values; + std::mt19937 random = MakeRandomEngine(); + std::uniform_int_distribution<uint64_t> uniform_uint64; + values.reserve(kSampleSize); + for (size_t i = 0; i < kSampleSize; ++i) { + values.emplace_back( + absl::MakeUint128(uniform_uint64(random), uniform_uint64(random)), + absl::MakeUint128(uniform_uint64(random), uniform_uint64(random))); + } + return values; +} + +void BM_MultiplyClass128(benchmark::State& state) { + auto values = GetRandomClass128Sample(); + while (state.KeepRunningBatch(values.size())) { + for (const auto& pair : values) { + benchmark::DoNotOptimize(pair.first * pair.second); + } + } +} +BENCHMARK(BM_MultiplyClass128); + +void BM_AddClass128(benchmark::State& state) { + auto values = GetRandomClass128Sample(); + while (state.KeepRunningBatch(values.size())) { + for (const auto& pair : values) { + benchmark::DoNotOptimize(pair.first + pair.second); + } + } +} +BENCHMARK(BM_AddClass128); + +#ifdef ABSL_HAVE_INTRINSIC_INT128 + +// Some implementations of <random> do not support __int128 when it is +// available, so we make our own uniform_int_distribution-like type. +class UniformIntDistribution128 { + public: + // NOLINTNEXTLINE: mimicking std::uniform_int_distribution API + unsigned __int128 operator()(std::mt19937& generator) { + return (static_cast<unsigned __int128>(dist64_(generator)) << 64) | + dist64_(generator); + } + + private: + std::uniform_int_distribution<uint64_t> dist64_; +}; + +std::vector<std::pair<unsigned __int128, unsigned __int128>> +GetRandomIntrinsic128SampleUniformDivisor() { + std::vector<std::pair<unsigned __int128, unsigned __int128>> values; + std::mt19937 random = MakeRandomEngine(); + UniformIntDistribution128 uniform_uint128; + values.reserve(kSampleSize); + for (size_t i = 0; i < kSampleSize; ++i) { + unsigned __int128 a = uniform_uint128(random); + unsigned __int128 b = uniform_uint128(random); + values.emplace_back( + std::max(a, b), + std::max(static_cast<unsigned __int128>(2), std::min(a, b))); + } + return values; +} + +void BM_DivideIntrinsic128UniformDivisor(benchmark::State& state) { + auto values = GetRandomIntrinsic128SampleUniformDivisor(); + while (state.KeepRunningBatch(values.size())) { + for (const auto& pair : values) { + benchmark::DoNotOptimize(pair.first / pair.second); + } + } +} +BENCHMARK(BM_DivideIntrinsic128UniformDivisor); + +std::vector<std::pair<unsigned __int128, uint64_t>> +GetRandomIntrinsic128SampleSmallDivisor() { + std::vector<std::pair<unsigned __int128, uint64_t>> values; + std::mt19937 random = MakeRandomEngine(); + UniformIntDistribution128 uniform_uint128; + std::uniform_int_distribution<uint64_t> uniform_uint64; + values.reserve(kSampleSize); + for (size_t i = 0; i < kSampleSize; ++i) { + unsigned __int128 a = uniform_uint128(random); + uint64_t b = std::max(uint64_t{2}, uniform_uint64(random)); + values.emplace_back(std::max(a, static_cast<unsigned __int128>(b)), b); + } + return values; +} + +void BM_DivideIntrinsic128SmallDivisor(benchmark::State& state) { + auto values = GetRandomIntrinsic128SampleSmallDivisor(); + while (state.KeepRunningBatch(values.size())) { + for (const auto& pair : values) { + benchmark::DoNotOptimize(pair.first / pair.second); + } + } +} +BENCHMARK(BM_DivideIntrinsic128SmallDivisor); + +std::vector<std::pair<unsigned __int128, unsigned __int128>> + GetRandomIntrinsic128Sample() { + std::vector<std::pair<unsigned __int128, unsigned __int128>> values; + std::mt19937 random = MakeRandomEngine(); + UniformIntDistribution128 uniform_uint128; + values.reserve(kSampleSize); + for (size_t i = 0; i < kSampleSize; ++i) { + values.emplace_back(uniform_uint128(random), uniform_uint128(random)); + } + return values; +} + +void BM_MultiplyIntrinsic128(benchmark::State& state) { + auto values = GetRandomIntrinsic128Sample(); + while (state.KeepRunningBatch(values.size())) { + for (const auto& pair : values) { + benchmark::DoNotOptimize(pair.first * pair.second); + } + } +} +BENCHMARK(BM_MultiplyIntrinsic128); + +void BM_AddIntrinsic128(benchmark::State& state) { + auto values = GetRandomIntrinsic128Sample(); + while (state.KeepRunningBatch(values.size())) { + for (const auto& pair : values) { + benchmark::DoNotOptimize(pair.first + pair.second); + } + } +} +BENCHMARK(BM_AddIntrinsic128); + +#endif // ABSL_HAVE_INTRINSIC_INT128 + +} // namespace diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index d1c6878c41e4..f06bdc0d01cf 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -158,6 +158,22 @@ cc_test( ) cc_test( + name = "memutil_benchmark", + srcs = [ + "internal/memutil.h", + "internal/memutil_benchmark.cc", + ], + copts = ABSL_TEST_COPTS, + tags = ["benchmark"], + visibility = ["//visibility:private"], + deps = [ + ":strings", + "//absl/base:core_headers", + "@com_github_google_benchmark//:benchmark_main", + ], +) + +cc_test( name = "memutil_test", size = "small", srcs = [ diff --git a/absl/strings/internal/memutil_benchmark.cc b/absl/strings/internal/memutil_benchmark.cc new file mode 100644 index 000000000000..77915adb958e --- /dev/null +++ b/absl/strings/internal/memutil_benchmark.cc @@ -0,0 +1,323 @@ +// Copyright 2018 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/memutil.h" + +#include <algorithm> +#include <cstdlib> + +#include "benchmark/benchmark.h" +#include "absl/strings/ascii.h" + +// We fill the haystack with aaaaaaaaaaaaaaaaaa...aaaab. +// That gives us: +// - an easy search: 'b' +// - a medium search: 'ab'. That means every letter is a possible match. +// - a pathological search: 'aaaaaa.......aaaaab' (half as many a's as haytack) +// We benchmark case-sensitive and case-insensitive versions of +// three memmem implementations: +// - memmem() from memutil.h +// - search() from STL +// - memmatch(), a custom implementation using memchr and memcmp. +// Here are sample results: +// +// Run on (12 X 3800 MHz CPU s) +// CPU Caches: +// L1 Data 32K (x6) +// L1 Instruction 32K (x6) +// L2 Unified 256K (x6) +// L3 Unified 15360K (x1) +// ---------------------------------------------------------------- +// Benchmark Time CPU Iterations +// ---------------------------------------------------------------- +// BM_Memmem 3583 ns 3582 ns 196469 2.59966GB/s +// BM_MemmemMedium 13743 ns 13742 ns 50901 693.986MB/s +// BM_MemmemPathological 13695030 ns 13693977 ns 51 713.133kB/s +// BM_Memcasemem 3299 ns 3299 ns 212942 2.82309GB/s +// BM_MemcasememMedium 16407 ns 16406 ns 42170 581.309MB/s +// BM_MemcasememPathological 17267745 ns 17266030 ns 41 565.598kB/s +// BM_Search 1610 ns 1609 ns 431321 5.78672GB/s +// BM_SearchMedium 11111 ns 11110 ns 63001 858.414MB/s +// BM_SearchPathological 12117390 ns 12116397 ns 58 805.984kB/s +// BM_Searchcase 3081 ns 3081 ns 229949 3.02313GB/s +// BM_SearchcaseMedium 16003 ns 16001 ns 44170 595.998MB/s +// BM_SearchcasePathological 15823413 ns 15821909 ns 44 617.222kB/s +// BM_Memmatch 197 ns 197 ns 3584225 47.2951GB/s +// BM_MemmatchMedium 52333 ns 52329 ns 13280 182.244MB/s +// BM_MemmatchPathological 659799 ns 659727 ns 1058 14.4556MB/s +// BM_Memcasematch 5460 ns 5460 ns 127606 1.70586GB/s +// BM_MemcasematchMedium 32861 ns 32857 ns 21258 290.248MB/s +// BM_MemcasematchPathological 15154243 ns 15153089 ns 46 644.464kB/s +// BM_MemmemStartup 5 ns 5 ns 150821500 +// BM_SearchStartup 5 ns 5 ns 150644203 +// BM_MemmatchStartup 7 ns 7 ns 97068802 +// +// Conclusions: +// +// The following recommendations are based on the sample results above. However, +// we have found that the performance of STL search can vary significantly +// depending on compiler and standard library implementation. We recommend you +// run the benchmarks for yourself on relevant platforms. +// +// If you need case-insensitive, STL search is slightly better than memmem for +// all cases. +// +// Case-sensitive is more subtle: +// Custom memmatch is _very_ fast at scanning, so if you have very few possible +// matches in your haystack, that's the way to go. Performance drops +// significantly with more matches. +// +// STL search is slightly faster than memmem in the medium and pathological +// benchmarks. However, the performance of memmem is currently more dependable +// across platforms and build configurations. + +namespace { + +constexpr int kHaystackSize = 10000; +constexpr int64_t kHaystackSize64 = kHaystackSize; +const char* MakeHaystack() { + char* haystack = new char[kHaystackSize]; + for (int i = 0; i < kHaystackSize - 1; ++i) haystack[i] = 'a'; + haystack[kHaystackSize - 1] = 'b'; + return haystack; +} +const char* const kHaystack = MakeHaystack(); + +void BM_Memmem(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize( + absl::strings_internal::memmem(kHaystack, kHaystackSize, "b", 1)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_Memmem); + +void BM_MemmemMedium(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize( + absl::strings_internal::memmem(kHaystack, kHaystackSize, "ab", 2)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_MemmemMedium); + +void BM_MemmemPathological(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(absl::strings_internal::memmem( + kHaystack, kHaystackSize, kHaystack + kHaystackSize / 2, + kHaystackSize - kHaystackSize / 2)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_MemmemPathological); + +void BM_Memcasemem(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize( + absl::strings_internal::memcasemem(kHaystack, kHaystackSize, "b", 1)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_Memcasemem); + +void BM_MemcasememMedium(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize( + absl::strings_internal::memcasemem(kHaystack, kHaystackSize, "ab", 2)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_MemcasememMedium); + +void BM_MemcasememPathological(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(absl::strings_internal::memcasemem( + kHaystack, kHaystackSize, kHaystack + kHaystackSize / 2, + kHaystackSize - kHaystackSize / 2)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_MemcasememPathological); + +bool case_eq(const char a, const char b) { + return absl::ascii_tolower(a) == absl::ascii_tolower(b); +} + +void BM_Search(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize, + kHaystack + kHaystackSize - 1, + kHaystack + kHaystackSize)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_Search); + +void BM_SearchMedium(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize, + kHaystack + kHaystackSize - 2, + kHaystack + kHaystackSize)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_SearchMedium); + +void BM_SearchPathological(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize, + kHaystack + kHaystackSize / 2, + kHaystack + kHaystackSize)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_SearchPathological); + +void BM_Searchcase(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize, + kHaystack + kHaystackSize - 1, + kHaystack + kHaystackSize, case_eq)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_Searchcase); + +void BM_SearchcaseMedium(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize, + kHaystack + kHaystackSize - 2, + kHaystack + kHaystackSize, case_eq)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_SearchcaseMedium); + +void BM_SearchcasePathological(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize, + kHaystack + kHaystackSize / 2, + kHaystack + kHaystackSize, case_eq)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_SearchcasePathological); + +char* memcasechr(const char* s, int c, size_t slen) { + c = absl::ascii_tolower(c); + for (; slen; ++s, --slen) { + if (absl::ascii_tolower(*s) == c) return const_cast<char*>(s); + } + return nullptr; +} + +const char* memcasematch(const char* phaystack, size_t haylen, + const char* pneedle, size_t neelen) { + if (0 == neelen) { + return phaystack; // even if haylen is 0 + } + if (haylen < neelen) return nullptr; + + const char* match; + const char* hayend = phaystack + haylen - neelen + 1; + while ((match = static_cast<char*>( + memcasechr(phaystack, pneedle[0], hayend - phaystack)))) { + if (absl::strings_internal::memcasecmp(match, pneedle, neelen) == 0) + return match; + else + phaystack = match + 1; + } + return nullptr; +} + +void BM_Memmatch(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize( + absl::strings_internal::memmatch(kHaystack, kHaystackSize, "b", 1)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_Memmatch); + +void BM_MemmatchMedium(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize( + absl::strings_internal::memmatch(kHaystack, kHaystackSize, "ab", 2)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_MemmatchMedium); + +void BM_MemmatchPathological(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(absl::strings_internal::memmatch( + kHaystack, kHaystackSize, kHaystack + kHaystackSize / 2, + kHaystackSize - kHaystackSize / 2)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_MemmatchPathological); + +void BM_Memcasematch(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(memcasematch(kHaystack, kHaystackSize, "b", 1)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_Memcasematch); + +void BM_MemcasematchMedium(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(memcasematch(kHaystack, kHaystackSize, "ab", 2)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_MemcasematchMedium); + +void BM_MemcasematchPathological(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(memcasematch(kHaystack, kHaystackSize, + kHaystack + kHaystackSize / 2, + kHaystackSize - kHaystackSize / 2)); + } + state.SetBytesProcessed(kHaystackSize64 * state.iterations()); +} +BENCHMARK(BM_MemcasematchPathological); + +void BM_MemmemStartup(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(absl::strings_internal::memmem( + kHaystack + kHaystackSize - 10, 10, kHaystack + kHaystackSize - 1, 1)); + } +} +BENCHMARK(BM_MemmemStartup); + +void BM_SearchStartup(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize( + std::search(kHaystack + kHaystackSize - 10, kHaystack + kHaystackSize, + kHaystack + kHaystackSize - 1, kHaystack + kHaystackSize)); + } +} +BENCHMARK(BM_SearchStartup); + +void BM_MemmatchStartup(benchmark::State& state) { + for (auto _ : state) { + benchmark::DoNotOptimize(absl::strings_internal::memmatch( + kHaystack + kHaystackSize - 10, 10, kHaystack + kHaystackSize - 1, 1)); + } +} +BENCHMARK(BM_MemmatchStartup); + +} // namespace diff --git a/absl/time/BUILD.bazel b/absl/time/BUILD.bazel index 64cb99f73716..fe55fe1f9bf6 100644 --- a/absl/time/BUILD.bazel +++ b/absl/time/BUILD.bazel @@ -93,3 +93,23 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "time_benchmark", + srcs = [ + "clock_benchmark.cc", + "duration_benchmark.cc", + "format_benchmark.cc", + "time_benchmark.cc", + ], + copts = ABSL_TEST_COPTS, + tags = [ + "benchmark", + ], + deps = [ + ":test_util", + ":time", + "//absl/base", + "@com_github_google_benchmark//:benchmark_main", + ], +) diff --git a/absl/time/clock_benchmark.cc b/absl/time/clock_benchmark.cc new file mode 100644 index 000000000000..3d3cd9d5c42c --- /dev/null +++ b/absl/time/clock_benchmark.cc @@ -0,0 +1,72 @@ +// Copyright 2018 The Abseil Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/time/clock.h" + +#if !defined(_WIN32) +#include <sys/time.h> +#endif // _WIN32 +#include <cstdio> + +#include "absl/base/internal/cycleclock.h" +#include "benchmark/benchmark.h" + +namespace { + +void BM_Clock_Now_AbslTime(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::Now()); + } +} +BENCHMARK(BM_Clock_Now_AbslTime); + +void BM_Clock_Now_GetCurrentTimeNanos(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::GetCurrentTimeNanos()); + } +} +BENCHMARK(BM_Clock_Now_GetCurrentTimeNanos); + +void BM_Clock_Now_AbslTime_ToUnixNanos(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::ToUnixNanos(absl::Now())); + } +} +BENCHMARK(BM_Clock_Now_AbslTime_ToUnixNanos); + +void BM_Clock_Now_CycleClock(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::base_internal::CycleClock::Now()); + } +} +BENCHMARK(BM_Clock_Now_CycleClock); + +#if !defined(_WIN32) +static void BM_Clock_Now_gettimeofday(benchmark::State& state) { + struct timeval tv; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(gettimeofday(&tv, nullptr)); + } +} +BENCHMARK(BM_Clock_Now_gettimeofday); + +static void BM_Clock_Now_clock_gettime(benchmark::State& state) { + struct timespec ts; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(clock_gettime(CLOCK_REALTIME, &ts)); + } +} +BENCHMARK(BM_Clock_Now_clock_gettime); +#endif // _WIN32 + +} // namespace diff --git a/absl/time/duration_benchmark.cc b/absl/time/duration_benchmark.cc new file mode 100644 index 000000000000..54f89a1f000d --- /dev/null +++ b/absl/time/duration_benchmark.cc @@ -0,0 +1,361 @@ +// Copyright 2018 The Abseil Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <cmath> +#include <cstddef> +#include <cstdint> +#include <ctime> +#include <string> + +#include "absl/time/time.h" +#include "benchmark/benchmark.h" + +namespace { + +// +// Factory functions +// + +void BM_Duration_Factory_Nanoseconds(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::Nanoseconds(1)); + } +} +BENCHMARK(BM_Duration_Factory_Nanoseconds); + +void BM_Duration_Factory_Microseconds(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::Microseconds(1)); + } +} +BENCHMARK(BM_Duration_Factory_Microseconds); + +void BM_Duration_Factory_Milliseconds(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::Milliseconds(1)); + } +} +BENCHMARK(BM_Duration_Factory_Milliseconds); + +void BM_Duration_Factory_Seconds(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::Seconds(1)); + } +} +BENCHMARK(BM_Duration_Factory_Seconds); + +void BM_Duration_Factory_Minutes(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::Minutes(1)); + } +} +BENCHMARK(BM_Duration_Factory_Minutes); + +void BM_Duration_Factory_Hours(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::Hours(1)); + } +} +BENCHMARK(BM_Duration_Factory_Hours); + +// +// Arithmetic +// + +void BM_Duration_Addition(benchmark::State& state) { + absl::Duration d = absl::Nanoseconds(1); + absl::Duration step = absl::Milliseconds(1); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d += step); + } +} +BENCHMARK(BM_Duration_Addition); + +void BM_Duration_Subtraction(benchmark::State& state) { + absl::Duration d = absl::Seconds(std::numeric_limits<int64_t>::max()); + absl::Duration step = absl::Milliseconds(1); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d -= step); + } +} +BENCHMARK(BM_Duration_Subtraction); + +void BM_Duration_Multiplication_Fixed(benchmark::State& state) { + absl::Duration d = absl::Milliseconds(1); + absl::Duration s; + int i = 0; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(s += d * (i + 1)); + ++i; + } +} +BENCHMARK(BM_Duration_Multiplication_Fixed); + +void BM_Duration_Multiplication_Double(benchmark::State& state) { + absl::Duration d = absl::Milliseconds(1); + absl::Duration s; + int i = 0; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(s += d * (i + 1.0)); + ++i; + } +} +BENCHMARK(BM_Duration_Multiplication_Double); + +void BM_Duration_Division_Fixed(benchmark::State& state) { + absl::Duration d = absl::Seconds(1); + int i = 0; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d /= i + 1); + ++i; + } +} +BENCHMARK(BM_Duration_Division_Fixed); + +void BM_Duration_Division_Double(benchmark::State& state) { + absl::Duration d = absl::Seconds(1); + int i = 0; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d /= i + 1.0); + ++i; + } +} +BENCHMARK(BM_Duration_Division_Double); + +void BM_Duration_FDivDuration_Nanoseconds(benchmark::State& state) { + double d = 1; + int i = 0; + while (state.KeepRunning()) { + benchmark::DoNotOptimize( + d += absl::FDivDuration(absl::Milliseconds(i), absl::Nanoseconds(1))); + ++i; + } +} +BENCHMARK(BM_Duration_FDivDuration_Nanoseconds); + +void BM_Duration_IDivDuration_Nanoseconds(benchmark::State& state) { + int64_t a = 1; + absl::Duration ignore; + int i = 0; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(a += + absl::IDivDuration(absl::Nanoseconds(i), + absl::Nanoseconds(1), &ignore)); + ++i; + } +} +BENCHMARK(BM_Duration_IDivDuration_Nanoseconds); + +void BM_Duration_IDivDuration_Microseconds(benchmark::State& state) { + int64_t a = 1; + absl::Duration ignore; + int i = 0; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(a += absl::IDivDuration(absl::Microseconds(i), + absl::Microseconds(1), + &ignore)); + ++i; + } +} +BENCHMARK(BM_Duration_IDivDuration_Microseconds); + +void BM_Duration_IDivDuration_Milliseconds(benchmark::State& state) { + int64_t a = 1; + absl::Duration ignore; + int i = 0; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(a += absl::IDivDuration(absl::Milliseconds(i), + absl::Milliseconds(1), + &ignore)); + ++i; + } +} +BENCHMARK(BM_Duration_IDivDuration_Milliseconds); + +void BM_Duration_IDivDuration_Seconds(benchmark::State& state) { + int64_t a = 1; + absl::Duration ignore; + int i = 0; + while (state.KeepRunning()) { + benchmark::DoNotOptimize( + a += absl::IDivDuration(absl::Seconds(i), absl::Seconds(1), &ignore)); + ++i; + } +} +BENCHMARK(BM_Duration_IDivDuration_Seconds); + +void BM_Duration_IDivDuration_Minutes(benchmark::State& state) { + int64_t a = 1; + absl::Duration ignore; + int i = 0; + while (state.KeepRunning()) { + benchmark::DoNotOptimize( + a += absl::IDivDuration(absl::Minutes(i), absl::Minutes(1), &ignore)); + ++i; + } +} +BENCHMARK(BM_Duration_IDivDuration_Minutes); + +void BM_Duration_IDivDuration_Hours(benchmark::State& state) { + int64_t a = 1; + absl::Duration ignore; + int i = 0; + while (state.KeepRunning()) { + benchmark::DoNotOptimize( + a += absl::IDivDuration(absl::Hours(i), absl::Hours(1), &ignore)); + ++i; + } +} +BENCHMARK(BM_Duration_IDivDuration_Hours); + +void BM_Duration_ToInt64Nanoseconds(benchmark::State& state) { + absl::Duration d = absl::Seconds(100000); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::ToInt64Nanoseconds(d)); + } +} +BENCHMARK(BM_Duration_ToInt64Nanoseconds); + +void BM_Duration_ToInt64Microseconds(benchmark::State& state) { + absl::Duration d = absl::Seconds(100000); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::ToInt64Microseconds(d)); + } +} +BENCHMARK(BM_Duration_ToInt64Microseconds); + +void BM_Duration_ToInt64Milliseconds(benchmark::State& state) { + absl::Duration d = absl::Seconds(100000); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::ToInt64Milliseconds(d)); + } +} +BENCHMARK(BM_Duration_ToInt64Milliseconds); + +void BM_Duration_ToInt64Seconds(benchmark::State& state) { + absl::Duration d = absl::Seconds(100000); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::ToInt64Seconds(d)); + } +} +BENCHMARK(BM_Duration_ToInt64Seconds); + +void BM_Duration_ToInt64Minutes(benchmark::State& state) { + absl::Duration d = absl::Seconds(100000); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::ToInt64Minutes(d)); + } +} +BENCHMARK(BM_Duration_ToInt64Minutes); + +void BM_Duration_ToInt64Hours(benchmark::State& state) { + absl::Duration d = absl::Seconds(100000); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::ToInt64Hours(d)); + } +} +BENCHMARK(BM_Duration_ToInt64Hours); + +// +// To/FromTimespec +// + +void BM_Duration_ToTimespec_AbslTime(benchmark::State& state) { + absl::Duration d = absl::Seconds(1); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::ToTimespec(d)); + } +} +BENCHMARK(BM_Duration_ToTimespec_AbslTime); + +ABSL_ATTRIBUTE_NOINLINE timespec DoubleToTimespec(double seconds) { + timespec ts; + ts.tv_sec = seconds; + ts.tv_nsec = (seconds - ts.tv_sec) * (1000 * 1000 * 1000); + return ts; +} + +void BM_Duration_ToTimespec_Double(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize(DoubleToTimespec(1.0)); + } +} +BENCHMARK(BM_Duration_ToTimespec_Double); + +void BM_Duration_FromTimespec_AbslTime(benchmark::State& state) { + timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 0; + while (state.KeepRunning()) { + if (++ts.tv_nsec == 1000 * 1000 * 1000) { + ++ts.tv_sec; + ts.tv_nsec = 0; + } + benchmark::DoNotOptimize(absl::DurationFromTimespec(ts)); + } +} +BENCHMARK(BM_Duration_FromTimespec_AbslTime); + +ABSL_ATTRIBUTE_NOINLINE double TimespecToDouble(timespec ts) { + return ts.tv_sec + (ts.tv_nsec / (1000 * 1000 * 1000)); +} + +void BM_Duration_FromTimespec_Double(benchmark::State& state) { + timespec ts; + ts.tv_sec = 0; + ts.tv_nsec = 0; + while (state.KeepRunning()) { + if (++ts.tv_nsec == 1000 * 1000 * 1000) { + ++ts.tv_sec; + ts.tv_nsec = 0; + } + benchmark::DoNotOptimize(TimespecToDouble(ts)); + } +} +BENCHMARK(BM_Duration_FromTimespec_Double); + +// +// String conversions +// + +const char* const kDurations[] = { + "0", // 0 + "123ns", // 1 + "1h2m3s", // 2 + "-2h3m4.005006007s", // 3 + "2562047788015215h30m7.99999999975s", // 4 +}; +const int kNumDurations = sizeof(kDurations) / sizeof(kDurations[0]); + +void BM_Duration_FormatDuration(benchmark::State& state) { + const std::string s = kDurations[state.range(0)]; + state.SetLabel(s); + absl::Duration d; + absl::ParseDuration(kDurations[state.range(0)], &d); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::FormatDuration(d)); + } +} +BENCHMARK(BM_Duration_FormatDuration)->DenseRange(0, kNumDurations - 1); + +void BM_Duration_ParseDuration(benchmark::State& state) { + const std::string s = kDurations[state.range(0)]; + state.SetLabel(s); + absl::Duration d; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::ParseDuration(s, &d)); + } +} +BENCHMARK(BM_Duration_ParseDuration)->DenseRange(0, kNumDurations - 1); + +} // namespace diff --git a/absl/time/duration_test.cc b/absl/time/duration_test.cc index 7918e1677052..704684edb34e 100644 --- a/absl/time/duration_test.cc +++ b/absl/time/duration_test.cc @@ -330,18 +330,10 @@ TEST(Duration, ToChrono) { EXPECT_EQ(hours::max(), absl::ToChronoHours(inf)); } -// Used for testing the factory overloads. -template <typename T> -struct ImplicitlyConvertible { - T n_; - explicit ImplicitlyConvertible(T n) : n_(n) {} - // Marking this conversion operator with 'explicit' will cause the test to - // fail (as desired). - operator T() { return n_; } -}; - TEST(Duration, FactoryOverloads) { + enum E { kOne = 1 }; #define TEST_FACTORY_OVERLOADS(NAME) \ + EXPECT_EQ(1, NAME(kOne) / NAME(kOne)); \ EXPECT_EQ(1, NAME(static_cast<int8_t>(1)) / NAME(1)); \ EXPECT_EQ(1, NAME(static_cast<int16_t>(1)) / NAME(1)); \ EXPECT_EQ(1, NAME(static_cast<int32_t>(1)) / NAME(1)); \ @@ -350,14 +342,6 @@ TEST(Duration, FactoryOverloads) { EXPECT_EQ(1, NAME(static_cast<uint16_t>(1)) / NAME(1)); \ EXPECT_EQ(1, NAME(static_cast<uint32_t>(1)) / NAME(1)); \ EXPECT_EQ(1, NAME(static_cast<uint64_t>(1)) / NAME(1)); \ - EXPECT_EQ(1, NAME(ImplicitlyConvertible<int8_t>(1)) / NAME(1)); \ - EXPECT_EQ(1, NAME(ImplicitlyConvertible<int16_t>(1)) / NAME(1)); \ - EXPECT_EQ(1, NAME(ImplicitlyConvertible<int32_t>(1)) / NAME(1)); \ - EXPECT_EQ(1, NAME(ImplicitlyConvertible<int64_t>(1)) / NAME(1)); \ - EXPECT_EQ(1, NAME(ImplicitlyConvertible<uint8_t>(1)) / NAME(1)); \ - EXPECT_EQ(1, NAME(ImplicitlyConvertible<uint16_t>(1)) / NAME(1)); \ - EXPECT_EQ(1, NAME(ImplicitlyConvertible<uint32_t>(1)) / NAME(1)); \ - EXPECT_EQ(1, NAME(ImplicitlyConvertible<uint64_t>(1)) / NAME(1)); \ EXPECT_EQ(NAME(1) / 2, NAME(static_cast<float>(0.5))); \ EXPECT_EQ(NAME(1) / 2, NAME(static_cast<double>(0.5))); \ EXPECT_EQ(1.5, absl::FDivDuration(NAME(static_cast<float>(1.5)), NAME(1))); \ diff --git a/absl/time/format_benchmark.cc b/absl/time/format_benchmark.cc new file mode 100644 index 000000000000..ee53d71c6854 --- /dev/null +++ b/absl/time/format_benchmark.cc @@ -0,0 +1,63 @@ +// Copyright 2018 The Abseil Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <cstddef> +#include <string> + +#include "absl/time/internal/test_util.h" +#include "absl/time/time.h" +#include "benchmark/benchmark.h" + +namespace { + +namespace { +const char* const kFormats[] = { + absl::RFC1123_full, // 0 + absl::RFC1123_no_wday, // 1 + absl::RFC3339_full, // 2 + absl::RFC3339_sec, // 3 + "%Y-%m-%dT%H:%M:%S", // 4 + "%Y-%m-%d", // 5 +}; +const int kNumFormats = sizeof(kFormats) / sizeof(kFormats[0]); +} // namespace + +void BM_Format_FormatTime(benchmark::State& state) { + const std::string fmt = kFormats[state.range(0)]; + state.SetLabel(fmt); + const absl::TimeZone lax = + absl::time_internal::LoadTimeZone("America/Los_Angeles"); + const absl::Time t = + absl::FromDateTime(1977, 6, 28, 9, 8, 7, lax) + absl::Nanoseconds(1); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::FormatTime(fmt, t, lax).length()); + } +} +BENCHMARK(BM_Format_FormatTime)->DenseRange(0, kNumFormats - 1); + +void BM_Format_ParseTime(benchmark::State& state) { + const std::string fmt = kFormats[state.range(0)]; + state.SetLabel(fmt); + const absl::TimeZone lax = + absl::time_internal::LoadTimeZone("America/Los_Angeles"); + absl::Time t = + absl::FromDateTime(1977, 6, 28, 9, 8, 7, lax) + absl::Nanoseconds(1); + const std::string when = absl::FormatTime(fmt, t, lax); + std::string err; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::ParseTime(fmt, when, lax, &t, &err)); + } +} +BENCHMARK(BM_Format_ParseTime)->DenseRange(0, kNumFormats - 1); + +} // namespace diff --git a/absl/time/internal/cctz/src/time_zone_format_test.cc b/absl/time/internal/cctz/src/time_zone_format_test.cc index 3a5f19ac8b1f..7d5b02ad3a68 100644 --- a/absl/time/internal/cctz/src/time_zone_format_test.cc +++ b/absl/time/internal/cctz/src/time_zone_format_test.cc @@ -463,13 +463,12 @@ TEST(Format, ExtendedSecondOffset) { EXPECT_TRUE(load_time_zone("Europe/Moscow", &tz)); tp = convert(civil_second(1919, 6, 30, 23, 59, 59), utc); - if (tz.lookup(tp).offset == 4 * 60 * 60) { - // We're likely dealing with zoneinfo that doesn't support really old - // timestamps, so Europe/Moscow never looks to be on local mean time. - } else { - TestFormatSpecifier(tp, tz, "%E*z", "+04:31:19"); - TestFormatSpecifier(tp, tz, "%Ez", "+04:31"); - } +#if defined(__ANDROID__) && __ANDROID_API__ < 25 + // Only Android 'N'.1 and beyond have this tz2016g transition. +#else + TestFormatSpecifier(tp, tz, "%E*z", "+04:31:19"); + TestFormatSpecifier(tp, tz, "%Ez", "+04:31"); +#endif tp += seconds(1); TestFormatSpecifier(tp, tz, "%E*z", "+04:00:00"); } diff --git a/absl/time/internal/cctz/src/time_zone_lookup_test.cc b/absl/time/internal/cctz/src/time_zone_lookup_test.cc index f97eab0227eb..06b172a80323 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc @@ -693,7 +693,14 @@ TEST(TimeZones, LoadZonesConcurrently) { // Allow a small number of failures to account for skew between // the contents of kTimeZoneNames and the zoneinfo data source. +#if defined(__ANDROID__) + // Cater to the possibility of using an even older zoneinfo data + // source when running on Android, where it is difficult to override + // the bionic tzdata provided by the test environment. + const std::size_t max_failures = 20; +#else const std::size_t max_failures = 3; +#endif std::set<std::string> failures; for (const auto& thread_failure : thread_failures) { failures.insert(thread_failure.begin(), thread_failure.end()); @@ -839,7 +846,7 @@ TEST(TimeZoneImpl, LocalTimeInFixed) { const time_zone tz = fixed_time_zone(offset); const auto tp = system_clock::from_time_t(0); ExpectTime(tp, tz, 1969, 12, 31, 15, 26, 13, offset.count(), false, - "UTC-083347"); + "-083347"); EXPECT_EQ(weekday::wednesday, get_weekday(civil_day(convert(tp, tz)))); } @@ -1098,6 +1105,9 @@ TEST(TimeZoneEdgeCase, PacificApia) { TEST(TimeZoneEdgeCase, AfricaCairo) { const time_zone tz = LoadZone("Africa/Cairo"); +#if defined(__ANDROID__) && __ANDROID_API__ < 21 + // Only Android 'L' and beyond have this tz2014c transition. +#else // An interesting case of midnight not existing. // // 1400191199 == Thu, 15 May 2014 23:59:59 +0200 (EET) @@ -1106,11 +1116,15 @@ TEST(TimeZoneEdgeCase, AfricaCairo) { ExpectTime(tp, tz, 2014, 5, 15, 23, 59, 59, 2 * 3600, false, "EET"); tp += seconds(1); ExpectTime(tp, tz, 2014, 5, 16, 1, 0, 0, 3 * 3600, true, "EEST"); +#endif } TEST(TimeZoneEdgeCase, AfricaMonrovia) { const time_zone tz = LoadZone("Africa/Monrovia"); +#if defined(__ANDROID__) && __ANDROID_API__ < 26 + // Only Android 'O' and beyond have this tz2017b transition. +#else // Strange offset change -00:44:30 -> +00:00:00 (non-DST) // // 63593069 == Thu, 6 Jan 1972 23:59:59 -0044 (MMT) @@ -1119,6 +1133,7 @@ TEST(TimeZoneEdgeCase, AfricaMonrovia) { ExpectTime(tp, tz, 1972, 1, 6, 23, 59, 59, -44.5 * 60, false, "MMT"); tp += seconds(1); ExpectTime(tp, tz, 1972, 1, 7, 0, 44, 30, 0 * 60, false, "GMT"); +#endif } TEST(TimeZoneEdgeCase, AmericaJamaica) { diff --git a/absl/time/time.h b/absl/time/time.h index c50d69a5689f..99c12bbd87ed 100644 --- a/absl/time/time.h +++ b/absl/time/time.h @@ -82,8 +82,15 @@ constexpr Duration MakeDuration(int64_t hi, uint32_t lo); constexpr Duration MakeDuration(int64_t hi, int64_t lo); constexpr int64_t kTicksPerNanosecond = 4; constexpr int64_t kTicksPerSecond = 1000 * 1000 * 1000 * kTicksPerNanosecond; +template <std::intmax_t N> +constexpr Duration FromInt64(int64_t v, std::ratio<1, N>); +constexpr Duration FromInt64(int64_t v, std::ratio<60>); +constexpr Duration FromInt64(int64_t v, std::ratio<3600>); +template <typename T> +using EnableIfIntegral = typename std::enable_if< + std::is_integral<T>::value || std::is_enum<T>::value, int>::type; template <typename T> -using IsFloatingPoint = +using EnableIfFloat = typename std::enable_if<std::is_floating_point<T>::value, int>::type; } // namespace time_internal @@ -178,15 +185,15 @@ inline Duration operator-(Duration lhs, Duration rhs) { return lhs -= rhs; } // Multiplicative Operators template <typename T> -inline Duration operator*(Duration lhs, T rhs) { +Duration operator*(Duration lhs, T rhs) { return lhs *= rhs; } template <typename T> -inline Duration operator*(T lhs, Duration rhs) { +Duration operator*(T lhs, Duration rhs) { return rhs *= lhs; } template <typename T> -inline Duration operator/(Duration lhs, T rhs) { +Duration operator/(Duration lhs, T rhs) { return lhs /= rhs; } inline int64_t operator/(Duration lhs, Duration rhs) { @@ -322,27 +329,27 @@ constexpr Duration Hours(int64_t n); // Example: // auto a = absl::Seconds(1.5); // OK // auto b = absl::Milliseconds(1500); // BETTER -template <typename T, time_internal::IsFloatingPoint<T> = 0> +template <typename T, time_internal::EnableIfFloat<T> = 0> Duration Nanoseconds(T n) { return n * Nanoseconds(1); } -template <typename T, time_internal::IsFloatingPoint<T> = 0> +template <typename T, time_internal::EnableIfFloat<T> = 0> Duration Microseconds(T n) { return n * Microseconds(1); } -template <typename T, time_internal::IsFloatingPoint<T> = 0> +template <typename T, time_internal::EnableIfFloat<T> = 0> Duration Milliseconds(T n) { return n * Milliseconds(1); } -template <typename T, time_internal::IsFloatingPoint<T> = 0> +template <typename T, time_internal::EnableIfFloat<T> = 0> Duration Seconds(T n) { return n * Seconds(1); } -template <typename T, time_internal::IsFloatingPoint<T> = 0> +template <typename T, time_internal::EnableIfFloat<T> = 0> Duration Minutes(T n) { return n * Minutes(1); } -template <typename T, time_internal::IsFloatingPoint<T> = 0> +template <typename T, time_internal::EnableIfFloat<T> = 0> Duration Hours(T n) { return n * Hours(1); } @@ -1154,10 +1161,16 @@ constexpr Duration FromInt64(int64_t v, std::ratio<1, N>) { v / N, v % N * kTicksPerNanosecond * 1000 * 1000 * 1000 / N); } constexpr Duration FromInt64(int64_t v, std::ratio<60>) { - return Minutes(v); + return (v <= std::numeric_limits<int64_t>::max() / 60 && + v >= std::numeric_limits<int64_t>::min() / 60) + ? MakeDuration(v * 60) + : v > 0 ? InfiniteDuration() : -InfiniteDuration(); } constexpr Duration FromInt64(int64_t v, std::ratio<3600>) { - return Hours(v); + return (v <= std::numeric_limits<int64_t>::max() / 3600 && + v >= std::numeric_limits<int64_t>::min() / 3600) + ? MakeDuration(v * 3600) + : v > 0 ? InfiniteDuration() : -InfiniteDuration(); } // IsValidRep64<T>(0) is true if the expression `int64_t{std::declval<T>()}` is @@ -1220,6 +1233,24 @@ T ToChronoDuration(Duration d) { } } // namespace time_internal +constexpr Duration Nanoseconds(int64_t n) { + return time_internal::FromInt64(n, std::nano{}); +} +constexpr Duration Microseconds(int64_t n) { + return time_internal::FromInt64(n, std::micro{}); +} +constexpr Duration Milliseconds(int64_t n) { + return time_internal::FromInt64(n, std::milli{}); +} +constexpr Duration Seconds(int64_t n) { + return time_internal::FromInt64(n, std::ratio<1>{}); +} +constexpr Duration Minutes(int64_t n) { + return time_internal::FromInt64(n, std::ratio<60>{}); +} +constexpr Duration Hours(int64_t n) { + return time_internal::FromInt64(n, std::ratio<3600>{}); +} constexpr bool operator<(Duration lhs, Duration rhs) { return time_internal::GetRepHi(lhs) != time_internal::GetRepHi(rhs) @@ -1261,39 +1292,6 @@ constexpr Duration operator-(Duration d) { time_internal::GetRepLo(d)); } -constexpr Duration Nanoseconds(int64_t n) { - return time_internal::MakeNormalizedDuration( - n / (1000 * 1000 * 1000), - n % (1000 * 1000 * 1000) * time_internal::kTicksPerNanosecond); -} - -constexpr Duration Microseconds(int64_t n) { - return time_internal::MakeNormalizedDuration( - n / (1000 * 1000), - n % (1000 * 1000) * (1000 * time_internal::kTicksPerNanosecond)); -} - -constexpr Duration Milliseconds(int64_t n) { - return time_internal::MakeNormalizedDuration( - n / 1000, n % 1000 * (1000 * 1000 * time_internal::kTicksPerNanosecond)); -} - -constexpr Duration Seconds(int64_t n) { return time_internal::MakeDuration(n); } - -constexpr Duration Minutes(int64_t n) { - return (n <= std::numeric_limits<int64_t>::max() / 60 && - n >= std::numeric_limits<int64_t>::min() / 60) - ? time_internal::MakeDuration(n * 60) - : n > 0 ? InfiniteDuration() : -InfiniteDuration(); -} - -constexpr Duration Hours(int64_t n) { - return (n <= std::numeric_limits<int64_t>::max() / 3600 && - n >= std::numeric_limits<int64_t>::min() / 3600) - ? time_internal::MakeDuration(n * 3600) - : n > 0 ? InfiniteDuration() : -InfiniteDuration(); -} - constexpr Duration InfiniteDuration() { return time_internal::MakeDuration(std::numeric_limits<int64_t>::max(), ~0U); } diff --git a/absl/time/time_benchmark.cc b/absl/time/time_benchmark.cc new file mode 100644 index 000000000000..e1009946cd58 --- /dev/null +++ b/absl/time/time_benchmark.cc @@ -0,0 +1,316 @@ +// Copyright 2018 The Abseil Authors. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/time/time.h" + +#if !defined(_WIN32) +#include <sys/time.h> +#endif // _WIN32 +#include <algorithm> +#include <cmath> +#include <cstddef> +#include <cstring> +#include <ctime> +#include <memory> +#include <string> + +#include "absl/time/clock.h" +#include "absl/time/internal/test_util.h" +#include "benchmark/benchmark.h" + +namespace { + +// +// Addition/Subtraction of a duration +// + +void BM_Time_Arithmetic(benchmark::State& state) { + const absl::Duration nano = absl::Nanoseconds(1); + const absl::Duration sec = absl::Seconds(1); + absl::Time t = absl::UnixEpoch(); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(t += nano); + benchmark::DoNotOptimize(t -= sec); + } +} +BENCHMARK(BM_Time_Arithmetic); + +// +// Time difference +// + +void BM_Time_Difference(benchmark::State& state) { + absl::Time start = absl::Now(); + absl::Time end = start + absl::Nanoseconds(1); + absl::Duration diff; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(diff += end - start); + } +} +BENCHMARK(BM_Time_Difference); + +// +// ToDateTime +// +// In each "ToDateTime" benchmark we switch between two instants +// separated by at least one transition in order to defeat any +// internal caching of previous results (e.g., see local_time_hint_). +// +// The "UTC" variants use UTC instead of the Google/local time zone. +// + +void BM_Time_ToDateTime_Absl(benchmark::State& state) { + const absl::TimeZone tz = + absl::time_internal::LoadTimeZone("America/Los_Angeles"); + absl::Time t = absl::FromUnixSeconds(1384569027); + absl::Time t2 = absl::FromUnixSeconds(1418962578); + while (state.KeepRunning()) { + std::swap(t, t2); + t += absl::Seconds(1); + benchmark::DoNotOptimize(t.In(tz)); + } +} +BENCHMARK(BM_Time_ToDateTime_Absl); + +void BM_Time_ToDateTime_Libc(benchmark::State& state) { + // No timezone support, so just use localtime. + time_t t = 1384569027; + time_t t2 = 1418962578; + while (state.KeepRunning()) { + std::swap(t, t2); + t += 1; + struct tm tm; +#if !defined(_WIN32) + benchmark::DoNotOptimize(localtime_r(&t, &tm)); +#else // _WIN32 + benchmark::DoNotOptimize(localtime_s(&tm, &t)); +#endif // _WIN32 + } +} +BENCHMARK(BM_Time_ToDateTime_Libc); + +void BM_Time_ToDateTimeUTC_Absl(benchmark::State& state) { + const absl::TimeZone tz = absl::UTCTimeZone(); + absl::Time t = absl::FromUnixSeconds(1384569027); + while (state.KeepRunning()) { + t += absl::Seconds(1); + benchmark::DoNotOptimize(t.In(tz)); + } +} +BENCHMARK(BM_Time_ToDateTimeUTC_Absl); + +void BM_Time_ToDateTimeUTC_Libc(benchmark::State& state) { + time_t t = 1384569027; + while (state.KeepRunning()) { + t += 1; + struct tm tm; +#if !defined(_WIN32) + benchmark::DoNotOptimize(gmtime_r(&t, &tm)); +#else // _WIN32 + benchmark::DoNotOptimize(gmtime_s(&tm, &t)); +#endif // _WIN32 + } +} +BENCHMARK(BM_Time_ToDateTimeUTC_Libc); + +// +// FromUnixMicros +// + +void BM_Time_FromUnixMicros(benchmark::State& state) { + int i = 0; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::FromUnixMicros(i)); + ++i; + } +} +BENCHMARK(BM_Time_FromUnixMicros); + +void BM_Time_ToUnixNanos(benchmark::State& state) { + const absl::Time t = absl::UnixEpoch() + absl::Seconds(123); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(ToUnixNanos(t)); + } +} +BENCHMARK(BM_Time_ToUnixNanos); + +void BM_Time_ToUnixMicros(benchmark::State& state) { + const absl::Time t = absl::UnixEpoch() + absl::Seconds(123); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(ToUnixMicros(t)); + } +} +BENCHMARK(BM_Time_ToUnixMicros); + +void BM_Time_ToUnixMillis(benchmark::State& state) { + const absl::Time t = absl::UnixEpoch() + absl::Seconds(123); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(ToUnixMillis(t)); + } +} +BENCHMARK(BM_Time_ToUnixMillis); + +void BM_Time_ToUnixSeconds(benchmark::State& state) { + const absl::Time t = absl::UnixEpoch() + absl::Seconds(123); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::ToUnixSeconds(t)); + } +} +BENCHMARK(BM_Time_ToUnixSeconds); + +// +// FromDateTime +// +// In each "FromDateTime" benchmark we switch between two YMDhms +// values separated by at least one transition in order to defeat any +// internal caching of previous results (e.g., see time_local_hint_). +// +// The "UTC" variants use UTC instead of the Google/local time zone. +// The "Day0" variants require normalization of the day of month. +// + +void BM_Time_FromDateTime_Absl(benchmark::State& state) { + const absl::TimeZone tz = + absl::time_internal::LoadTimeZone("America/Los_Angeles"); + int i = 0; + while (state.KeepRunning()) { + if ((i & 1) == 0) { + absl::FromDateTime(2014, 12, 18, 20, 16, 18, tz); + } else { + absl::FromDateTime(2013, 11, 15, 18, 30, 27, tz); + } + ++i; + } +} +BENCHMARK(BM_Time_FromDateTime_Absl); + +void BM_Time_FromDateTime_Libc(benchmark::State& state) { + // No timezone support, so just use localtime. + int i = 0; + while (state.KeepRunning()) { + struct tm tm; + if ((i & 1) == 0) { + tm.tm_year = 2014 - 1900; + tm.tm_mon = 12 - 1; + tm.tm_mday = 18; + tm.tm_hour = 20; + tm.tm_min = 16; + tm.tm_sec = 18; + } else { + tm.tm_year = 2013 - 1900; + tm.tm_mon = 11 - 1; + tm.tm_mday = 15; + tm.tm_hour = 18; + tm.tm_min = 30; + tm.tm_sec = 27; + } + tm.tm_isdst = -1; + mktime(&tm); + ++i; + } +} +BENCHMARK(BM_Time_FromDateTime_Libc); + +void BM_Time_FromDateTimeUTC_Absl(benchmark::State& state) { + const absl::TimeZone tz = absl::UTCTimeZone(); + while (state.KeepRunning()) { + FromDateTime(2014, 12, 18, 20, 16, 18, tz); + } +} +BENCHMARK(BM_Time_FromDateTimeUTC_Absl); + +void BM_Time_FromDateTimeDay0_Absl(benchmark::State& state) { + const absl::TimeZone tz = + absl::time_internal::LoadTimeZone("America/Los_Angeles"); + int i = 0; + while (state.KeepRunning()) { + if ((i & 1) == 0) { + absl::FromDateTime(2014, 12, 0, 20, 16, 18, tz); + } else { + absl::FromDateTime(2013, 11, 0, 18, 30, 27, tz); + } + ++i; + } +} +BENCHMARK(BM_Time_FromDateTimeDay0_Absl); + +void BM_Time_FromDateTimeDay0_Libc(benchmark::State& state) { + // No timezone support, so just use localtime. + int i = 0; + while (state.KeepRunning()) { + struct tm tm; + if ((i & 1) == 0) { + tm.tm_year = 2014 - 1900; + tm.tm_mon = 12 - 1; + tm.tm_mday = 0; + tm.tm_hour = 20; + tm.tm_min = 16; + tm.tm_sec = 18; + } else { + tm.tm_year = 2013 - 1900; + tm.tm_mon = 11 - 1; + tm.tm_mday = 0; + tm.tm_hour = 18; + tm.tm_min = 30; + tm.tm_sec = 27; + } + tm.tm_isdst = -1; + mktime(&tm); + ++i; + } +} +BENCHMARK(BM_Time_FromDateTimeDay0_Libc); + +// +// To/FromTimespec +// + +void BM_Time_ToTimespec(benchmark::State& state) { + absl::Time now = absl::Now(); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::ToTimespec(now)); + } +} +BENCHMARK(BM_Time_ToTimespec); + +void BM_Time_FromTimespec(benchmark::State& state) { + timespec ts = absl::ToTimespec(absl::Now()); + while (state.KeepRunning()) { + if (++ts.tv_nsec == 1000 * 1000 * 1000) { + ++ts.tv_sec; + ts.tv_nsec = 0; + } + benchmark::DoNotOptimize(absl::TimeFromTimespec(ts)); + } +} +BENCHMARK(BM_Time_FromTimespec); + +// +// Comparison with InfiniteFuture/Past +// + +void BM_Time_InfiniteFuture(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::InfiniteFuture()); + } +} +BENCHMARK(BM_Time_InfiniteFuture); + +void BM_Time_InfinitePast(benchmark::State& state) { + while (state.KeepRunning()) { + benchmark::DoNotOptimize(absl::InfinitePast()); + } +} +BENCHMARK(BM_Time_InfinitePast); + +} // namespace diff --git a/absl/types/BUILD.bazel b/absl/types/BUILD.bazel index c26c68d0b35f..1fc4c098e9bd 100644 --- a/absl/types/BUILD.bazel +++ b/absl/types/BUILD.bazel @@ -28,7 +28,7 @@ licenses(["notice"]) # Apache 2.0 cc_library( name = "any", hdrs = ["any.h"], - copts = ABSL_DEFAULT_COPTS + ABSL_EXCEPTIONS_FLAG, + copts = ABSL_DEFAULT_COPTS, deps = [ ":bad_any_cast", "//absl/base:config", @@ -40,9 +40,19 @@ cc_library( cc_library( name = "bad_any_cast", - srcs = ["bad_any_cast.cc"], hdrs = ["bad_any_cast.h"], + copts = ABSL_DEFAULT_COPTS, + deps = [":bad_any_cast_impl"], +) + +cc_library( + name = "bad_any_cast_impl", + srcs = [ + "bad_any_cast.cc", + "bad_any_cast.h", + ], copts = ABSL_EXCEPTIONS_FLAG + ABSL_DEFAULT_COPTS, + visibility = ["//visibility:private"], deps = [ "//absl/base", "//absl/base:config", @@ -206,7 +216,6 @@ cc_test( ], ) - cc_library( name = "variant", srcs = ["internal/variant.h"], diff --git a/absl/types/optional_exception_safety_test.cc b/absl/types/optional_exception_safety_test.cc index 7f6348e1b8e1..d2ef04b8d1b0 100644 --- a/absl/types/optional_exception_safety_test.cc +++ b/absl/types/optional_exception_safety_test.cc @@ -170,25 +170,22 @@ TEST(OptionalExceptionSafety, EverythingThrowsSwap) { TEST(OptionalExceptionSafety, NoThrowMoveSwap) { // Tests the nothrow guarantee for optional of T with non-throwing move - auto nothrow_test = - MakeExceptionSafetyTester().WithInvariants(testing::nothrow_guarantee); - auto nothrow_test_empty = nothrow_test.WithInitialValue(MoveOptional()); - auto nothrow_test_nonempty = - nothrow_test.WithInitialValue(MoveOptional(kInitialInteger)); - - auto swap_empty = [](MoveOptional* optional_ptr) { + { auto empty = MoveOptional(); - optional_ptr->swap(empty); - }; - EXPECT_TRUE(nothrow_test_nonempty.Test(swap_empty)); - - auto swap_nonempty = [](MoveOptional* optional_ptr) { - auto nonempty = - MoveOptional(absl::in_place, kUpdatedInteger, testing::nothrow_ctor); - optional_ptr->swap(nonempty); - }; - EXPECT_TRUE(nothrow_test_empty.Test(swap_nonempty)); - EXPECT_TRUE(nothrow_test_nonempty.Test(swap_nonempty)); + auto nonempty = MoveOptional(kInitialInteger); + EXPECT_TRUE(testing::TestNothrowOp([&]() { nonempty.swap(empty); })); + } + { + auto nonempty = MoveOptional(kUpdatedInteger); + auto empty = MoveOptional(); + EXPECT_TRUE(testing::TestNothrowOp([&]() { empty.swap(nonempty); })); + } + { + auto nonempty_from = MoveOptional(kUpdatedInteger); + auto nonempty_to = MoveOptional(kInitialInteger); + EXPECT_TRUE( + testing::TestNothrowOp([&]() { nonempty_to.swap(nonempty_from); })); + } } TEST(OptionalExceptionSafety, CopyAssign) { @@ -251,32 +248,33 @@ TEST(OptionalExceptionSafety, MoveAssign) { TEST(OptionalExceptionSafety, NothrowMoveAssign) { // Tests the nothrow guarantee for optional of T with non-throwing move - auto nothrow_test = - MakeExceptionSafetyTester().WithInvariants(testing::nothrow_guarantee); - auto nothrow_test_empty = nothrow_test.WithInitialValue(MoveOptional()); - auto nothrow_test_nonempty = - nothrow_test.WithInitialValue(MoveOptional(kInitialInteger)); - - auto moveassign_empty = [](MoveOptional* optional_ptr) { + { auto empty = MoveOptional(); - *optional_ptr = std::move(empty); - }; - EXPECT_TRUE(nothrow_test_nonempty.Test(moveassign_empty)); - - auto moveassign_nonempty = [](MoveOptional* optional_ptr) { - auto nonempty = - MoveOptional(absl::in_place, kUpdatedInteger, testing::nothrow_ctor); - *optional_ptr = std::move(nonempty); - }; - EXPECT_TRUE(nothrow_test_empty.Test(moveassign_nonempty)); - EXPECT_TRUE(nothrow_test_nonempty.Test(moveassign_nonempty)); - - auto moveassign_thrower = [](MoveOptional* optional_ptr) { - auto thrower = MoveThrower(kUpdatedInteger, testing::nothrow_ctor); - *optional_ptr = std::move(thrower); - }; - EXPECT_TRUE(nothrow_test_empty.Test(moveassign_thrower)); - EXPECT_TRUE(nothrow_test_nonempty.Test(moveassign_thrower)); + auto nonempty = MoveOptional(kInitialInteger); + EXPECT_TRUE(testing::TestNothrowOp([&]() { nonempty = std::move(empty); })); + } + { + auto nonempty = MoveOptional(kInitialInteger); + auto empty = MoveOptional(); + EXPECT_TRUE(testing::TestNothrowOp([&]() { empty = std::move(nonempty); })); + } + { + auto nonempty_from = MoveOptional(kUpdatedInteger); + auto nonempty_to = MoveOptional(kInitialInteger); + EXPECT_TRUE(testing::TestNothrowOp( + [&]() { nonempty_to = std::move(nonempty_from); })); + } + { + auto thrower = MoveThrower(kUpdatedInteger); + auto empty = MoveOptional(); + EXPECT_TRUE(testing::TestNothrowOp([&]() { empty = std::move(thrower); })); + } + { + auto thrower = MoveThrower(kUpdatedInteger); + auto nonempty = MoveOptional(kInitialInteger); + EXPECT_TRUE( + testing::TestNothrowOp([&]() { nonempty = std::move(thrower); })); + } } } // namespace diff --git a/absl/types/span.h b/absl/types/span.h index f781353c2347..76be819ecca2 100644 --- a/absl/types/span.h +++ b/absl/types/span.h @@ -279,7 +279,7 @@ class Span { using size_type = size_t; using difference_type = ptrdiff_t; - static const size_type npos = ~size_type{0}; + static const size_type npos = ~(size_type(0)); constexpr Span() noexcept : Span(nullptr, 0) {} constexpr Span(pointer array, size_type length) noexcept diff --git a/absl/types/variant_exception_safety_test.cc b/absl/types/variant_exception_safety_test.cc index 377e4afac572..27c0b96ca6f3 100644 --- a/absl/types/variant_exception_safety_test.cc +++ b/absl/types/variant_exception_safety_test.cc @@ -27,8 +27,8 @@ namespace absl { namespace { using ::testing::MakeExceptionSafetyTester; -using ::testing::nothrow_guarantee; using ::testing::strong_guarantee; +using ::testing::TestNothrowOp; using ::testing::TestThrowingCtor; using Thrower = testing::ThrowingValue<>; @@ -120,7 +120,11 @@ testing::AssertionResult CheckInvariants(ThrowingVariant* v) { return AssertionSuccess(); } -Thrower ExpectedThrower() { return Thrower(42); } +template <typename... Args> +Thrower ExpectedThrower(Args&&... args) { + return Thrower(42, args...); +} + ThrowerVec ExpectedThrowerVec() { return {Thrower(100), Thrower(200)}; } ThrowingVariant ValuelessByException() { ThrowingVariant v; @@ -193,18 +197,14 @@ TEST(VariantExceptionSafetyTest, CopyAssign) { { // - neither *this nor rhs holds a value const ThrowingVariant rhs = ValuelessByException(); - EXPECT_TRUE(MakeExceptionSafetyTester() - .WithInitialValue(ValuelessByException()) - .WithInvariants(nothrow_guarantee) - .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + ThrowingVariant lhs = ValuelessByException(); + EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; })); } { // - *this holds a value but rhs does not const ThrowingVariant rhs = ValuelessByException(); - EXPECT_TRUE(MakeExceptionSafetyTester() - .WithInitialValue(WithThrower()) - .WithInvariants(nothrow_guarantee) - .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + ThrowingVariant lhs = WithThrower(); + EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; })); } // - index() == j { @@ -237,10 +237,8 @@ TEST(VariantExceptionSafetyTest, CopyAssign) { // should not throw because emplace() invokes Tj's copy ctor // which should not throw. const ThrowingVariant rhs(CopyNothrow{}); - EXPECT_TRUE(MakeExceptionSafetyTester() - .WithInitialValue(WithThrower()) - .WithInvariants(nothrow_guarantee) - .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + ThrowingVariant lhs = WithThrower(); + EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; })); } { // is_nothrow_copy_constructible<Tj> == false && @@ -281,23 +279,14 @@ TEST(VariantExceptionSafetyTest, MoveAssign) { { // - neither *this nor rhs holds a value ThrowingVariant rhs = ValuelessByException(); - - EXPECT_TRUE(MakeExceptionSafetyTester() - .WithInitialValue(ValuelessByException()) - .WithInvariants(nothrow_guarantee) - .Test([rhs](ThrowingVariant* lhs) mutable { - *lhs = std::move(rhs); - })); + ThrowingVariant lhs = ValuelessByException(); + EXPECT_TRUE(TestNothrowOp([&]() { lhs = std::move(rhs); })); } { // - *this holds a value but rhs does not ThrowingVariant rhs = ValuelessByException(); - EXPECT_TRUE(MakeExceptionSafetyTester() - .WithInitialValue(WithThrower()) - .WithInvariants(nothrow_guarantee) - .Test([rhs](ThrowingVariant* lhs) mutable { - *lhs = std::move(rhs); - })); + ThrowingVariant lhs = WithThrower(); + EXPECT_TRUE(TestNothrowOp([&]() { lhs = std::move(rhs); })); } { // - index() == j @@ -310,13 +299,14 @@ TEST(VariantExceptionSafetyTest, MoveAssign) { // Since Thrower's move assignment has basic guarantee, so should variant's. auto tester = MakeExceptionSafetyTester() .WithInitialValue(WithThrower()) - .WithOperation([rhs](ThrowingVariant* lhs) mutable { - *lhs = std::move(rhs); + .WithOperation([&](ThrowingVariant* lhs) { + auto copy = rhs; + *lhs = std::move(copy); }); EXPECT_TRUE(tester .WithInvariants( CheckInvariants, - [j](ThrowingVariant* lhs) { return lhs->index() == j; }) + [&](ThrowingVariant* lhs) { return lhs->index() == j; }) .Test()); EXPECT_FALSE(tester.WithInvariants(strong_guarantee).Test()); } @@ -332,8 +322,9 @@ TEST(VariantExceptionSafetyTest, MoveAssign) { [](ThrowingVariant* lhs) { return lhs->valueless_by_exception(); }) - .Test([rhs](ThrowingVariant* lhs) mutable { - *lhs = std::move(rhs); + .Test([&](ThrowingVariant* lhs) { + auto copy = rhs; + *lhs = std::move(copy); })); } } @@ -365,8 +356,9 @@ TEST(VariantExceptionSafetyTest, ValueAssign) { // move assign auto move_tester = MakeExceptionSafetyTester() .WithInitialValue(WithThrower()) - .WithOperation([rhs](ThrowingVariant* lhs) mutable { - *lhs = std::move(rhs); + .WithOperation([&](ThrowingVariant* lhs) { + auto copy = rhs; + *lhs = std::move(copy); }); EXPECT_TRUE(move_tester .WithInvariants(CheckInvariants, @@ -388,19 +380,13 @@ TEST(VariantExceptionSafetyTest, ValueAssign) { // invokes the copy/move constructor and it should not throw. { const CopyNothrow rhs; - EXPECT_TRUE(MakeExceptionSafetyTester() - .WithInitialValue(WithThrower()) - .WithInvariants(nothrow_guarantee) - .Test([&rhs](ThrowingVariant* lhs) { *lhs = rhs; })); + ThrowingVariant lhs = WithThrower(); + EXPECT_TRUE(TestNothrowOp([&]() { lhs = rhs; })); } { MoveNothrow rhs; - EXPECT_TRUE(MakeExceptionSafetyTester() - .WithInitialValue(WithThrower()) - .WithInvariants(nothrow_guarantee) - .Test([rhs](ThrowingVariant* lhs) mutable { - *lhs = std::move(rhs); - })); + ThrowingVariant lhs = WithThrower(); + EXPECT_TRUE(TestNothrowOp([&]() { lhs = std::move(rhs); })); } // if is_nothrow_constructible_v<Tj, T> == false && // is_nothrow_move_constructible<Tj> == false @@ -423,8 +409,8 @@ TEST(VariantExceptionSafetyTest, ValueAssign) { // move auto move_tester = MakeExceptionSafetyTester() .WithInitialValue(WithCopyNoThrow()) - .WithOperation([rhs](ThrowingVariant* lhs) mutable { - *lhs = std::move(rhs); + .WithOperation([](ThrowingVariant* lhs) { + *lhs = ExpectedThrower(testing::nothrow_ctor); }); EXPECT_TRUE(move_tester .WithInvariants(CheckInvariants, @@ -477,21 +463,20 @@ TEST(VariantExceptionSafetyTest, Swap) { // if both are valueless_by_exception(), no effect { ThrowingVariant rhs = ValuelessByException(); - EXPECT_TRUE( - MakeExceptionSafetyTester() - .WithInitialValue(ValuelessByException()) - .WithInvariants(nothrow_guarantee) - .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); })); + ThrowingVariant lhs = ValuelessByException(); + EXPECT_TRUE(TestNothrowOp([&]() { lhs.swap(rhs); })); } // if index() == rhs.index(), calls swap(get<i>(*this), get<i>(rhs)) // where i is index(). { ThrowingVariant rhs = ExpectedThrower(); - EXPECT_TRUE( - MakeExceptionSafetyTester() - .WithInitialValue(WithThrower()) - .WithInvariants(CheckInvariants) - .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); })); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithThrower()) + .WithInvariants(CheckInvariants) + .Test([&](ThrowingVariant* lhs) { + auto copy = rhs; + lhs->swap(copy); + })); } // Otherwise, exchanges the value of rhs and *this. The exception safety // involves variant in moved-from state which is not specified in the @@ -499,19 +484,23 @@ TEST(VariantExceptionSafetyTest, Swap) { // overall strong guarantee. So, we are only checking basic guarantee here. { ThrowingVariant rhs = ExpectedThrower(); - EXPECT_TRUE( - MakeExceptionSafetyTester() - .WithInitialValue(WithCopyNoThrow()) - .WithInvariants(CheckInvariants) - .Test([rhs](ThrowingVariant* lhs) mutable { lhs->swap(rhs); })); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithInvariants(CheckInvariants) + .Test([&](ThrowingVariant* lhs) { + auto copy = rhs; + lhs->swap(copy); + })); } { ThrowingVariant rhs = ExpectedThrower(); - EXPECT_TRUE( - MakeExceptionSafetyTester() - .WithInitialValue(WithCopyNoThrow()) - .WithInvariants(CheckInvariants) - .Test([rhs](ThrowingVariant* lhs) mutable { rhs.swap(*lhs); })); + EXPECT_TRUE(MakeExceptionSafetyTester() + .WithInitialValue(WithCopyNoThrow()) + .WithInvariants(CheckInvariants) + .Test([&](ThrowingVariant* lhs) { + auto copy = rhs; + copy.swap(*lhs); + })); } } |