SliceBudget.h (5473B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef js_SliceBudget_h 8 #define js_SliceBudget_h 9 10 #include "mozilla/Assertions.h" 11 #include "mozilla/Atomics.h" 12 #include "mozilla/TimeStamp.h" 13 #include "mozilla/Variant.h" 14 15 #include <stdint.h> 16 17 #include "jstypes.h" 18 19 namespace JS { 20 21 struct JS_PUBLIC_API TimeBudget { 22 const mozilla::TimeDuration budget; 23 mozilla::TimeStamp deadline; // Calculated when SliceBudget is constructed. 24 25 explicit TimeBudget(mozilla::TimeDuration duration) : budget(duration) {} 26 explicit TimeBudget(int64_t milliseconds) 27 : budget(mozilla::TimeDuration::FromMilliseconds(milliseconds)) {} 28 29 void setDeadlineFromNow(); 30 double progress(mozilla::TimeStamp t) const { 31 return (t - (deadline - budget)) / budget; 32 } 33 }; 34 35 struct JS_PUBLIC_API WorkBudget { 36 const int64_t budget; 37 38 explicit WorkBudget(int64_t work) : budget(work) {} 39 double progress(int64_t work) const { return double(work) / double(budget); } 40 }; 41 42 struct UnlimitedBudget {}; 43 44 /* 45 * This class describes a limit to the amount of work to be performed in a GC 46 * slice, so that we can return to the mutator without pausing for too long. The 47 * budget may be based on a deadline time or an amount of work to be performed, 48 * or may be unlimited. 49 * 50 * To reduce the number of gettimeofday calls, we only check the time every 1000 51 * operations. 52 */ 53 class JS_PUBLIC_API SliceBudget { 54 public: 55 using InterruptRequestFlag = mozilla::Atomic<bool, mozilla::Relaxed>; 56 57 private: 58 static constexpr int64_t UnlimitedCounter = INT64_MAX; 59 60 // Most calls to isOverBudget will only check the counter value. Every N 61 // steps, do a more "expensive" check -- look at the current time and/or 62 // check the atomic interrupt flag. 63 static constexpr int64_t StepsPerExpensiveCheck = 1000; 64 65 // How many steps to count before checking the time and possibly the interrupt 66 // flag. 67 int64_t counter = StepsPerExpensiveCheck; 68 69 // External flag to request the current slice to be interrupted 70 // (and return isOverBudget() early.) Applies only to time-based budgets. 71 InterruptRequestFlag* interruptRequested = nullptr; 72 73 // Configuration 74 mozilla::Variant<TimeBudget, WorkBudget, UnlimitedBudget> budget; 75 76 // This SliceBudget is considered interrupted from the time isOverBudget() 77 // finds the interrupt flag set. 78 bool interrupted = false; 79 80 public: 81 // Whether this slice is running in (predicted to be) idle time. 82 // Only used for recording in the profile. 83 bool idle = false; 84 85 // Whether this slice was given an extended budget, larger than 86 // the predicted idle time. 87 bool extended = false; 88 89 // Temporarily allow going over budget. (isOverBudget() will return false, 90 // though step() will still have an effect and will matter once this is set 91 // back to false.) 92 bool keepGoing = false; 93 94 private: 95 explicit SliceBudget(InterruptRequestFlag* irqPtr) 96 : counter(irqPtr ? StepsPerExpensiveCheck : UnlimitedCounter), 97 interruptRequested(irqPtr), 98 budget(UnlimitedBudget()) {} 99 100 bool checkOverBudget(); 101 102 public: 103 // Use to create an unlimited budget. 104 static SliceBudget unlimited() { return SliceBudget(nullptr); } 105 106 // Instantiate as SliceBudget(TimeBudget(n)). 107 explicit SliceBudget(TimeBudget time, 108 InterruptRequestFlag* interrupt = nullptr); 109 110 explicit SliceBudget(mozilla::TimeDuration duration, 111 InterruptRequestFlag* interrupt = nullptr) 112 : SliceBudget(TimeBudget(duration.ToMilliseconds()), interrupt) {} 113 114 // Instantiate as SliceBudget(WorkBudget(n)). 115 explicit SliceBudget(WorkBudget work); 116 117 // Register having performed the given number of steps (counted against a 118 // work budget, or progress towards the next time or callback check). 119 void step(uint64_t steps = 1) { 120 MOZ_ASSERT(steps > 0); 121 counter -= steps; 122 } 123 124 // Force an "expensive" (time) check on the next call to isOverBudget. Useful 125 // when switching between major phases of an operation like a cycle 126 // collection. 127 void forceCheck() { 128 if (isTimeBudget()) { 129 counter = 0; 130 } 131 } 132 133 bool isOverBudget() { 134 return counter <= 0 && !keepGoing && checkOverBudget(); 135 } 136 137 // Return the fraction of progress towards the deadline. Note that this can 138 // return values outside of the range [0,1]. 139 double progress() const { 140 if (isUnlimited()) { 141 return 0.0; 142 } 143 if (isTimeBudget()) { 144 return budget.as<TimeBudget>().progress(mozilla::TimeStamp::Now()); 145 } 146 return budget.as<WorkBudget>().progress(workBudget() - counter); 147 } 148 149 bool isWorkBudget() const { return budget.is<WorkBudget>(); } 150 bool isTimeBudget() const { return budget.is<TimeBudget>(); } 151 bool isUnlimited() const { return budget.is<UnlimitedBudget>(); } 152 153 mozilla::TimeDuration timeBudgetDuration() const { 154 return budget.as<TimeBudget>().budget; 155 } 156 int64_t timeBudget() const { return timeBudgetDuration().ToMilliseconds(); } 157 int64_t workBudget() const { return budget.as<WorkBudget>().budget; } 158 159 mozilla::TimeStamp deadline() const { 160 return budget.as<TimeBudget>().deadline; 161 } 162 163 int describe(char* buffer, size_t maxlen) const; 164 }; 165 166 } // namespace JS 167 168 #endif /* js_SliceBudget_h */