// Copyright 2019 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
//
// https://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.
#ifndef ABSL_BASE_INTERNAL_PERIODIC_SAMPLER_H_
#define ABSL_BASE_INTERNAL_PERIODIC_SAMPLER_H_
#include <stdint.h>
#include <atomic>
#include "absl/base/internal/exponential_biased.h"
#include "absl/base/optimization.h"
namespace absl {
namespace base_internal {
// PeriodicSamplerBase provides the basic period sampler implementation.
//
// This is the base class for the templated PeriodicSampler class, which holds
// a global std::atomic value identified by a user defined tag, such that
// each specific PeriodSampler implementation holds its own global period.
//
// PeriodicSamplerBase is thread-compatible except where stated otherwise.
class PeriodicSamplerBase {
public:
// PeriodicSamplerBase is trivial / copyable / movable / destructible.
PeriodicSamplerBase() = default;
PeriodicSamplerBase(PeriodicSamplerBase&&) = default;
PeriodicSamplerBase(const PeriodicSamplerBase&) = default;
// Returns true roughly once every `period` calls. This is established by a
// randomly picked `stride` that is counted down on each call to `Sample`.
// This stride is picked such that the probability of `Sample()` returning
// true is 1 in `period`.
inline bool Sample() noexcept;
// The below methods are intended for optimized use cases where the
// size of the inlined fast path code is highly important. Applications
// should use the `Sample()` method unless they have proof that their
// specific use case requires the optimizations offered by these methods.
//
// An example of such a use case is SwissTable sampling. All sampling checks
// are in inlined SwissTable methods, and the number of call sites is huge.
// In this case, the inlined code size added to each translation unit calling
// SwissTable methods is non-trivial.
//
// The `SubtleMaybeSample()` function spuriously returns true even if the
// function should not be sampled, applications MUST match each call to
// 'SubtleMaybeSample()' returning true with a `SubtleConfirmSample()` call,
// and use the result of the latter as the sampling decision.
// In other words: the code should logically be equivalent to:
//
// if (SubtleMaybeSample() && SubtleConfirmSample()) {
// // Sample this call
// }
//
// In the 'inline-size' optimized case, the `SubtleConfirmSample()` call can
// be placed out of line, for example, the typical use case looks as follows:
//
// // --- frobber.h -----------
// void FrobberSampled();
//
// inline void FrobberImpl() {
// // ...
// }
//
// inline void Frobber() {
// if (ABSL_PREDICT_FALSE(sampler.SubtleMaybeSample())) {
// FrobberSampled();
// } else {
// FrobberImpl();
// }
// }
//
// // --- frobber.cc -----------
// void FrobberSampled() {
// if (!sampler.SubtleConfirmSample())) {
// // Spurious false positive
// FrobberImpl();
// return;
// }
//
// // Sampled execution
// // ...
// }
inline bool SubtleMaybeSample() noexcept;
bool SubtleConfirmSample() noexcept;
protected:
// We explicitly don't use a virtual destructor as this class is never
// virtually destroyed, and it keeps the class trivial, which avoids TLS
// prologue and epilogue code for our TLS instances.
~PeriodicSamplerBase() = default;
// Returns the next stride for our sampler.
// This function is virtual for testing purposes only.
virtual int64_t GetExponentialBiased(int period) noexcept;
private:
// Returns the current period of this sampler. Thread-safe.
virtual int period() const noexcept = 0;
// Keep and decrement stride_ as an unsigned integer, but compare the value
// to zero casted as a signed int. clang and msvc do not create optimum code
// if we use signed for the combined decrement and sign comparison.
//
// Below 3 alternative options, all compiles generate the best code
// using the unsigned increment <---> signed int comparison option.
//
// Option 1:
// int64_t stride_;
// if (ABSL_PREDICT_TRUE(++stride_ < 0)) { ... }
//
// GCC x64 (OK) : https://gcc.godbolt.org/z/R5MzzA
// GCC ppc (OK) : https://gcc.godbolt.org/z/z7NZAt
// Clang x64 (BAD): https://gcc.godbolt.org/z/t4gPsd
// ICC x64 (OK) : https://gcc.godbolt.org/z/rE6s8W
// MSVC x64 (OK) : https://gcc.godbolt.org/z/ARMXqS
//
// Option 2:
// int64_t stride_ = 0;
// if (ABSL_PREDICT_TRUE(--stride_ >= 0)) { ... }
//
// GCC x64 (OK) : https://gcc.godbolt.org/z/jSQxYK
// GCC ppc (OK) : https://gcc.godbolt.org/z/VJdYaA
// Clang x64 (BAD): https://gcc.godbolt.org/z/Xm4NjX
// ICC x64 (OK) : https://gcc.godbolt.org/z/4snaFd
// MSVC x64 (BAD): https://gcc.godbolt.org/z/BgnEKE
//
// Option 3:
// uint64_t stride_;
// if (ABSL_PREDICT_TRUE(static_cast<int64_t>(++stride_) < 0)) { ... }
//
// GCC x64 (OK) : https://gcc.godbolt.org/z/bFbfPy
// GCC ppc (OK) : https://gcc.godbolt.org/z/S9KkUE
// Clang x64 (OK) : https://gcc.godbolt.org/z/UYzRb4
// ICC x64 (OK) : https://gcc.godbolt.org/z/ptTNfD
// MSVC x64 (OK) : https://gcc.godbolt.org/z/76j4-5
uint64_t stride_ = 0;
ExponentialBiased rng_;
};
inline bool PeriodicSamplerBase::SubtleMaybeSample() noexcept {
// See comments on `stride_` for the unsigned increment / signed compare.
if (ABSL_PREDICT_TRUE(static_cast<int64_t>(++stride_) < 0)) {
return false;
}
return true;
}
inline bool PeriodicSamplerBase::Sample() noexcept {
return ABSL_PREDICT_FALSE(SubtleMaybeSample()) ? SubtleConfirmSample()
: false;
}
// PeriodicSampler is a concreted periodic sampler implementation.
// The user provided Tag identifies the implementation, and is required to
// isolate the global state of this instance from other instances.
//
// Typical use case:
//
// struct HashTablezTag {};
// thread_local PeriodicSampler sampler;
//
// void HashTableSamplingLogic(...) {
// if (sampler.Sample()) {
// HashTableSlowSamplePath(...);
// }
// }
//
template <typename Tag, int default_period = 0>
class PeriodicSampler final : public PeriodicSamplerBase {
public:
~PeriodicSampler() = default;
int period() const noexcept final {
return period_.load(std::memory_order_relaxed);
}
// Sets the global period for this sampler. Thread-safe.
// Setting a period of 0 disables the sampler, i.e., every call to Sample()
// will return false. Setting a period of 1 puts the sampler in 'always on'
// mode, i.e., every call to Sample() returns true.
static void SetGlobalPeriod(int period) {
period_.store(period, std::memory_order_relaxed);
}
private:
static std::atomic<int> period_;
};
template <typename Tag, int default_period>
std::atomic<int> PeriodicSampler<Tag, default_period>::period_(default_period);
} // namespace base_internal
} // namespace absl
#endif // ABSL_BASE_INTERNAL_PERIODIC_SAMPLER_H_