Reduce gratuitous resets for OneShotTimer
OneShotTimer::reset just resets an internal timer thread for it to go
back to sleep. When just implemented with semaphores, this introduces a
lot of syscall churn due to posting a semaphore -> waking up the timer
thread -> having the thread go back to sleep. Instead, we can avoid
resetting the timer if we know that the thread is about to wait for a
short amount of time, and only reset the timer if the thread is in an
idle state.
This patch reduces instruction counts by 5%, and CPU cycles incurred by
an additional 5%.
Bug: 232272570
Test: bouncy ball
Change-Id: I83f968042395857237875aab8dca0e6b90d392cb
diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.cpp b/services/surfaceflinger/Scheduler/OneShotTimer.cpp
index 16f041a..9c6e56d 100644
--- a/services/surfaceflinger/Scheduler/OneShotTimer.cpp
+++ b/services/surfaceflinger/Scheduler/OneShotTimer.cpp
@@ -47,6 +47,7 @@
mInterval(interval),
mResetCallback(resetCallback),
mTimeoutCallback(timeoutCallback) {
+ mLastResetTime = std::chrono::steady_clock::time_point::min();
LOG_ALWAYS_FATAL_IF(!mClock, "Clock must not be provided");
}
@@ -116,9 +117,10 @@
auto triggerTime = mClock->now() + mInterval;
state = TimerState::WAITING;
- while (state == TimerState::WAITING) {
+ while (true) {
+ mWaiting = true;
constexpr auto zero = std::chrono::steady_clock::duration::zero();
- // Wait for mInterval time for semaphore signal.
+ // Wait for mInterval time to check if we need to reset or drop into the idle state.
struct timespec ts;
calculateTimeoutTime(std::chrono::nanoseconds(mInterval), &ts);
int result = sem_clockwait(&mSemaphore, CLOCK_MONOTONIC, &ts);
@@ -128,13 +130,21 @@
LOG_ALWAYS_FATAL("%s", ss.str().c_str());
}
+ mWaiting = false;
state = checkForResetAndStop(state);
- if (state == TimerState::RESET) {
- triggerTime = mClock->now() + mInterval;
- state = TimerState::WAITING;
- } else if (state == TimerState::WAITING && (triggerTime - mClock->now()) <= zero) {
+ if (state == TimerState::STOPPED) {
+ break;
+ }
+
+ if (state == TimerState::WAITING && (triggerTime - mClock->now()) <= zero) {
triggerTimeout = true;
state = TimerState::IDLE;
+ break;
+ }
+
+ if (state == TimerState::RESET) {
+ triggerTime = mLastResetTime.load() + mInterval;
+ state = TimerState::WAITING;
}
}
@@ -158,9 +168,14 @@
}
void OneShotTimer::reset() {
+ mLastResetTime = mClock->now();
mResetTriggered = true;
- int result = sem_post(&mSemaphore);
- LOG_ALWAYS_FATAL_IF(result, "sem_post failed");
+ // If mWaiting is true, then we are guaranteed to be in a block where we are waiting on
+ // mSemaphore for a timeout, rather than idling. So we can avoid a sem_post call since we can
+ // just check that we triggered a reset on timeout.
+ if (!mWaiting) {
+ LOG_ALWAYS_FATAL_IF(sem_post(&mSemaphore), "sem_post failed");
+ }
}
std::string OneShotTimer::dump() const {