2 * Copyright 2014-present Facebook, Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
22 #include <folly/detail/Futex.h>
23 #include <folly/hash/Hash.h>
24 #include <folly/synchronization/AtomicStruct.h>
25 #include <folly/system/ThreadId.h>
27 namespace folly { namespace detail {
29 /// MemoryIdler provides helper routines that allow routines to return
30 /// some assigned memory resources back to the system. The intended
31 /// use is that when a thread is waiting for a long time (perhaps it
32 /// is in a LIFO thread pool and hasn't been needed for a long time)
33 /// it should release its thread-local malloc caches (both jemalloc and
34 /// tcmalloc use these for better performance) and unmap the stack pages
35 /// that contain no useful data.
38 /// Returns memory from thread-local allocation pools to the global
39 /// pool, if we know how to for the current malloc implementation.
40 /// jemalloc is supported.
41 static void flushLocalMallocCaches();
45 /// This value is a tradeoff between reclaiming memory and triggering
46 /// a page fault immediately on wakeup. Note that the actual unit
47 /// of idling for the stack is pages, so the actual stack that
48 /// will be available on wakeup without a page fault is between
49 /// kDefaultStackToRetain and kDefaultStackToRetain + PageSize -
51 kDefaultStackToRetain = 1024,
54 /// Uses madvise to discard the portion of the thread's stack that
55 /// currently doesn't hold any data, trying to ensure that no page
56 /// faults will occur during the next retain bytes of stack allocation
57 static void unmapUnusedStack(size_t retain = kDefaultStackToRetain);
60 /// The system-wide default for the amount of time a blocking
61 /// thread should wait before reclaiming idle memory. Set this to
62 /// Duration::max() to never wait. The default value is 5 seconds.
63 /// Endpoints using this idle timeout might randomly wait longer to
64 /// avoid synchronizing their flushes.
65 static AtomicStruct<std::chrono::steady_clock::duration> defaultIdleTimeout;
67 /// Selects a timeout pseudo-randomly chosen to be between
68 /// idleTimeout and idleTimeout * (1 + timeoutVariationFraction), to
69 /// smooth out the behavior in a bursty system
70 template <typename IdleTime = std::chrono::steady_clock::duration>
71 static IdleTime getVariationTimeout(
72 IdleTime const& idleTimeout =
73 defaultIdleTimeout.load(std::memory_order_acquire),
74 float timeoutVariationFrac = 0.5) {
75 if (idleTimeout <= IdleTime::zero() || timeoutVariationFrac <= 0) {
79 // hash the pthread_t and the time to get the adjustment
80 // Standard hash func isn't very good, so bit mix the result
81 uint64_t h = folly::hash::twang_mix64(folly::hash::hash_combine(
83 std::chrono::system_clock::now().time_since_epoch().count()));
85 // multiplying the duration by a floating point doesn't work, grr
87 timeoutVariationFrac / std::numeric_limits<uint64_t>::max() * h;
88 auto tics = uint64_t(idleTimeout.count() * (1 + extraFrac));
89 return IdleTime(tics);
92 /// Equivalent to fut.futexWait(expected, waitMask), but calls
93 /// flushLocalMallocCaches() and unmapUnusedStack(stackToRetain)
94 /// after idleTimeout has passed (if it has passed). Internally uses
95 /// fut.futexWait and fut.futexWaitUntil. The actual timeout will be
96 /// pseudo-randomly chosen to be between idleTimeout and idleTimeout *
97 /// (1 + timeoutVariationFraction), to smooth out the behavior in a
98 /// system with bursty requests. The default is to wait up to 50%
99 /// extra, so on average 25% extra.
101 template <typename> class Atom,
102 typename IdleTime = std::chrono::steady_clock::duration>
103 static FutexResult futexWait(
106 uint32_t waitMask = -1,
107 IdleTime const& idleTimeout =
108 defaultIdleTimeout.load(std::memory_order_acquire),
109 size_t stackToRetain = kDefaultStackToRetain,
110 float timeoutVariationFrac = 0.5) {
112 if (futexWaitPreIdle(
116 std::chrono::steady_clock::time_point::max(),
120 timeoutVariationFrac)) {
123 return fut.futexWait(expected, waitMask);
126 /// Equivalent to fut.futexWaitUntil(expected, deadline, waitMask), but
127 /// calls flushLocalMallocCaches() and unmapUnusedStack(stackToRetain)
128 /// after idleTimeout has passed (if it has passed). Internally uses
129 /// fut.futexWaitUntil. The actual timeout will be pseudo-randomly
130 /// chosen to be between idleTimeout and idleTimeout *
131 /// (1 + timeoutVariationFraction), to smooth out the behavior in a
132 /// system with bursty requests. The default is to wait up to 50%
133 /// extra, so on average 25% extra.
135 template <typename> class Atom,
137 typename IdleTime = std::chrono::steady_clock::duration>
138 static FutexResult futexWaitUntil(
141 Deadline const& deadline,
142 uint32_t waitMask = -1,
143 IdleTime const& idleTimeout =
144 defaultIdleTimeout.load(std::memory_order_acquire),
145 size_t stackToRetain = kDefaultStackToRetain,
146 float timeoutVariationFrac = 0.5) {
148 if (futexWaitPreIdle(
156 timeoutVariationFrac)) {
159 return fut.futexWaitUntil(expected, deadline, waitMask);
164 template <typename> class Atom,
167 static bool futexWaitPreIdle(
171 Deadline const& deadline,
173 IdleTime idleTimeout,
174 size_t stackToRetain,
175 float timeoutVariationFrac) {
176 // idleTimeout < 0 means no flush behavior
177 if (idleTimeout < IdleTime::zero()) {
181 // idleTimeout == 0 means flush immediately, without variation
182 // idleTimeout > 0 means flush after delay, with variation
183 if (idleTimeout > IdleTime::zero()) {
184 idleTimeout = std::max(
186 getVariationTimeout(idleTimeout, timeoutVariationFrac));
188 if (idleTimeout > IdleTime::zero()) {
189 auto idleDeadline = Deadline::clock::now() + idleTimeout;
190 if (idleDeadline < deadline) {
192 auto rv = fut.futexWaitUntil(expected, idleDeadline, waitMask);
193 if (rv == FutexResult::TIMEDOUT) {
196 // finished before timeout hit, no flush
204 flushLocalMallocCaches();
205 unmapUnusedStack(stackToRetain);
210 } // namespace detail