Skip to content

Commit df48aee

Browse files
Moved prioritisedJobs.enqueue the end of the layout of the DefaultActorImpl to minimise false sharing
1 parent f7cdd42 commit df48aee

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
@@ -847,44 +847,65 @@ static_assert(sizeof(ActiveActorStatus) == ACTIVE_ACTOR_STATUS_SIZE,
847847
/// exist yet. As a result, the subset of rules that currently apply
848848
/// are (1), (3), (5), (6).
849849
class DefaultActorImpl : public HeapObject {
850+
struct Header {
850851
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
851-
// If actors are locks, we don't need to maintain any extra bookkeeping in the
852-
// ActiveActorStatus since all threads which are contending will block
853-
// synchronously, no job queue is needed and the lock will handle all priority
854-
// escalation logic
855-
Mutex drainLock;
852+
// If actors are locks, we don't need to maintain any extra bookkeeping in the
853+
// ActiveActorStatus since all threads which are contending will block
854+
// synchronously, no job queue is needed and the lock will handle all priority
855+
// escalation logic
856+
Mutex drainLock;
856857
#else
857-
// Note: There is some padding that is added here by the compiler in order to
858-
// enforce alignment. This is space that is available for us to use in
859-
// the future
860-
alignas(sizeof(ActiveActorStatus)) char StatusStorage[sizeof(ActiveActorStatus)];
858+
// Note: There is some padding that is added here by the compiler in order to
859+
// enforce alignment. This is space that is available for us to use in
860+
// the future
861+
alignas(sizeof(ActiveActorStatus)) char StatusStorage[sizeof(ActiveActorStatus)];
862+
#endif
863+
// TODO (rokhinip): Make this a flagset
864+
bool isDistributedRemoteActor;
865+
} header;
861866

867+
#if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
862868
using SimpleQueue = swift::SimpleQueue<Job *, JobQueueTraits>;
863869
using PriorityQueue = swift::PriorityQueue<Job *, JobQueueTraits>;
864870

865-
// When enqueued, jobs are atomically added to a linked list with the head
866-
// stored inside ActiveActorStatus. This list contains jobs in the LIFO order
867-
// regardless of their priorities.
868-
//
869-
// After that processing thread collects them and adds to the
870-
// `prioritisedJobs` while holding the actor lock.
871-
//
872-
// TODO: Move this to the end of the actor memory to minimise false sharing
873-
//
874-
PriorityQueue prioritisedJobs;
871+
struct Footer {
872+
// When enqueued, jobs are atomically added to a linked list with the head
873+
// stored inside ActiveActorStatus. This list contains jobs in the LIFO order
874+
// regardless of their priorities.
875+
//
876+
// After that processing thread collects them and adds to the
877+
// `prioritisedJobs` while holding the actor lock.
878+
//
879+
PriorityQueue prioritisedJobs;
880+
};
881+
882+
struct Padding {
883+
enum {
884+
// Available space
885+
x1 = (int)(sizeof(void *) * NumWords_DefaultActor),
886+
// ... subtracting size of footer
887+
x2 = x1 - (int)sizeof(Footer),
888+
// ... ensuring footer is aligned
889+
x3 = x2 - (int)(x2 % alignof(Footer)),
890+
// ... subtracting size of header
891+
x4 = x3 - (int)sizeof(Header),
892+
// ... subtracting padding before header
893+
size = x4 - (int)((alignof(Header) - sizeof(HeapObject) % alignof(Header)) % alignof(Header))
894+
};
895+
};
896+
char padding[Padding::size];
897+
Footer footer;
875898
#endif
876-
// TODO (rokhinip): Make this a flagset
877-
bool isDistributedRemoteActor;
878899

879900
public:
880901
/// Properly construct an actor, except for the heap header.
881902
void initialize(bool isDistributedRemote = false) {
882-
this->isDistributedRemoteActor = isDistributedRemote;
903+
this->header.isDistributedRemoteActor = isDistributedRemote;
883904
#if SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
884-
new (&this->drainLock) Mutex();
905+
new (&this->header.drainLock) Mutex();
885906
#else
886907
_status().store(ActiveActorStatus(), std::memory_order_relaxed);
887-
new (&this->prioritisedJobs) PriorityQueue();
908+
new (&this->footer.prioritisedJobs) PriorityQueue();
888909
#endif
889910
SWIFT_TASK_DEBUG_LOG("Creating default actor %p", this);
890911
concurrency::trace::actor_create(this);
@@ -924,20 +945,25 @@ class DefaultActorImpl : public HeapObject {
924945

925946
#if !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS
926947
swift::atomic<ActiveActorStatus> &_status() {
927-
return reinterpret_cast<swift::atomic<ActiveActorStatus>&> (this->StatusStorage);
948+
return reinterpret_cast<swift::atomic<ActiveActorStatus>&> (this->header.StatusStorage);
928949
}
929950

930951
const swift::atomic<ActiveActorStatus> &_status() const {
931-
return reinterpret_cast<const swift::atomic<ActiveActorStatus>&> (this->StatusStorage);
952+
return reinterpret_cast<const swift::atomic<ActiveActorStatus>&> (this->header.StatusStorage);
932953
}
933954

934955
// Only for static assert use below, not for actual use otherwise
935956
static constexpr size_t offsetOfActiveActorStatus() {
936957
#pragma clang diagnostic push
937958
#pragma clang diagnostic ignored "-Winvalid-offsetof"
938-
return offsetof(DefaultActorImpl, StatusStorage);
959+
return offsetof(DefaultActorImpl, header.StatusStorage);
939960
#pragma clang diagnostic pop
940961
}
962+
963+
PriorityQueue &_prioritisedJobs() {
964+
return footer.prioritisedJobs;
965+
}
966+
941967
#endif /* !SWIFT_CONCURRENCY_ACTORS_AS_LOCKS */
942968

943969
private:
@@ -1266,7 +1292,7 @@ void DefaultActorImpl::processJobs() {
12661292
auto job = jobs.head;
12671293
while (job) {
12681294
auto next = getNextJob(job);
1269-
prioritisedJobs.enqueue(job);
1295+
_prioritisedJobs().enqueue(job);
12701296
job = next;
12711297
}
12721298
}
@@ -1276,8 +1302,8 @@ Job *DefaultActorImpl::drainOne() {
12761302
SWIFT_TASK_DEBUG_LOG("Draining one job from default actor %p", this);
12771303

12781304
processJobs();
1279-
traceJobQueue(this, prioritisedJobs.peek());
1280-
auto firstJob = prioritisedJobs.dequeue();
1305+
traceJobQueue(this, _prioritisedJobs().peek());
1306+
auto firstJob = _prioritisedJobs().dequeue();
12811307
if (!firstJob) {
12821308
SWIFT_TASK_DEBUG_LOG("No jobs to drain on actor %p", this);
12831309
} else {
@@ -1522,7 +1548,7 @@ retry:;
15221548
std::memory_order_relaxed)) {
15231549
_swift_tsan_acquire(this);
15241550
if (assertNoJobs) {
1525-
assert(prioritisedJobs.empty());
1551+
assert(_prioritisedJobs().empty());
15261552
}
15271553
traceActorStateTransition(this, oldState, newState, distributedActorIsRemote);
15281554
return true;
@@ -1573,7 +1599,7 @@ bool DefaultActorImpl::unlock(bool forceUnlock)
15731599

15741600
auto newState = oldState;
15751601
// Lock is still held at this point, so it is safe to access prioritisedJobs
1576-
if (!prioritisedJobs.empty() || oldState.getFirstUnprioritisedJob()) {
1602+
if (!_prioritisedJobs().empty() || oldState.getFirstUnprioritisedJob()) {
15771603
// There is work left to do, don't unlock the actor
15781604
if (!forceUnlock) {
15791605
SWIFT_TASK_DEBUG_LOG("Unlock-ing actor %p failed", this);
@@ -2094,5 +2120,5 @@ bool swift::swift_distributed_actor_is_remote(HeapObject *_actor) {
20942120
}
20952121

20962122
bool DefaultActorImpl::isDistributedRemote() {
2097-
return this->isDistributedRemoteActor;
2123+
return this->header.isDistributedRemoteActor;
20982124
}

0 commit comments

Comments
 (0)