Optimize pending job queue iteration.
Batch the jobs pulled during iteration so that we pull several of an app's jobs from the queue sequentially (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)) for A <= 33, where A = # apps and J = average # jobs per app. Bug: 204924801 Test: atest FrameworksServicesTests:PendingJobQueueTest Change-Id: I4d3dc17afb4d4ab0fb573d75a319bdb20a68c634
This commit is contained in:
@@ -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;
|
||||
@@ -59,6 +60,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) {
|
||||
@@ -132,16 +148,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;
|
||||
@@ -179,6 +223,11 @@ class PendingJobQueue {
|
||||
mNeedToResetIterators = true;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setOptimizeIteration(boolean optimize) {
|
||||
mOptimizeIteration = optimize;
|
||||
}
|
||||
|
||||
int size() {
|
||||
return mSize;
|
||||
}
|
||||
|
||||
@@ -212,13 +212,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++;
|
||||
}
|
||||
}
|
||||
@@ -380,7 +393,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 (for a UID) are sorted by override state
|
||||
@@ -440,6 +455,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) {
|
||||
|
||||
Reference in New Issue
Block a user