Merge "Optimize pending job queue iteration." into tm-dev

This commit is contained in:
Kweku Adams
2022-03-23 03:59:19 +00:00
committed by Android (Google) Code Review
2 changed files with 72 additions and 7 deletions

View File

@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.util.Pools;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.job.controllers.JobStatus;
import java.util.ArrayList;
@@ -66,6 +67,21 @@ class PendingJobQueue {
private int mSize = 0;
/**
* Whether to batch iteration so that we pull several of an app's jobs from the queue at the
* same time (resulting in some out of order pulls) instead of pulling purely based on the
* sort order. Batching it this way will mean we try to run several jobs of the same app at the
* same, resulting in fewer process restarts, and can allow the iteration runtime to amortize
* to O(A*J) instead of O(A*J*log(A)), where A = # apps and J = average # jobs per app.
*/
private boolean mOptimizeIteration = true;
/**
* Number of jobs that have been pulled from the queue in succession. Used when
* {@link #mOptimizeIteration} is true to know when to switch to the next AppJobQueue.
*/
private int mPullCount = 0;
private boolean mNeedToResetIterators = false;
void add(@NonNull JobStatus job) {
@@ -139,16 +155,44 @@ class PendingJobQueue {
mOrderedQueues.offer(ajq);
}
mNeedToResetIterators = false;
// Reset the pull count when the front of the queue changes.
mPullCount = 0;
} else if (mOrderedQueues.size() == 0) {
// Something significant changed, so the priority queue was cleared. Lazily regenerate
// the queue.
for (int i = mCurrentQueues.size() - 1; i >= 0; --i) {
final AppJobQueue ajq = mCurrentQueues.valueAt(i);
mOrderedQueues.offer(ajq);
}
// Reset the pull count when the front of the queue changes.
mPullCount = 0;
}
final AppJobQueue earliestQueue = mOrderedQueues.poll();
final int numQueues = mOrderedQueues.size();
if (numQueues == 0) {
return null;
}
// Increase the pull limit at a slightly faster rate than log(A) increases (until A>=33).
// The pull limit increase is intended to balance fairness (one app can't starve out others)
// with efficiency (reducing process restarts).
// 1-4 apps --> pullLimit = 1, 5-8 apps --> pullLimit = 2, 9+ apps --> pullLimit = 3
final int pullLimit = mOptimizeIteration ? Math.min(3, ((numQueues - 1) >>> 2) + 1) : 1;
final AppJobQueue earliestQueue = mOrderedQueues.peek();
if (earliestQueue != null) {
JobStatus job = earliestQueue.next();
mOrderedQueues.offer(earliestQueue);
final JobStatus job = earliestQueue.next();
// Change the front of the queue if we've pulled pullLimit jobs from the current head
// or the current head has no more jobs to provide.
if (++mPullCount >= pullLimit
|| earliestQueue.peekNextTimestamp() == AppJobQueue.NO_NEXT_TIMESTAMP) {
mOrderedQueues.poll();
if (earliestQueue.peekNextTimestamp() != AppJobQueue.NO_NEXT_TIMESTAMP) {
// No need to put back in the queue if it has no more jobs to give.
mOrderedQueues.offer(earliestQueue);
}
// Reset the pull count when the front of the queue changes.
mPullCount = 0;
}
return job;
}
return null;
@@ -186,6 +230,11 @@ class PendingJobQueue {
mNeedToResetIterators = true;
}
@VisibleForTesting
void setOptimizeIteration(boolean optimize) {
mOptimizeIteration = optimize;
}
int size() {
return mSize;
}

View File

@@ -211,13 +211,26 @@ public class PendingJobQueueTest {
jobQueue.add(eC11);
checkPendingJobInvariants(jobQueue);
final JobStatus[] expectedOrder = new JobStatus[]{
eA7, rA1, eB6, rB2, eC3, rD4, eE5, eF9, rH8, rF8, eC11, rC10, rG12, rG13, eE14};
int idx = 0;
JobStatus job;
final JobStatus[] expectedPureOrder = new JobStatus[]{
eC3, rD4, eE5, eB6, rB2, eA7, rA1, rH8, eF9, rF8, eC11, rC10, rG12, rG13, eE14};
int idx = 0;
jobQueue.setOptimizeIteration(false);
jobQueue.resetIterator();
while ((job = jobQueue.next()) != null) {
assertEquals("List wasn't correctly sorted @ index " + idx,
expectedOrder[idx].getJobId(), job.getJobId());
expectedPureOrder[idx].getJobId(), job.getJobId());
idx++;
}
final JobStatus[] expectedOptimizedOrder = new JobStatus[]{
eC3, eC11, rD4, eE5, eE14, eB6, rB2, eA7, rA1, rH8, eF9, rF8, rC10, rG12, rG13};
idx = 0;
jobQueue.setOptimizeIteration(true);
jobQueue.resetIterator();
while ((job = jobQueue.next()) != null) {
assertEquals("Optimized list wasn't correctly sorted @ index " + idx,
expectedOptimizedOrder[idx].getJobId(), job.getJobId());
idx++;
}
}
@@ -379,7 +392,9 @@ public class PendingJobQueueTest {
JobStatus job;
jobQueue.resetIterator();
int count = 0;
while ((job = jobQueue.next()) != null) {
count++;
final int uid = job.getSourceUid();
// Invariant #1: All jobs are sorted by override state
@@ -436,6 +451,7 @@ public class PendingJobQueueTest {
fail("UID " + uid + " had an EJ ordered after a regular job");
}
}
assertEquals("Iterator didn't go through all jobs", jobQueue.size(), count);
}
private static String testJobToString(JobStatus job) {