Skip to content

Commit a81d563

Browse files
Moved prioritisedJobs.enqueue the end of the layout of the DefaultActorImpl to minimise false sharing
1 parent 528bba4 commit a81d563

File tree

1 file changed

+59
-33
lines changed

1 file changed

+59
-33
lines changed

stdlib/public/Concurrency/Actor.cpp

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -998,44 +998,65 @@ static_assert(sizeof(ActiveActorStatus) == ACTIVE_ACTOR_STATUS_SIZE,
998998
/// exist yet. As a result, the subset of rules that currently apply
999999
/// are (1), (3), (5), (6).
10001000
class DefaultActorImpl : public HeapObject {
1001+
struct Header {
10011002
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1002-
// If actors are locks, we don't need to maintain any extra bookkeeping in the
1003-
// ActiveActorStatus since all threads which are contending will block
1004-
// synchronously, no job queue is needed and the lock will handle all priority
1005-
// escalation logic
1006-
Mutex drainLock;
1003+
// If actors are locks, we don't need to maintain any extra bookkeeping in the
1004+
// ActiveActorStatus since all threads which are contending will block
1005+
// synchronously, no job queue is needed and the lock will handle all priority
1006+
// escalation logic
1007+
Mutex drainLock;
10071008
#else
1008-
// Note: There is some padding that is added here by the compiler in order to
1009-
// enforce alignment. This is space that is available for us to use in
1010-
// the future
1011-
alignas(sizeof(ActiveActorStatus)) char StatusStorage[sizeof(ActiveActorStatus)];
1009+
// Note: There is some padding that is added here by the compiler in order to
1010+
// enforce alignment. This is space that is available for us to use in
1011+
// the future
1012+
alignas(sizeof(ActiveActorStatus)) char StatusStorage[sizeof(ActiveActorStatus)];
1013+
#endif
1014+
// TODO (rokhinip): Make this a flagset
1015+
bool isDistributedRemoteActor;
1016+
} header;
10121017

1018+
#if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
10131019
using SimpleQueue = swift::SimpleQueue<Job *, JobQueueTraits>;
10141020
using PriorityQueue = swift::PriorityQueue<Job *, JobQueueTraits>;
10151021

1016-
// When enqueued, jobs are atomically added to a linked list with the head
1017-
// stored inside ActiveActorStatus. This list contains jobs in the LIFO order
1018-
// regardless of their priorities.
1019-
//
1020-
// After that processing thread collects them and adds to the
1021-
// `prioritisedJobs` while holding the actor lock.
1022-
//
1023-
// TODO: Move this to the end of the actor memory to minimise false sharing
1024-
//
1025-
PriorityQueue prioritisedJobs;
1022+
struct Footer {
1023+
// When enqueued, jobs are atomically added to a linked list with the head
1024+
// stored inside ActiveActorStatus. This list contains jobs in the LIFO order
1025+
// regardless of their priorities.
1026+
//
1027+
// After that processing thread collects them and adds to the
1028+
// `prioritisedJobs` while holding the actor lock.
1029+
//
1030+
PriorityQueue prioritisedJobs;
1031+
};
1032+
1033+
struct Padding {
1034+
enum {
1035+
// Available space
1036+
x1 = (int)(sizeof(void *) * NumWords_DefaultActor),
1037+
// ... subtracting size of footer
1038+
x2 = x1 - (int)sizeof(Footer),
1039+
// ... ensuring footer is aligned
1040+
x3 = x2 - (int)(x2 % alignof(Footer)),
1041+
// ... subtracting size of header
1042+
x4 = x3 - (int)sizeof(Header),
1043+
// ... subtracting padding before header
1044+
size = x4 - (int)((alignof(Header) - sizeof(HeapObject) % alignof(Header)) % alignof(Header))
1045+
};
1046+
};
1047+
char padding[Padding::size];
1048+
Footer footer;
10261049
#endif
1027-
// TODO (rokhinip): Make this a flagset
1028-
bool isDistributedRemoteActor;
10291050

10301051
public:
10311052
/// Properly construct an actor, except for the heap header.
10321053
void initialize(bool isDistributedRemote = false) {
1033-
this->isDistributedRemoteActor = isDistributedRemote;
1054+
this->header.isDistributedRemoteActor = isDistributedRemote;
10341055
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
1035-
new (&this->drainLock) Mutex();
1056+
new (&this->header.drainLock) Mutex();
10361057
#else
10371058
_status().store(ActiveActorStatus(), std::memory_order_relaxed);
1038-
new (&this->prioritisedJobs) PriorityQueue();
1059+
new (&this->footer.prioritisedJobs) PriorityQueue();
10391060
#endif
10401061
SWIFT_TASK_DEBUG_LOG("Creating default actor %p", this);
10411062
concurrency::trace::actor_create(this);
@@ -1075,20 +1096,25 @@ class DefaultActorImpl : public HeapObject {
10751096

10761097
#if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
10771098
swift::atomic<ActiveActorStatus> &_status() {
1078-
return reinterpret_cast<swift::atomic<ActiveActorStatus>&> (this->StatusStorage);
1099+
return reinterpret_cast<swift::atomic<ActiveActorStatus>&> (this->header.StatusStorage);
10791100
}
10801101

10811102
const swift::atomic<ActiveActorStatus> &_status() const {
1082-
return reinterpret_cast<const swift::atomic<ActiveActorStatus>&> (this->StatusStorage);
1103+
return reinterpret_cast<const swift::atomic<ActiveActorStatus>&> (this->header.StatusStorage);
10831104
}
10841105

10851106
// Only for static assert use below, not for actual use otherwise
10861107
static constexpr size_t offsetOfActiveActorStatus() {
10871108
#pragma clang diagnostic push
10881109
#pragma clang diagnostic ignored "-Winvalid-offsetof"
1089-
return offsetof(DefaultActorImpl, StatusStorage);
1110+
return offsetof(DefaultActorImpl, header.StatusStorage);
10901111
#pragma clang diagnostic pop
10911112
}
1113+
1114+
PriorityQueue &_prioritisedJobs() {
1115+
return footer.prioritisedJobs;
1116+
}
1117+
10921118
#endif /* !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS */
10931119

10941120
private:
@@ -1417,7 +1443,7 @@ void DefaultActorImpl::processJobs() {
14171443
auto job = jobs.head;
14181444
while (job) {
14191445
auto next = getNextJob(job);
1420-
prioritisedJobs.enqueue(job);
1446+
_prioritisedJobs().enqueue(job);
14211447
job = next;
14221448
}
14231449
}
@@ -1427,8 +1453,8 @@ Job *DefaultActorImpl::drainOne() {
14271453
SWIFT_TASK_DEBUG_LOG("Draining one job from default actor %p", this);
14281454

14291455
processJobs();
1430-
traceJobQueue(this, prioritisedJobs.peek());
1431-
auto firstJob = prioritisedJobs.dequeue();
1456+
traceJobQueue(this, _prioritisedJobs().peek());
1457+
auto firstJob = _prioritisedJobs().dequeue();
14321458
if (!firstJob) {
14331459
SWIFT_TASK_DEBUG_LOG("No jobs to drain on actor %p", this);
14341460
} else {
@@ -1673,7 +1699,7 @@ retry:;
16731699
std::memory_order_relaxed)) {
16741700
_swift_tsan_acquire(this);
16751701
if (assertNoJobs) {
1676-
assert(prioritisedJobs.empty());
1702+
assert(_prioritisedJobs().empty());
16771703
}
16781704
traceActorStateTransition(this, oldState, newState, distributedActorIsRemote);
16791705
return true;
@@ -1724,7 +1750,7 @@ bool DefaultActorImpl::unlock(bool forceUnlock)
17241750

17251751
auto newState = oldState;
17261752
// Lock is still held at this point, so it is safe to access prioritisedJobs
1727-
if (!prioritisedJobs.empty() || oldState.getFirstUnprioritisedJob()) {
1753+
if (!_prioritisedJobs().empty() || oldState.getFirstUnprioritisedJob()) {
17281754
// There is work left to do, don't unlock the actor
17291755
if (!forceUnlock) {
17301756
SWIFT_TASK_DEBUG_LOG("Unlock-ing actor %p failed", this);
@@ -2245,5 +2271,5 @@ bool swift::swift_distributed_actor_is_remote(HeapObject *_actor) {
22452271
}
22462272

22472273
bool DefaultActorImpl::isDistributedRemote() {
2248-
return this->isDistributedRemoteActor;
2274+
return this->header.isDistributedRemoteActor;
22492275
}

0 commit comments

Comments
 (0)