1325254 - optimize TimerThread data structures

1325254 P1 Make TimerThread::mTimers store RefPtr<nsTimerImpl> objects.

1325254 P2 Make TimerThread list store an entry struct and just drop nsTimerImpl ref on cancel.

1325254 P3 Sort TimerThread list as a binary heap.

1325254 P4 Dynamically allocate Entry structs stored in TimerThread::mTimers.

1325254 P5 Make nsITimer::Cancel() O(c).
This commit is contained in:
win7-7 2024-04-27 20:04:28 +03:00 committed by wuggy
commit f109b02bc3
4 changed files with 157 additions and 43 deletions

View file

@ -335,7 +335,7 @@ TimerThread::Shutdown()
return NS_ERROR_NOT_INITIALIZED;
}
nsTArray<nsTimerImpl*> timers;
nsTArray<UniquePtr<Entry>> timers;
{
// lock scope
MonitorAutoLock lock(mMonitor);
@ -354,14 +354,15 @@ TimerThread::Shutdown()
// might potentially call some code reentering the same lock
// that leads to unexpected behavior or deadlock.
// See bug 422472.
timers.SwapElements(mTimers);
mTimers.SwapElements(timers);
}
uint32_t timersCount = timers.Length();
for (uint32_t i = 0; i < timersCount; i++) {
nsTimerImpl* timer = timers[i];
timer->Cancel();
ReleaseTimerInternal(timer);
RefPtr<nsTimerImpl> timer = timers[i]->Take();
if (timer) {
timer->Cancel();
}
}
mThread->Shutdown(); // wait for the thread to die
@ -432,12 +433,11 @@ TimerThread::Run()
} else {
waitFor = PR_INTERVAL_NO_TIMEOUT;
TimeStamp now = TimeStamp::Now();
nsTimerImpl* timer = nullptr;
RemoveLeadingCanceledTimersInternal();
if (!mTimers.IsEmpty()) {
timer = mTimers[0];
if (now >= timer->mTimeout || forceRunThisTimer) {
if (now >= mTimers[0]->Value()->mTimeout || forceRunThisTimer) {
next:
// NB: AddRef before the Release under RemoveTimerInternal to avoid
// mRefCnt passing through zero, in case all other refs than the one
@ -445,9 +445,8 @@ TimerThread::Run()
// must be racing with us, blocked in gThread->RemoveTimer waiting
// for TimerThread::mMonitor, under nsTimerImpl::Release.
RefPtr<nsTimerImpl> timerRef(timer);
RemoveTimerInternal(timer);
timer = nullptr;
RefPtr<nsTimerImpl> timerRef(mTimers[0]->Take());
RemoveFirstTimerInternal();
MOZ_LOG(GetTimerLog(), LogLevel::Debug,
("Timer thread woke up %fms from when it was supposed to\n",
@ -489,10 +488,10 @@ TimerThread::Run()
}
}
if (!mTimers.IsEmpty()) {
timer = mTimers[0];
RemoveLeadingCanceledTimersInternal();
TimeStamp timeout = timer->mTimeout;
if (!mTimers.IsEmpty()) {
TimeStamp timeout = mTimers[0]->Value()->mTimeout;
// Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer
// is due now or overdue.
@ -558,13 +557,12 @@ TimerThread::AddTimer(nsTimerImpl* aTimer)
}
// Add the timer to our list.
int32_t i = AddTimerInternal(aTimer);
if (i < 0) {
if(!AddTimerInternal(aTimer)) {
return NS_ERROR_OUT_OF_MEMORY;
}
// Awaken the timer thread.
if (mWaiting && i == 0) {
if (mWaiting && mTimers[0]->Value() == aTimer) {
mNotified = true;
mMonitor.Notify();
}
@ -640,24 +638,23 @@ TimerThread::FindNextFireTimeForCurrentThread(TimeStamp aDefault, uint32_t aSear
}
// This function must be called from within a lock
int32_t
bool
TimerThread::AddTimerInternal(nsTimerImpl* aTimer)
{
mMonitor.AssertCurrentThreadOwns();
if (mShutdown) {
return -1;
return false;
}
TimeStamp now = TimeStamp::Now();
TimerAdditionComparator c(now, aTimer);
nsTimerImpl** insertSlot = mTimers.InsertElementSorted(aTimer, c);
if (!insertSlot) {
return -1;
UniquePtr<Entry>* entry = mTimers.AppendElement(
MakeUnique<Entry>(now, aTimer->mTimeout, aTimer), mozilla::fallible);
if (!entry) {
return false;
}
NS_ADDREF(aTimer);
std::push_heap(mTimers.begin(), mTimers.end(), Entry::UniquePtrLessThan);
#ifdef MOZ_TASK_TRACER
// Caller of AddTimer is the parent task of its timer event, so we store the
@ -665,30 +662,55 @@ TimerThread::AddTimerInternal(nsTimerImpl* aTimer)
aTimer->GetTLSTraceInfo();
#endif
return insertSlot - mTimers.Elements();
return true;
}
// Note: this function must be called from within a lock.
bool
TimerThread::RemoveTimerInternal(nsTimerImpl* aTimer)
{
mMonitor.AssertCurrentThreadOwns();
if (!mTimers.RemoveElement(aTimer)) {
if (!aTimer || !aTimer->mHolder) {
return false;
}
ReleaseTimerInternal(aTimer);
aTimer->mHolder->Forget(aTimer);
return true;
}
void
TimerThread::ReleaseTimerInternal(nsTimerImpl* aTimer)
TimerThread::RemoveLeadingCanceledTimersInternal()
{
if (!mShutdown) {
// copied to a local array before releasing in shutdown
mMonitor.AssertCurrentThreadOwns();
mMonitor.AssertCurrentThreadOwns();
// Move all canceled timers from the front of the list to
// the back of the list using std::pop_heap(). We do this
// without actually removing them from the list so we can
// modify the nsTArray in a single bulk operation.
auto sortedEnd = mTimers.end();
while (sortedEnd != mTimers.begin() && !mTimers[0]->Value()) {
std::pop_heap(mTimers.begin(), sortedEnd, Entry::UniquePtrLessThan);
--sortedEnd;
}
NS_RELEASE(aTimer);
// If there were no canceled timers then we are done.
if (sortedEnd == mTimers.end()) {
return;
}
// Finally, remove the canceled timers from the back of the
// nsTArray. Note, since std::pop_heap() uses iterators
// we must convert to nsTArray indices and number of
// elements here.
mTimers.RemoveElementsAt(sortedEnd - mTimers.begin(),
mTimers.end() - sortedEnd);
}
void
TimerThread::RemoveFirstTimerInternal()
{
mMonitor.AssertCurrentThreadOwns();
MOZ_ASSERT(!mTimers.IsEmpty());
std::pop_heap(mTimers.begin(), mTimers.end(), Entry::UniquePtrLessThan);
mTimers.RemoveElementAt(mTimers.Length() - 1);
}
already_AddRefed<nsTimerImpl>