Skip to content

Commit 455cf7d

Browse files
drexinktoso
andauthored
[Concurrency] Reduce overhead of Task.yield and Task.sleep (#37090) (#37345)
* [Concurrency] Reduce overhead of Task.yield and Task.sleep Instead of creating a new task, we create a simple job that wraps a Builtin.RawUnsafeContinuation and resumes the continuation when it is executed. The job instance is allocated on the task local allocator, meaning we don't malloc anything. * Update stdlib/public/Concurrency/Task.swift Co-authored-by: Konrad `ktoso` Malawski <[email protected]> Co-authored-by: Konrad `ktoso` Malawski <[email protected]> Co-authored-by: Konrad `ktoso` Malawski <[email protected]>
1 parent 47b5e6d commit 455cf7d

File tree

6 files changed

+84
-29
lines changed

6 files changed

+84
-29
lines changed

include/swift/ABI/MetadataValues.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1977,7 +1977,8 @@ enum class JobKind : size_t {
19771977

19781978
DefaultActorInline = First_Reserved,
19791979
DefaultActorSeparate,
1980-
DefaultActorOverride
1980+
DefaultActorOverride,
1981+
NullaryContinuation
19811982
};
19821983

19831984
/// The priority of a job. Higher priorities are larger values.

include/swift/ABI/Task.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,25 @@ class ActiveTaskStatus {
155155
}
156156
};
157157

158+
class NullaryContinuationJob : public Job {
159+
160+
private:
161+
AsyncTask* Task;
162+
AsyncTask* Continuation;
163+
164+
public:
165+
NullaryContinuationJob(AsyncTask *task, JobPriority priority, AsyncTask *continuation)
166+
: Job({JobKind::NullaryContinuation, priority}, &process),
167+
Task(task), Continuation(continuation) {}
168+
169+
SWIFT_CC(swiftasync)
170+
static void process(Job *job);
171+
172+
static bool classof(const Job *job) {
173+
return job->Flags.getKind() == JobKind::NullaryContinuation;
174+
}
175+
};
176+
158177
/// An asynchronous task. Tasks are the analogue of threads for
159178
/// asynchronous functions: that is, they are a persistent identity
160179
/// for the overall async computation.
@@ -562,6 +581,10 @@ class ContinuationAsyncContext : public AsyncContext {
562581
/// The executor that should be resumed to.
563582
ExecutorRef ResumeToExecutor;
564583

584+
void setErrorResult(SwiftError *error) {
585+
ErrorResult = error;
586+
}
587+
565588
static bool classof(const AsyncContext *context) {
566589
return context->Flags.getKind() == AsyncContextKind::Continuation;
567590
}

include/swift/Runtime/Concurrency.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#ifndef SWIFT_RUNTIME_CONCURRENCY_H
1818
#define SWIFT_RUNTIME_CONCURRENCY_H
1919

20+
#include "swift/ABI/Task.h"
2021
#include "swift/ABI/TaskGroup.h"
2122
#include "swift/ABI/TaskStatus.h"
2223

@@ -348,6 +349,13 @@ SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
348349
void swift_task_removeCancellationHandler(
349350
CancellationNotificationStatusRecord *record);
350351

352+
/// Create a NullaryContinuationJob from a continuation.
353+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
354+
NullaryContinuationJob*
355+
swift_task_createNullaryContinuationJob(
356+
size_t priority,
357+
AsyncTask *continuation);
358+
351359
/// Report error about attempting to bind a task-local value from an illegal context.
352360
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
353361
void swift_task_reportIllegalTaskLocalBindingWithinWithTaskGroup(

stdlib/public/CompatibilityOverride/CompatibilityOverrideConcurrency.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,11 @@ OVERRIDE_TASK(task_removeCancellationHandler, void,
141141
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift), swift::,
142142
(CancellationNotificationStatusRecord *record), (record))
143143

144+
OVERRIDE_TASK(task_createNullaryContinuationJob, NullaryContinuationJob *,
145+
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift), swift::,
146+
(size_t priority,
147+
AsyncTask *continuation), (priority, continuation))
148+
144149
OVERRIDE_TASK(task_asyncMainDrainQueue, void,
145150
SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift), swift::,
146151
, )

stdlib/public/Concurrency/Task.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,20 @@ FutureFragment::Status AsyncTask::waitFuture(AsyncTask *waitingTask) {
9595
}
9696
}
9797

98+
void NullaryContinuationJob::process(Job *_job) {
99+
auto *job = cast<NullaryContinuationJob>(_job);
100+
101+
auto *task = job->Task;
102+
auto *continuation = job->Continuation;
103+
104+
_swift_task_dealloc_specific(task, job);
105+
106+
auto *context = cast<ContinuationAsyncContext>(continuation->ResumeContext);
107+
108+
context->setErrorResult(nullptr);
109+
swift_continuation_resume(continuation);
110+
}
111+
98112
void AsyncTask::completeFuture(AsyncContext *context) {
99113
using Status = FutureFragment::Status;
100114
using WaitQueueItem = FutureFragment::WaitQueueItem;
@@ -879,6 +893,21 @@ static void swift_task_removeCancellationHandlerImpl(
879893
swift_task_dealloc(record);
880894
}
881895

896+
SWIFT_CC(swift)
897+
static NullaryContinuationJob*
898+
swift_task_createNullaryContinuationJobImpl(
899+
size_t priority,
900+
AsyncTask *continuation) {
901+
void *allocation =
902+
swift_task_alloc(sizeof(NullaryContinuationJob));
903+
auto *job =
904+
new (allocation) NullaryContinuationJob(
905+
swift_task_getCurrent(), static_cast<JobPriority>(priority),
906+
continuation);
907+
908+
return job;
909+
}
910+
882911
SWIFT_CC(swift)
883912
void swift::swift_continuation_logFailedCheck(const char *message) {
884913
swift_reportError(0, message);

stdlib/public/Concurrency/Task.swift

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -648,19 +648,13 @@ extension Task {
648648
///
649649
/// This function does _not_ block the underlying thread.
650650
public static func sleep(_ duration: UInt64) async {
651-
// Set up the job flags for a new task.
652-
var flags = Task.JobFlags()
653-
flags.kind = .task
654-
flags.priority = .default
655-
flags.isFuture = true
651+
let currentTask = Builtin.getCurrentAsyncTask()
652+
let priority = getJobFlags(currentTask).priority ?? Task.currentPriority._downgradeUserInteractive
656653

657-
// Create the asynchronous task future.
658-
let (task, _) = Builtin.createAsyncTaskFuture(flags.bits, {})
659-
660-
// Enqueue the resulting job.
661-
_enqueueJobGlobalWithDelay(duration, Builtin.convertTaskToJob(task))
662-
663-
await Handle<Void, Never>(task).get()
654+
return await Builtin.withUnsafeContinuation { (continuation: Builtin.RawUnsafeContinuation) -> Void in
655+
let job = _taskCreateNullaryContinuationJob(priority: priority.rawValue, continuation: continuation)
656+
_enqueueJobGlobalWithDelay(duration, job)
657+
}
664658
}
665659
}
666660

@@ -676,22 +670,13 @@ extension Task {
676670
/// if the task is the highest-priority task in the system, it might go
677671
/// immediately back to executing.
678672
public static func yield() async {
679-
// Prepare the job flags
680-
var flags = JobFlags()
681-
flags.kind = .task
682-
flags.priority = .default
683-
flags.isFuture = true
684-
685-
// Create the asynchronous task future, it will do nothing, but simply serves
686-
// as a way for us to yield our execution until the executor gets to it and
687-
// resumes us.
688-
// TODO: consider if it would be useful for this task to be a child task
689-
let (task, _) = Builtin.createAsyncTaskFuture(flags.bits, {})
690-
691-
// Enqueue the resulting job.
692-
_enqueueJobGlobal(Builtin.convertTaskToJob(task))
693-
694-
let _ = await Handle<Void, Never>(task).get()
673+
let currentTask = Builtin.getCurrentAsyncTask()
674+
let priority = getJobFlags(currentTask).priority ?? Task.currentPriority._downgradeUserInteractive
675+
676+
return await Builtin.withUnsafeContinuation { (continuation: Builtin.RawUnsafeContinuation) -> Void in
677+
let job = _taskCreateNullaryContinuationJob(priority: priority.rawValue, continuation: continuation)
678+
_enqueueJobGlobal(job)
679+
}
695680
}
696681
}
697682

@@ -901,6 +886,10 @@ func _taskCancel(_ task: Builtin.NativeObject)
901886
@_silgen_name("swift_task_isCancelled")
902887
func _taskIsCancelled(_ task: Builtin.NativeObject) -> Bool
903888

889+
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
890+
@_silgen_name("swift_task_createNullaryContinuationJob")
891+
func _taskCreateNullaryContinuationJob(priority: Int, continuation: Builtin.RawUnsafeContinuation) -> Builtin.Job
892+
904893
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
905894
@usableFromInline
906895
@_silgen_name("swift_task_isCurrentExecutor")

0 commit comments

Comments
 (0)