Commit b1fe65ff authored by Yedidya Feldblum's avatar Yedidya Feldblum Committed by Facebook Github Bot

MemoryIdler::futexWaitUntil

Summary:
[Folly] `MemoryIdler::futexWaitUntil`.

Adds `MemoryIdler::futexWaitUntil` that works like `Futex::futexWaitUntil`, in a similar way that `MemoryIdler::futexWait` works like `Futex::futexWait`.

Removes the ability to customize the idle-timeout clock for `MemoryIdler::futexWait` as a side-effect; the idle-timeout is now a pure duration. Now, the clock used with the idle-timeout is the same as the normal deadline clock, so the idle-timeout clock can be set for `MemoryIdler::futexWaitUntil` by providing a deadline with that clock type. This normally would not matter, but it affects the unit-tests.

Reviewed By: djwatson

Differential Revision: D6681679

fbshipit-source-id: e3cf6e71d7530c5877a834b318b423eb91f71eb9
parent 3d2b926a
...@@ -67,74 +67,143 @@ struct MemoryIdler { ...@@ -67,74 +67,143 @@ struct MemoryIdler {
/// Selects a timeout pseudo-randomly chosen to be between /// Selects a timeout pseudo-randomly chosen to be between
/// idleTimeout and idleTimeout * (1 + timeoutVariationFraction), to /// idleTimeout and idleTimeout * (1 + timeoutVariationFraction), to
/// smooth out the behavior in a bursty system /// smooth out the behavior in a bursty system
template <typename Clock = std::chrono::steady_clock> template <typename IdleTime = std::chrono::steady_clock::duration>
static typename Clock::duration getVariationTimeout( static IdleTime getVariationTimeout(
typename Clock::duration idleTimeout IdleTime const& idleTimeout =
= defaultIdleTimeout.load(std::memory_order_acquire), defaultIdleTimeout.load(std::memory_order_acquire),
float timeoutVariationFrac = 0.5) { float timeoutVariationFrac = 0.5) {
if (idleTimeout.count() > 0 && timeoutVariationFrac > 0) { if (idleTimeout <= IdleTime::zero() || timeoutVariationFrac <= 0) {
// hash the pthread_t and the time to get the adjustment. return idleTimeout;
}
// hash the pthread_t and the time to get the adjustment
// Standard hash func isn't very good, so bit mix the result // Standard hash func isn't very good, so bit mix the result
auto pr = std::make_pair(getCurrentThreadID(), uint64_t h = folly::hash::twang_mix64(folly::hash::hash_combine(
Clock::now().time_since_epoch().count()); getCurrentThreadID(),
std::hash<decltype(pr)> hash_fn; std::chrono::system_clock::now().time_since_epoch().count()));
uint64_t h = folly::hash::twang_mix64(hash_fn(pr));
// multiplying the duration by a floating point doesn't work, grr.. // multiplying the duration by a floating point doesn't work, grr
auto extraFrac = auto extraFrac =
timeoutVariationFrac / std::numeric_limits<uint64_t>::max() * h; timeoutVariationFrac / std::numeric_limits<uint64_t>::max() * h;
auto tics = uint64_t(idleTimeout.count() * (1 + extraFrac)); auto tics = uint64_t(idleTimeout.count() * (1 + extraFrac));
idleTimeout = typename Clock::duration(tics); return IdleTime(tics);
}
return idleTimeout;
} }
/// Equivalent to fut.futexWait(expected, waitMask), but calls /// Equivalent to fut.futexWait(expected, waitMask), but calls
/// flushLocalMallocCaches() and unmapUnusedStack(stackToRetain) /// flushLocalMallocCaches() and unmapUnusedStack(stackToRetain)
/// after idleTimeout has passed (if it has passed). Internally uses /// after idleTimeout has passed (if it has passed). Internally uses
/// fut.futexWait and fut.futexWaitUntil. Like futexWait, returns /// fut.futexWait and fut.futexWaitUntil. The actual timeout will be
/// false if interrupted with a signal. The actual timeout will be
/// pseudo-randomly chosen to be between idleTimeout and idleTimeout * /// pseudo-randomly chosen to be between idleTimeout and idleTimeout *
/// (1 + timeoutVariationFraction), to smooth out the behavior in a /// (1 + timeoutVariationFraction), to smooth out the behavior in a
/// system with bursty requests. The default is to wait up to 50% /// system with bursty requests. The default is to wait up to 50%
/// extra, so on average 25% extra /// extra, so on average 25% extra.
template < template <
template <typename> class Atom, template <typename> class Atom,
typename Clock = std::chrono::steady_clock> typename IdleTime = std::chrono::steady_clock::duration>
static FutexResult futexWait( static FutexResult futexWait(
Futex<Atom>& fut, Futex<Atom>& fut,
uint32_t expected, uint32_t expected,
uint32_t waitMask = -1, uint32_t waitMask = -1,
typename Clock::duration idleTimeout = IdleTime const& idleTimeout =
defaultIdleTimeout.load(std::memory_order_acquire), defaultIdleTimeout.load(std::memory_order_acquire),
size_t stackToRetain = kDefaultStackToRetain, size_t stackToRetain = kDefaultStackToRetain,
float timeoutVariationFrac = 0.5) { float timeoutVariationFrac = 0.5) {
if (idleTimeout == Clock::duration::max()) { FutexResult pre;
// no need to use futexWaitUntil if no timeout is possible if (futexWaitPreIdle(
pre,
fut,
expected,
std::chrono::steady_clock::time_point::max(),
waitMask,
idleTimeout,
stackToRetain,
timeoutVariationFrac)) {
return pre;
}
return fut.futexWait(expected, waitMask); return fut.futexWait(expected, waitMask);
} }
idleTimeout = getVariationTimeout(idleTimeout, timeoutVariationFrac); /// Equivalent to fut.futexWaitUntil(expected, deadline, waitMask), but
if (idleTimeout.count() > 0) { /// calls flushLocalMallocCaches() and unmapUnusedStack(stackToRetain)
/// after idleTimeout has passed (if it has passed). Internally uses
/// fut.futexWaitUntil. The actual timeout will be pseudo-randomly
/// chosen to be between idleTimeout and idleTimeout *
/// (1 + timeoutVariationFraction), to smooth out the behavior in a
/// system with bursty requests. The default is to wait up to 50%
/// extra, so on average 25% extra.
template <
template <typename> class Atom,
typename Deadline,
typename IdleTime = std::chrono::steady_clock::duration>
static FutexResult futexWaitUntil(
Futex<Atom>& fut,
uint32_t expected,
Deadline const& deadline,
uint32_t waitMask = -1,
IdleTime const& idleTimeout =
defaultIdleTimeout.load(std::memory_order_acquire),
size_t stackToRetain = kDefaultStackToRetain,
float timeoutVariationFrac = 0.5) {
FutexResult pre;
if (futexWaitPreIdle(
pre,
fut,
expected,
deadline,
waitMask,
idleTimeout,
stackToRetain,
timeoutVariationFrac)) {
return pre;
}
return fut.futexWaitUntil(expected, deadline, waitMask);
}
private:
template <
template <typename> class Atom,
typename Deadline,
typename IdleTime>
static bool futexWaitPreIdle(
FutexResult& _ret,
Futex<Atom>& fut,
uint32_t expected,
Deadline const& deadline,
uint32_t waitMask,
IdleTime idleTimeout,
size_t stackToRetain,
float timeoutVariationFrac) {
// idleTimeout < 0 means no flush behavior
if (idleTimeout < IdleTime::zero()) {
return false;
}
// idleTimeout == 0 means flush immediately, without variation
// idleTimeout > 0 means flush after delay, with variation
if (idleTimeout > IdleTime::zero()) {
idleTimeout = std::max(
IdleTime::zero(),
getVariationTimeout(idleTimeout, timeoutVariationFrac));
}
if (idleTimeout > IdleTime::zero()) {
auto idleDeadline = Deadline::clock::now() + idleTimeout;
if (idleDeadline < deadline) {
while (true) { while (true) {
auto rv = fut.futexWaitUntil( auto rv = fut.futexWaitUntil(expected, idleDeadline, waitMask);
expected, Clock::now() + idleTimeout, waitMask);
if (rv == FutexResult::TIMEDOUT) { if (rv == FutexResult::TIMEDOUT) {
// timeout is over
break; break;
} }
// finished before timeout hit, no flush // finished before timeout hit, no flush
assert(rv == FutexResult::VALUE_CHANGED || rv == FutexResult::AWOKEN || _ret = rv;
rv == FutexResult::INTERRUPTED); return true;
return rv; }
} }
} }
// flush, then wait with no timeout // flush, then wait
flushLocalMallocCaches(); flushLocalMallocCaches();
unmapUnusedStack(stackToRetain); unmapUnusedStack(stackToRetain);
return fut.futexWait(expected, waitMask); return false;
} }
}; };
......
...@@ -64,10 +64,10 @@ struct MockAtom : public std::atomic<T> { ...@@ -64,10 +64,10 @@ struct MockAtom : public std::atomic<T> {
/// extending its scope beyond that of the test. I generally avoid /// extending its scope beyond that of the test. I generally avoid
/// shared_ptr, but a weak_ptr is just the ticket here /// shared_ptr, but a weak_ptr is just the ticket here
struct MockClock { struct MockClock {
typedef std::chrono::steady_clock::duration duration; using duration = std::chrono::steady_clock::duration;
typedef std::chrono::steady_clock::time_point time_point; using time_point = std::chrono::time_point<MockClock, duration>;
MOCK_METHOD0(nowImpl, time_point(void)); MOCK_METHOD0(nowImpl, time_point());
/// Hold on to the returned shared_ptr until the end of the test /// Hold on to the returned shared_ptr until the end of the test
static std::shared_ptr<StrictMock<MockClock>> setup() { static std::shared_ptr<StrictMock<MockClock>> setup() {
...@@ -103,6 +103,8 @@ struct Futex<MockAtom> { ...@@ -103,6 +103,8 @@ struct Futex<MockAtom> {
} // namespace detail } // namespace detail
} // namespace folly } // namespace folly
static auto const forever = MockClock::time_point::max();
TEST(MemoryIdler, futexWaitValueChangedEarly) { TEST(MemoryIdler, futexWaitValueChangedEarly) {
StrictMock<Futex<MockAtom>> fut; StrictMock<Futex<MockAtom>> fut;
auto clock = MockClock::setup(); auto clock = MockClock::setup();
...@@ -115,8 +117,7 @@ TEST(MemoryIdler, futexWaitValueChangedEarly) { ...@@ -115,8 +117,7 @@ TEST(MemoryIdler, futexWaitValueChangedEarly) {
Lt(begin + 2 * idleTimeout)), -1)) Lt(begin + 2 * idleTimeout)), -1))
.WillOnce(Return(FutexResult::VALUE_CHANGED)); .WillOnce(Return(FutexResult::VALUE_CHANGED));
EXPECT_EQ( EXPECT_EQ(
FutexResult::VALUE_CHANGED, FutexResult::VALUE_CHANGED, MemoryIdler::futexWaitUntil(fut, 1, forever));
(MemoryIdler::futexWait<MockAtom, MockClock>(fut, 1)));
} }
TEST(MemoryIdler, futexWaitValueChangedLate) { TEST(MemoryIdler, futexWaitValueChangedLate) {
...@@ -130,11 +131,10 @@ TEST(MemoryIdler, futexWaitValueChangedLate) { ...@@ -130,11 +131,10 @@ TEST(MemoryIdler, futexWaitValueChangedLate) {
EXPECT_CALL(fut, futexWaitUntil(1, AllOf(Ge(begin + idleTimeout), EXPECT_CALL(fut, futexWaitUntil(1, AllOf(Ge(begin + idleTimeout),
Lt(begin + 2 * idleTimeout)), -1)) Lt(begin + 2 * idleTimeout)), -1))
.WillOnce(Return(FutexResult::TIMEDOUT)); .WillOnce(Return(FutexResult::TIMEDOUT));
EXPECT_CALL(fut, futexWait(1, -1)) EXPECT_CALL(fut, futexWaitUntil(1, forever, -1))
.WillOnce(Return(FutexResult::VALUE_CHANGED)); .WillOnce(Return(FutexResult::VALUE_CHANGED));
EXPECT_EQ( EXPECT_EQ(
FutexResult::VALUE_CHANGED, FutexResult::VALUE_CHANGED, MemoryIdler::futexWaitUntil(fut, 1, forever));
(MemoryIdler::futexWait<MockAtom, MockClock>(fut, 1)));
} }
TEST(MemoryIdler, futexWaitAwokenEarly) { TEST(MemoryIdler, futexWaitAwokenEarly) {
...@@ -147,9 +147,7 @@ TEST(MemoryIdler, futexWaitAwokenEarly) { ...@@ -147,9 +147,7 @@ TEST(MemoryIdler, futexWaitAwokenEarly) {
.WillOnce(Return(begin)); .WillOnce(Return(begin));
EXPECT_CALL(fut, futexWaitUntil(1, Ge(begin + idleTimeout), -1)) EXPECT_CALL(fut, futexWaitUntil(1, Ge(begin + idleTimeout), -1))
.WillOnce(Return(FutexResult::AWOKEN)); .WillOnce(Return(FutexResult::AWOKEN));
EXPECT_EQ( EXPECT_EQ(FutexResult::AWOKEN, MemoryIdler::futexWaitUntil(fut, 1, forever));
FutexResult::AWOKEN,
(MemoryIdler::futexWait<MockAtom, MockClock>(fut, 1)));
} }
TEST(MemoryIdler, futexWaitAwokenLate) { TEST(MemoryIdler, futexWaitAwokenLate) {
...@@ -162,31 +160,33 @@ TEST(MemoryIdler, futexWaitAwokenLate) { ...@@ -162,31 +160,33 @@ TEST(MemoryIdler, futexWaitAwokenLate) {
.WillOnce(Return(begin)); .WillOnce(Return(begin));
EXPECT_CALL(fut, futexWaitUntil(1, begin + idleTimeout, -1)) EXPECT_CALL(fut, futexWaitUntil(1, begin + idleTimeout, -1))
.WillOnce(Return(FutexResult::TIMEDOUT)); .WillOnce(Return(FutexResult::TIMEDOUT));
EXPECT_CALL(fut, futexWait(1, -1)).WillOnce(Return(FutexResult::AWOKEN)); EXPECT_CALL(fut, futexWaitUntil(1, forever, -1))
.WillOnce(Return(FutexResult::AWOKEN));
EXPECT_EQ( EXPECT_EQ(
FutexResult::AWOKEN, FutexResult::AWOKEN,
(MemoryIdler::futexWait<MockAtom, MockClock>( MemoryIdler::futexWaitUntil(fut, 1, forever, -1, idleTimeout, 100, 0.0f));
fut, 1, -1, idleTimeout, 100, 0.0f)));
} }
TEST(MemoryIdler, futexWaitImmediateFlush) { TEST(MemoryIdler, futexWaitImmediateFlush) {
StrictMock<Futex<MockAtom>> fut; StrictMock<Futex<MockAtom>> fut;
auto clock = MockClock::setup(); auto clock = MockClock::setup();
EXPECT_CALL(fut, futexWait(2, 0xff)).WillOnce(Return(FutexResult::AWOKEN)); EXPECT_CALL(fut, futexWaitUntil(2, forever, 0xff))
.WillOnce(Return(FutexResult::AWOKEN));
EXPECT_EQ( EXPECT_EQ(
FutexResult::AWOKEN, FutexResult::AWOKEN,
(MemoryIdler::futexWait<MockAtom, MockClock>( MemoryIdler::futexWaitUntil(
fut, 2, 0xff, std::chrono::seconds(0)))); fut, 2, forever, 0xff, std::chrono::seconds(0)));
} }
TEST(MemoryIdler, futexWaitNeverFlush) { TEST(MemoryIdler, futexWaitNeverFlush) {
StrictMock<Futex<MockAtom>> fut; StrictMock<Futex<MockAtom>> fut;
auto clock = MockClock::setup(); auto clock = MockClock::setup();
EXPECT_CALL(fut, futexWait(1, -1)).WillOnce(Return(FutexResult::AWOKEN)); EXPECT_CALL(fut, futexWaitUntil(1, forever, -1))
.WillOnce(Return(FutexResult::AWOKEN));
EXPECT_EQ( EXPECT_EQ(
FutexResult::AWOKEN, FutexResult::AWOKEN,
(MemoryIdler::futexWait<MockAtom, MockClock>( MemoryIdler::futexWaitUntil(
fut, 1, -1, MockClock::duration::max()))); fut, 1, forever, -1, std::chrono::seconds(-7)));
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment