Skip to content

Commit 0abf211

Browse files
Fixed quadratic performance of ListMerger when each executed job creates 2+ new jobs of the same priority
See https://forums.swift.org/t/quadratic-performance-of-the-listmerger-in-specific-use-case/69393
1 parent 6f3146d commit 0abf211

File tree

5 files changed

+89
-58
lines changed

5 files changed

+89
-58
lines changed

include/swift/Basic/ListMerger.h

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#define SWIFT_BASIC_LISTMERGER_H
2020

2121
#include <assert.h>
22+
#include <tuple>
2223

2324
namespace swift {
2425

@@ -62,13 +63,31 @@ namespace swift {
6263
/// to the merger and before being released except by the merger.
6364
template <class Node, class NodeTraits>
6465
class ListMerger {
66+
public:
67+
class LastInsertionPoint {
68+
friend class ListMerger;
69+
Node node = Node();
70+
bool isKnownLastOfEquals = false;
71+
72+
public:
73+
LastInsertionPoint() {}
74+
75+
void nodeWasRemoved(Node removedNode) {
76+
if (node == removedNode) {
77+
*this = LastInsertionPoint();
78+
}
79+
}
80+
};
81+
82+
private:
6583
Node root;
66-
Node lastInsertionPoint = Node();
67-
bool lastInsertionPointIsKnownLastOfEquals = false;
84+
LastInsertionPoint lastInsertionPoint;
85+
6886
public:
6987
/// Construct a merger with the given sorted list as its current list.
70-
ListMerger(Node initialList = Node())
71-
: root(initialList) {}
88+
ListMerger(Node initialList = Node(),
89+
LastInsertionPoint insertionPoint = LastInsertionPoint())
90+
: root(initialList), lastInsertionPoint(insertionPoint) {}
7291

7392
/// Add a single node to this merger's current list.
7493
///
@@ -86,7 +105,7 @@ class ListMerger {
86105
Node stopper = Node();
87106

88107
// If we have a previous insertion point, compare against it.
89-
if (Node lastIP = lastInsertionPoint) {
108+
if (Node lastIP = lastInsertionPoint.node) {
90109
int comparison = NodeTraits::compare(lastIP, newNode);
91110

92111
// If it compares equal, put the new node immediately after the
@@ -169,7 +188,7 @@ class ListMerger {
169188

170189
// If we have a previous insertion point, check for the presumed-common
171190
// case that we're inserting something that should immediately follow it.
172-
if (auto lastIP = lastInsertionPoint) {
191+
if (auto lastIP = lastInsertionPoint.node) {
173192
lastIP = findLastOfEqualsFromLastIP(lastIP);
174193

175194
// Compare against the next node after lastIP, if it exists.
@@ -246,7 +265,7 @@ class ListMerger {
246265

247266
// If we have a previous insertion point, compare the new root
248267
// against it.
249-
if (Node lastIP = lastInsertionPoint) {
268+
if (Node lastIP = lastInsertionPoint.node) {
250269
int comparison = NodeTraits::compare(lastIP, rootOfNewList);
251270

252271
// If it compares equal, we've got an insertion point where
@@ -341,27 +360,27 @@ class ListMerger {
341360

342361
/// Get the current list that's been built up, and clear the internal
343362
/// state of this merger.
344-
Node release() {
345-
Node result = root;
363+
std::tuple<Node, LastInsertionPoint> release() {
364+
auto result = std::make_tuple(root, lastInsertionPoint);
346365
root = Node();
347-
lastInsertionPoint = Node();
366+
lastInsertionPoint = LastInsertionPoint();
348367
return result;
349368
}
350369

351370
private:
352371
/// Set the last point at which we inserted a node, and specify
353372
/// whether we know it was the last in its sequence of equals.
354373
void setLastInsertionPoint(Node lastIP, bool knownEndOfEquals) {
355-
lastInsertionPoint = lastIP;
356-
lastInsertionPointIsKnownLastOfEquals = knownEndOfEquals;
374+
lastInsertionPoint.node = lastIP;
375+
lastInsertionPoint.isKnownLastOfEquals = knownEndOfEquals;
357376
}
358377

359378
/// Given the value of lastInsertionPoint (passed in to avoid
360379
/// reloading it), find the last node in the sequence of equals that
361380
/// contains it.
362381
Node findLastOfEqualsFromLastIP(Node lastIP) const {
363-
assert(lastIP == lastInsertionPoint);
364-
if (!lastInsertionPointIsKnownLastOfEquals)
382+
assert(lastIP == lastInsertionPoint.node);
383+
if (!lastInsertionPoint.isKnownLastOfEquals)
365384
return findLastOfEquals(lastIP);
366385
return lastIP;
367386
}

stdlib/public/Concurrency/Actor.cpp

Lines changed: 51 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,35 @@ class alignas(sizeof(void *) * 2) ActiveActorStatus {
979979
}
980980
};
981981

982+
#if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
983+
984+
/// Given that a job is enqueued normally on a default actor, get/set
985+
/// the next job in the actor's queue.
986+
static JobRef getNextJobInQueue(Job *job) {
987+
return *reinterpret_cast<JobRef *>(job->SchedulerPrivate);
988+
}
989+
static void setNextJobInQueue(Job *job, JobRef next) {
990+
*reinterpret_cast<JobRef *>(job->SchedulerPrivate) = next;
991+
}
992+
993+
namespace {
994+
995+
struct JobQueueTraits {
996+
static Job *getNext(Job *job) {
997+
return getNextJobInQueue(job).getAsPreprocessedJob();
998+
}
999+
static void setNext(Job *job, Job *next) {
1000+
setNextJobInQueue(job, JobRef::getPreprocessed(next));
1001+
}
1002+
static int compare(Job *lhs, Job *rhs) {
1003+
return descendingPriorityOrder(lhs->getPriority(), rhs->getPriority());
1004+
}
1005+
};
1006+
1007+
} // end anonymous namespace
1008+
1009+
#endif
1010+
9821011
#if SWIFT_CONCURRENCY_ENABLE_PRIORITY_ESCALATION && SWIFT_POINTER_IS_4_BYTES
9831012
#define ACTIVE_ACTOR_STATUS_SIZE (4 * (sizeof(uintptr_t)))
9841013
#else
@@ -1050,10 +1079,13 @@ class DefaultActorImpl : public HeapObject {
10501079
// enforce alignment. This is space that is available for us to use in
10511080
// the future
10521081
alignas(sizeof(ActiveActorStatus)) char StatusStorage[sizeof(ActiveActorStatus)];
1082+
1083+
using ListMerger = swift::ListMerger<Job *, JobQueueTraits>;
1084+
ListMerger::LastInsertionPoint lastInsertionPoint =
1085+
ListMerger::LastInsertionPoint();
10531086
#endif
10541087
// TODO (rokhinip): Make this a flagset
10551088
bool isDistributedRemoteActor;
1056-
10571089
public:
10581090
/// Properly construct an actor, except for the heap header.
10591091
void initialize(bool isDistributedRemote = false) {
@@ -1126,6 +1158,10 @@ class DefaultActorImpl : public HeapObject {
11261158
/// It can be done when actor transitions from Idle to Scheduled or
11271159
/// when actor gets a priority override and we schedule a stealer.
11281160
void scheduleActorProcessJob(JobPriority priority);
1161+
1162+
Job *preprocessQueue(JobRef start);
1163+
Job *preprocessQueue(JobRef unprocessedStart, JobRef unprocessedEnd,
1164+
Job *existingProcessedJobsToMergeInto);
11291165
#endif /* !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS */
11301166

11311167
void deallocateUnconditional();
@@ -1201,31 +1237,6 @@ static NonDefaultDistributedActorImpl *asImpl(NonDefaultDistributedActor *actor)
12011237
/*****************************************************************************/
12021238

12031239
#if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1204-
/// Given that a job is enqueued normally on a default actor, get/set
1205-
/// the next job in the actor's queue.
1206-
static JobRef getNextJobInQueue(Job *job) {
1207-
return *reinterpret_cast<JobRef*>(job->SchedulerPrivate);
1208-
}
1209-
static void setNextJobInQueue(Job *job, JobRef next) {
1210-
*reinterpret_cast<JobRef*>(job->SchedulerPrivate) = next;
1211-
}
1212-
1213-
namespace {
1214-
1215-
struct JobQueueTraits {
1216-
static Job *getNext(Job *job) {
1217-
return getNextJobInQueue(job).getAsPreprocessedJob();
1218-
}
1219-
static void setNext(Job *job, Job *next) {
1220-
setNextJobInQueue(job, JobRef::getPreprocessed(next));
1221-
}
1222-
static int compare(Job *lhs, Job *rhs) {
1223-
return descendingPriorityOrder(lhs->getPriority(), rhs->getPriority());
1224-
}
1225-
};
1226-
1227-
} // end anonymous namespace
1228-
12291240

12301241
// Called with the actor drain lock held
12311242
//
@@ -1238,15 +1249,14 @@ struct JobQueueTraits {
12381249
// and the previous start. We can then process these jobs and merge them into
12391250
// the already processed list of jobs from the previous iteration of
12401251
// preprocessQueue
1241-
static Job *
1242-
preprocessQueue(JobRef unprocessedStart, JobRef unprocessedEnd, Job *existingProcessedJobsToMergeInto)
1243-
{
1252+
Job *DefaultActorImpl::preprocessQueue(JobRef unprocessedStart,
1253+
JobRef unprocessedEnd,
1254+
Job *existingProcessedJobsToMergeInto) {
12441255
assert(existingProcessedJobsToMergeInto != NULL);
12451256
assert(unprocessedStart.needsPreprocessing());
12461257
assert(unprocessedStart.getAsJob() != unprocessedEnd.getAsJob());
12471258

12481259
// Build up a list of jobs we need to preprocess
1249-
using ListMerger = swift::ListMerger<Job*, JobQueueTraits>;
12501260
ListMerger jobsToProcess;
12511261

12521262
// Get just the prefix list of unprocessed jobs
@@ -1261,19 +1271,20 @@ preprocessQueue(JobRef unprocessedStart, JobRef unprocessedEnd, Job *existingPro
12611271
}
12621272

12631273
// Finish processing the unprocessed jobs
1264-
Job *newProcessedJobs = jobsToProcess.release();
1274+
Job *newProcessedJobs = std::get<0>(jobsToProcess.release());
12651275
assert(newProcessedJobs);
12661276

1267-
ListMerger mergedList(existingProcessedJobsToMergeInto);
1277+
ListMerger mergedList(existingProcessedJobsToMergeInto, lastInsertionPoint);
12681278
mergedList.merge(newProcessedJobs);
1269-
return mergedList.release();
1279+
Job *result;
1280+
std::tie(result, lastInsertionPoint) = mergedList.release();
1281+
return result;
12701282
}
12711283

12721284
// Called with the actor drain lock held.
12731285
//
12741286
// Preprocess the queue starting from the top
1275-
static Job *
1276-
preprocessQueue(JobRef start) {
1287+
Job *DefaultActorImpl::preprocessQueue(JobRef start) {
12771288
if (!start) {
12781289
return NULL;
12791290
}
@@ -1286,7 +1297,6 @@ preprocessQueue(JobRef start) {
12861297
// There exist some jobs which haven't been preprocessed
12871298

12881299
// Build up a list of jobs we need to preprocess
1289-
using ListMerger = swift::ListMerger<Job*, JobQueueTraits>;
12901300
ListMerger jobsToProcess;
12911301

12921302
Job *wellFormedListStart = NULL;
@@ -1309,18 +1319,19 @@ preprocessQueue(JobRef start) {
13091319
}
13101320

13111321
// Finish processing the unprocessed jobs
1312-
auto processedJobHead = jobsToProcess.release();
1322+
auto processedJobHead = std::get<0>(jobsToProcess.release());
13131323
assert(processedJobHead);
13141324

13151325
Job *firstJob = NULL;
13161326
if (wellFormedListStart) {
13171327
// Merge it with already known well formed list if we have one.
1318-
ListMerger mergedList(wellFormedListStart);
1328+
ListMerger mergedList(wellFormedListStart, lastInsertionPoint);
13191329
mergedList.merge(processedJobHead);
1320-
firstJob = mergedList.release();
1330+
std::tie(firstJob, lastInsertionPoint) = mergedList.release();
13211331
} else {
13221332
// Nothing to merge with, just return the head we already have
13231333
firstJob = processedJobHead;
1334+
lastInsertionPoint = ListMerger::LastInsertionPoint();
13241335
}
13251336

13261337
return firstJob;
@@ -1526,6 +1537,7 @@ Job * DefaultActorImpl::drainOne() {
15261537
if (_status().compare_exchange_weak(oldState, newState,
15271538
/* success */ std::memory_order_relaxed,
15281539
/* failure */ std::memory_order_relaxed)) {
1540+
lastInsertionPoint.nodeWasRemoved(firstJob);
15291541
SWIFT_TASK_DEBUG_LOG("Drained first job %p from actor %p", firstJob, this);
15301542
traceActorStateTransition(this, oldState, newState, distributedActorIsRemote);
15311543
concurrency::trace::actor_dequeue(this, firstJob);

stdlib/public/Concurrency/CooperativeGlobalExecutor.inc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ static void swift_task_enqueueGlobalImpl(Job *job) {
108108

109109
JobQueueMerger merger(JobQueue);
110110
merger.insert(job);
111-
JobQueue = merger.release();
111+
JobQueue = std::get<0>(merger.release());
112112
}
113113

114114
/// Enqueues a task on the main executor.
@@ -202,7 +202,7 @@ static void recognizeReadyDelayedJobs() {
202202
nextDelayedJob = next;
203203
}
204204

205-
JobQueue = readyJobs.release();
205+
JobQueue = std::get<0>(readyJobs.release());
206206
DelayedJobQueue = nextDelayedJob;
207207
}
208208

utils/test-list-merger/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
SWIFT_SRCROOT=${CURDIR}/../..
22
SRCROOT=${SWIFT_SRCROOT}/..
33
LLVM_SRCROOT=${SRCROOT}/llvm/
4-
LLVM_OBJROOT=${SRCROOT}/build/Ninja-DebugAssert/llvm-macosx-x86_64
4+
LLVM_OBJROOT=${SRCROOT}/build/Ninja-DebugAssert/llvm-macosx-$(shell uname -m)
55

66
HEADERS=${SWIFT_SRCROOT}/include/swift/Basic/ListMerger.h
77

utils/test-list-merger/TestListMerger.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ static void runInsertAndMergeTest(llvm::ArrayRef<Instruction> values) {
161161
assert(!lastMergeEntry && "ended while still building a merge list");
162162

163163
entries.sort(creationOrder);
164-
entries.checkSameAs(merger.release());
164+
entries.checkSameAs(std::get<0>(merger.release()));
165165
}
166166

167167
static void runInsertAtFrontTest(llvm::ArrayRef<unsigned> values) {
@@ -171,7 +171,7 @@ static void runInsertAtFrontTest(llvm::ArrayRef<unsigned> values) {
171171
merger.insertAtFront(entries.create(value));
172172
}
173173
entries.sort(reverseCreationOrder);
174-
entries.checkSameAs(merger.release());
174+
entries.checkSameAs(std::get<0>(merger.release()));
175175
}
176176

177177
static void runConcreteTests() {

0 commit comments

Comments
 (0)