Merge "Optimize pending job queue iteration." into tm-dev
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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user