1a8fe3ae1355b19c906b1b93470bbf325c5afe85
[folly.git] / folly / detail / MemoryIdler.h
1 /*
2  * Copyright 2017 Facebook, Inc.
3  *
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
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 #pragma once
18
19 #include <atomic>
20 #include <chrono>
21
22 #include <folly/AtomicStruct.h>
23 #include <folly/Hash.h>
24 #include <folly/Traits.h>
25 #include <folly/detail/Futex.h>
26 #include <folly/system/ThreadId.h>
27
28 namespace folly {
29
30 // gcc 4.7 doesn't do std::is_trivial correctly, override so we can use
31 // AtomicStruct<duration>
32 template <>
33 struct IsTriviallyCopyable<std::chrono::steady_clock::duration>
34   : std::true_type {};
35
36 } // namespace folly
37
38 namespace folly { namespace detail {
39
40 /// MemoryIdler provides helper routines that allow routines to return
41 /// some assigned memory resources back to the system.  The intended
42 /// use is that when a thread is waiting for a long time (perhaps it
43 /// is in a LIFO thread pool and hasn't been needed for a long time)
44 /// it should release its thread-local malloc caches (both jemalloc and
45 /// tcmalloc use these for better performance) and unmap the stack pages
46 /// that contain no useful data.
47 struct MemoryIdler {
48
49   /// Returns memory from thread-local allocation pools to the global
50   /// pool, if we know how to for the current malloc implementation.
51   /// jemalloc is supported.
52   static void flushLocalMallocCaches();
53
54
55   enum {
56     /// This value is a tradeoff between reclaiming memory and triggering
57     /// a page fault immediately on wakeup.  Note that the actual unit
58     /// of idling for the stack is pages, so the actual stack that
59     /// will be available on wakeup without a page fault is between
60     /// kDefaultStackToRetain and kDefaultStackToRetain + PageSize -
61     /// 1 bytes.
62     kDefaultStackToRetain = 1024,
63   };
64
65   /// Uses madvise to discard the portion of the thread's stack that
66   /// currently doesn't hold any data, trying to ensure that no page
67   /// faults will occur during the next retain bytes of stack allocation
68   static void unmapUnusedStack(size_t retain = kDefaultStackToRetain);
69
70
71   /// The system-wide default for the amount of time a blocking
72   /// thread should wait before reclaiming idle memory.  Set this to
73   /// Duration::max() to never wait.  The default value is 5 seconds.
74   /// Endpoints using this idle timeout might randomly wait longer to
75   /// avoid synchronizing their flushes.
76   static AtomicStruct<std::chrono::steady_clock::duration> defaultIdleTimeout;
77
78   /// Selects a timeout pseudo-randomly chosen to be between
79   /// idleTimeout and idleTimeout * (1 + timeoutVariationFraction), to
80   /// smooth out the behavior in a bursty system
81   template <typename Clock = std::chrono::steady_clock>
82   static typename Clock::duration getVariationTimeout(
83       typename Clock::duration idleTimeout
84           = defaultIdleTimeout.load(std::memory_order_acquire),
85       float timeoutVariationFrac = 0.5) {
86     if (idleTimeout.count() > 0 && timeoutVariationFrac > 0) {
87       // hash the pthread_t and the time to get the adjustment.
88       // Standard hash func isn't very good, so bit mix the result
89       auto pr = std::make_pair(getCurrentThreadID(),
90                                Clock::now().time_since_epoch().count());
91       std::hash<decltype(pr)> hash_fn;
92       uint64_t h = folly::hash::twang_mix64(hash_fn(pr));
93
94       // multiplying the duration by a floating point doesn't work, grr..
95       auto extraFrac =
96         timeoutVariationFrac / std::numeric_limits<uint64_t>::max() * h;
97       auto tics = uint64_t(idleTimeout.count() * (1 + extraFrac));
98       idleTimeout = typename Clock::duration(tics);
99     }
100
101     return idleTimeout;
102   }
103
104   /// Equivalent to fut.futexWait(expected, waitMask), but calls
105   /// flushLocalMallocCaches() and unmapUnusedStack(stackToRetain)
106   /// after idleTimeout has passed (if it has passed).  Internally uses
107   /// fut.futexWait and fut.futexWaitUntil.  Like futexWait, returns
108   /// false if interrupted with a signal.  The actual timeout will be
109   /// pseudo-randomly chosen to be between idleTimeout and idleTimeout *
110   /// (1 + timeoutVariationFraction), to smooth out the behavior in a
111   /// system with bursty requests.  The default is to wait up to 50%
112   /// extra, so on average 25% extra
113   template <
114       template <typename> class Atom,
115       typename Clock = std::chrono::steady_clock>
116   static bool futexWait(
117       Futex<Atom>& fut,
118       uint32_t expected,
119       uint32_t waitMask = -1,
120       typename Clock::duration idleTimeout =
121           defaultIdleTimeout.load(std::memory_order_acquire),
122       size_t stackToRetain = kDefaultStackToRetain,
123       float timeoutVariationFrac = 0.5) {
124
125     if (idleTimeout == Clock::duration::max()) {
126       // no need to use futexWaitUntil if no timeout is possible
127       return fut.futexWait(expected, waitMask);
128     }
129
130     idleTimeout = getVariationTimeout(idleTimeout, timeoutVariationFrac);
131     if (idleTimeout.count() > 0) {
132       while (true) {
133         auto rv = fut.futexWaitUntil(
134           expected, Clock::now() + idleTimeout, waitMask);
135         if (rv == FutexResult::TIMEDOUT) {
136           // timeout is over
137           break;
138         }
139         // finished before timeout hit, no flush
140         assert(rv == FutexResult::VALUE_CHANGED || rv == FutexResult::AWOKEN ||
141                rv == FutexResult::INTERRUPTED);
142         return rv == FutexResult::AWOKEN;
143       }
144     }
145
146     // flush, then wait with no timeout
147     flushLocalMallocCaches();
148     unmapUnusedStack(stackToRetain);
149     return fut.futexWait(expected, waitMask);
150   }
151 };
152
153 } // namespace detail
154 } // namespace folly