Skip to content

Commit

Permalink
Improve branch prediction in the AsyncMethodBuilders
Browse files Browse the repository at this point in the history
  • Loading branch information
benaadams committed May 18, 2020
1 parent 350e484 commit 81f420a
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,11 @@ internal sealed override void ExecuteFromThreadPool(Thread threadPoolThread)
bool loggingOn = AsyncCausalityTracer.LoggingOn;
if (loggingOn)
{
AsyncCausalityTracer.TraceSynchronousWorkStart(this, CausalitySynchronousWork.Execution);
// Jump forward for logging, so its not picked.
goto LogStart;
}

Start:
Debug.Assert(StateMachine != null);

ExecutionContext? context = Context;
Expand All @@ -326,6 +328,7 @@ internal sealed override void ExecuteFromThreadPool(Thread threadPoolThread)
ExecutionContext.RunForThreadPoolUnsafe(context, s_callback, this, threadPoolThread);
}

// Can't do much here to work with the branch predictor without excessive gotos.
if (IsCompleted)
{
// If async debugging is enabled, remove the task from tracking.
Expand All @@ -351,62 +354,98 @@ internal sealed override void ExecuteFromThreadPool(Thread threadPoolThread)
#endif
}

if (loggingOn)
if (!loggingOn)
{
AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalitySynchronousWork.Execution);
// Logging off: Preferred.
return;
}

// Logging on: Not preferred.
AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalitySynchronousWork.Execution);
return;

LogStart:
AsyncCausalityTracer.TraceSynchronousWorkStart(this, CausalitySynchronousWork.Execution);
goto Start;
}

/// <summary>Calls MoveNext on <see cref="StateMachine"/></summary>
public void MoveNext()
{
Debug.Assert(!IsCompleted);

// As we can't annotate to Jit which branch is unlikely to be taken
// this is arranged to prefer specific paths.
bool loggingOn = AsyncCausalityTracer.LoggingOn;
if (loggingOn)
{
AsyncCausalityTracer.TraceSynchronousWorkStart(this, CausalitySynchronousWork.Execution);
// Jump forward for logging, so its not picked.
goto LogStart;
}

Start:
Debug.Assert(StateMachine != null);

ExecutionContext? context = Context;
if (context == null)
{
StateMachine.MoveNext();
}
else if (!context.IsDefault)
{
ExecutionContext.RunInternal(context, s_callback, this);
}
else

if (context != null)
{
Thread currentThread = Thread.CurrentThread;
ExecutionContext? currentContext = currentThread._executionContext;
if (currentContext != null && !currentContext.IsDefault)
{
// Current thread is not on Default
ExecutionContext.RunOnDefaultContext(currentThread, currentContext, s_callback, this);
}
else
if (context.IsDefault)
{
// On Default and to run on Default; however we need to undo any changes that happen in call.
SynchronizationContext? previousSyncCtx = currentThread._synchronizationContext;
ExceptionDispatchInfo? edi = null;
try
// 1st preference: Default context.
Thread currentThread = Thread.CurrentThread;
ExecutionContext? currentContext = currentThread._executionContext;
if (currentContext == null || currentContext.IsDefault)
{
// Run directly
StateMachine.MoveNext();
// Preferred: On Default and to run on Default; however we need to undo any changes that happen in call.
SynchronizationContext? previousSyncCtx = currentThread._synchronizationContext;
ExceptionDispatchInfo? edi = null;
try
{
// Run directly
StateMachine.MoveNext();
}
catch (Exception ex)
{
edi = ExceptionDispatchInfo.Capture(ex);
}

// Enregister references as they have been stack spilled due to crossing EH.
Thread thread = currentThread;
SynchronizationContext? syncCtx = previousSyncCtx;
if (thread._executionContext == null)
{
if (thread._synchronizationContext != syncCtx)
{
thread._synchronizationContext = syncCtx;
}

edi?.Throw();
}
else
{
ExecutionContext.RestoreDefaultContextThrowIfNeeded(thread, syncCtx, edi);
}
}
catch (Exception ex)
else
{
edi = ExceptionDispatchInfo.Capture(ex);
// Not preferred: Current thread is not on Default.
ExecutionContext.RunOnDefaultContext(currentThread, currentContext, s_callback, this);
}

ExecutionContext.RestoreDefaultContextThrowIfNeeded(currentThread, previousSyncCtx, edi);
}
else
{
// 2nd preference: non-default context.
ExecutionContext.RunInternal(context, s_callback, this);
}
}
else
{
// 3rd preference: flow supressed context.
StateMachine.MoveNext();
}

// Can't do much here to work with the branch predictor without excessive gotos.
if (IsCompleted)
{
// If async debugging is enabled, remove the task from tracking.
Expand All @@ -432,10 +471,20 @@ public void MoveNext()
#endif
}

if (loggingOn)
if (!loggingOn)
{
AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalitySynchronousWork.Execution);
// Logging Off: Preferred.
return;
}

// Logging on: Not preferred.
AsyncCausalityTracer.TraceSynchronousWorkCompletion(CausalitySynchronousWork.Execution);
return;

LogStart:
AsyncCausalityTracer.TraceSynchronousWorkStart(this, CausalitySynchronousWork.Execution);
goto Start;

}

/// <summary>Gets the state machine as a boxed object. This should only be used for debugging purposes.</summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -482,41 +482,63 @@ public void MoveNext()
Debug.Assert(!(StateMachine is null));

ExecutionContext? context = Context;

if (context is null)
// As we can't annotate to Jit which branch is unlikely to be taken
// this is arranged to prefer specific paths.
if (context != null)
{
StateMachine.MoveNext();
}
else if (!context.IsDefault)
{
ExecutionContext.RunInternal(context, s_callback, this);
}
else
{
Thread currentThread = Thread.CurrentThread;
ExecutionContext? currentContext = currentThread._executionContext;
if (currentContext != null && !currentContext.IsDefault)
{
// Current thread is not on Default
ExecutionContext.RunOnDefaultContext(currentThread, currentContext, s_callback, this);
}
else
if (context.IsDefault)
{
// On Default and to run on Default; however we need to undo any changes that happen in call.
SynchronizationContext? previousSyncCtx = currentThread._synchronizationContext;
ExceptionDispatchInfo? edi = null;
try
// 1st preference: Default context
Thread currentThread = Thread.CurrentThread;
ExecutionContext? currentContext = currentThread._executionContext;
if (currentContext == null || currentContext.IsDefault)
{
// Run directly
StateMachine.MoveNext();
// Preferred: On Default and to run on Default; however we need to undo any changes that happen in call.
SynchronizationContext? previousSyncCtx = currentThread._synchronizationContext;
ExceptionDispatchInfo? edi = null;
try
{
// Run directly
StateMachine.MoveNext();
}
catch (Exception ex)
{
edi = ExceptionDispatchInfo.Capture(ex);
}

// Enregister references as they have been stack spilled due to crossing EH.
Thread thread = currentThread;
SynchronizationContext? syncCtx = previousSyncCtx;
if (thread._executionContext == null)
{
if (thread._synchronizationContext != syncCtx)
{
thread._synchronizationContext = syncCtx;
}

edi?.Throw();
}
else
{
ExecutionContext.RestoreDefaultContextThrowIfNeeded(thread, syncCtx, edi);
}
}
catch (Exception ex)
else
{
edi = ExceptionDispatchInfo.Capture(ex);
// Not preferred: Current thread is not on Default
ExecutionContext.RunOnDefaultContext(currentThread, currentContext, s_callback, this);
}

ExecutionContext.RestoreDefaultContextThrowIfNeeded(currentThread, previousSyncCtx, edi);
}
else
{
// 2nd preference: non-default context
ExecutionContext.RunInternal(context, s_callback, this);
}
}
else
{
// 3rd preference: flow supressed context
StateMachine.MoveNext();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,21 +425,23 @@ internal static void ResetThreadPoolThread(Thread currentThread)
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void RestoreDefaultContextThrowIfNeeded(Thread currentThread, SynchronizationContext? previousSyncCtx, ExceptionDispatchInfo? edi)
{
ExecutionContext? currentExecutionCtx = currentThread._executionContext;

// Reset to Default
currentThread._executionContext = null;
currentThread._synchronizationContext = previousSyncCtx;
if (currentThread._synchronizationContext != previousSyncCtx)
{
currentThread._synchronizationContext = previousSyncCtx;
}

if (currentExecutionCtx != null && currentExecutionCtx.HasChangeNotifications)
{
OnValuesChanged(currentExecutionCtx, nextExecutionCtx: null);

// Reset to defaults again without change notifications in case the Change handler changed the contexts
currentThread._synchronizationContext = null;
currentThread._synchronizationContext = previousSyncCtx;
currentThread._executionContext = null;
}

Expand Down

0 comments on commit 81f420a

Please sign in to comment.