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