Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Make "async ValueTask/ValueTask<T>" methods ammortized allocation-free #26310

Merged
merged 1 commit into from
Oct 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public static void Start<TStateMachine>(ref TStateMachine stateMachine) where TS
}
}

public static void SetStateMachine(IAsyncStateMachine stateMachine, Task task)
public static void SetStateMachine(IAsyncStateMachine stateMachine, Task? task)
{
if (stateMachine == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,36 @@ namespace System.Runtime.CompilerServices
internal static class AsyncTaskCache
{
/// <summary>A cached Task{Boolean}.Result == true.</summary>
internal static readonly Task<bool> s_trueTask = CreateCacheableTask(true);
internal static readonly Task<bool> s_trueTask = CreateCacheableTask(result: true);
/// <summary>A cached Task{Boolean}.Result == false.</summary>
internal static readonly Task<bool> s_falseTask = CreateCacheableTask(false);
internal static readonly Task<bool> s_falseTask = CreateCacheableTask(result: false);
/// <summary>The cache of Task{Int32}.</summary>
internal static readonly Task<int>[] s_int32Tasks = CreateInt32Tasks();
/// <summary>The minimum value, inclusive, for which we want a cached task.</summary>
internal const int InclusiveInt32Min = -1;
/// <summary>The maximum value, exclusive, for which we want a cached task.</summary>
internal const int ExclusiveInt32Max = 9;

/// <summary>true if we should use reusable boxes for async completions of ValueTask methods; false if we should use tasks.</summary>
/// <remarks>
/// We rely on tiered compilation turning this into a const and doing dead code elimination to make checks on this efficient.
/// It's also required for safety that this value never changes once observed, as Unsafe.As casts are employed based on its value.
/// </remarks>
internal static readonly bool s_valueTaskPoolingEnabled = GetPoolAsyncValueTasksSwitch();
/// <summary>Maximum number of boxes that are allowed to be cached per state machine type.</summary>
internal static readonly int s_valueTaskPoolingCacheSize = GetPoolAsyncValueTasksLimitValue();

private static bool GetPoolAsyncValueTasksSwitch()
{
string? value = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_THREADING_POOLASYNCVALUETASKS");
return value != null && (bool.IsTrueStringIgnoreCase(value) || value.Equals("1"));
}

private static int GetPoolAsyncValueTasksLimitValue() =>
int.TryParse(Environment.GetEnvironmentVariable("DOTNET_SYSTEM_THREADING_POOLASYNCVALUETASKSLIMIT"), out int result) && result > 0 ?
result :
Environment.ProcessorCount * 4; // arbitrary default value
Copy link
Member

@benaadams benaadams Oct 22, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems a bit low
suggestion~~ ~~ Environment.ProcessorCount * 32; // arbitrary default value~~ ~~

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nvm, have same allocation profile with 4 and 32.


/// <summary>Creates a non-disposable task.</summary>
/// <typeparam name="TResult">Specifies the result type.</typeparam>
/// <param name="result">The result for the task.</param>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,12 @@ public struct AsyncTaskMethodBuilder
/// <summary>A cached VoidTaskResult task used for builders that complete synchronously.</summary>
private static readonly Task<VoidTaskResult> s_cachedCompleted = AsyncTaskMethodBuilder<VoidTaskResult>.s_defaultResultTask;

/// <summary>The generic builder object to which this non-generic instance delegates.</summary>
private AsyncTaskMethodBuilder<VoidTaskResult> m_builder; // mutable struct: must not be readonly. Debugger depends on the exact name of this field.
/// <summary>The lazily-initialized built task.</summary>
private Task<VoidTaskResult>? m_task; // Debugger depends on the exact name of this field.

/// <summary>Initializes a new <see cref="AsyncTaskMethodBuilder"/>.</summary>
/// <returns>The initialized <see cref="AsyncTaskMethodBuilder"/>.</returns>
public static AsyncTaskMethodBuilder Create() =>
// m_builder should be initialized to AsyncTaskMethodBuilder<VoidTaskResult>.Create(), but on coreclr
// that Create() is a nop, so we can just return the default here.
default;
public static AsyncTaskMethodBuilder Create() => default;

/// <summary>Initiates the builder's execution with the associated state machine.</summary>
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
Expand All @@ -44,7 +41,7 @@ public void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMac
/// <exception cref="System.ArgumentNullException">The <paramref name="stateMachine"/> argument was null (Nothing in Visual Basic).</exception>
/// <exception cref="System.InvalidOperationException">The builder is incorrectly initialized.</exception>
public void SetStateMachine(IAsyncStateMachine stateMachine) =>
m_builder.SetStateMachine(stateMachine);
AsyncMethodBuilderCore.SetStateMachine(stateMachine, task: null);

/// <summary>
/// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
Expand All @@ -57,7 +54,7 @@ public void AwaitOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : INotifyCompletion
where TStateMachine : IAsyncStateMachine =>
m_builder.AwaitOnCompleted(ref awaiter, ref stateMachine);
AsyncTaskMethodBuilder<VoidTaskResult>.AwaitOnCompleted(ref awaiter, ref stateMachine, ref m_task);

/// <summary>
/// Schedules the specified state machine to be pushed forward when the specified awaiter completes.
Expand All @@ -66,19 +63,32 @@ public void AwaitOnCompleted<TAwaiter, TStateMachine>(
/// <typeparam name="TStateMachine">Specifies the type of the state machine.</typeparam>
/// <param name="awaiter">The awaiter.</param>
/// <param name="stateMachine">The state machine.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(
ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine =>
m_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
AsyncTaskMethodBuilder<VoidTaskResult>.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine, ref m_task);

/// <summary>Gets the <see cref="System.Threading.Tasks.Task"/> for this builder.</summary>
/// <returns>The <see cref="System.Threading.Tasks.Task"/> representing the builder's asynchronous operation.</returns>
/// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
public Task Task
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => m_builder.Task;
get => m_task ?? InitializeTaskAsPromise();
}

/// <summary>
/// Initializes the task, which must not yet be initialized. Used only when the Task is being forced into
/// existence when no state machine is needed, e.g. when the builder is being synchronously completed with
/// an exception, when the builder is being used out of the context of an async method, etc.
/// </summary>
[MethodImpl(MethodImplOptions.NoInlining)]
private Task<VoidTaskResult> InitializeTaskAsPromise()
{
Debug.Assert(m_task == null);
return m_task = new Task<VoidTaskResult>();
}

/// <summary>
Expand All @@ -87,7 +97,20 @@ public Task Task
/// </summary>
/// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
/// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
public void SetResult() => m_builder.SetResult(s_cachedCompleted); // Using s_cachedCompleted is faster than using s_defaultResultTask.
public void SetResult()
{
// Get the currently stored task, which will be non-null if get_Task has already been accessed.
// If there isn't one, store the supplied completed task.
if (m_task is null)
{
m_task = s_cachedCompleted;
}
else
{
// Otherwise, complete the task that's there.
AsyncTaskMethodBuilder<VoidTaskResult>.SetExistingTaskResult(m_task, default!);
}
}

/// <summary>
/// Completes the <see cref="System.Threading.Tasks.Task"/> in the
Expand All @@ -97,7 +120,8 @@ public Task Task
/// <exception cref="System.ArgumentNullException">The <paramref name="exception"/> argument is null (Nothing in Visual Basic).</exception>
/// <exception cref="System.InvalidOperationException">The builder is not initialized.</exception>
/// <exception cref="System.InvalidOperationException">The task has already completed.</exception>
public void SetException(Exception exception) => m_builder.SetException(exception);
public void SetException(Exception exception) =>
AsyncTaskMethodBuilder<VoidTaskResult>.SetException(exception, ref m_task);

/// <summary>
/// Called by the debugger to request notification when the first wait operation
Expand All @@ -106,7 +130,8 @@ public Task Task
/// <param name="enabled">
/// true to enable notification; false to disable a previously set notification.
/// </param>
internal void SetNotificationForWaitCompletion(bool enabled) => m_builder.SetNotificationForWaitCompletion(enabled);
internal void SetNotificationForWaitCompletion(bool enabled) =>
AsyncTaskMethodBuilder<VoidTaskResult>.SetNotificationForWaitCompletion(enabled, ref m_task);

/// <summary>
/// Gets an object that may be used to uniquely identify this builder to the debugger.
Expand All @@ -116,6 +141,7 @@ public Task Task
/// It must only be used by the debugger and tracing purposes, and only in a single-threaded manner
/// when no other threads are in the middle of accessing this property or this.Task.
/// </remarks>
internal object ObjectIdForDebugger => m_builder.ObjectIdForDebugger;
internal object ObjectIdForDebugger =>
m_task ??= AsyncTaskMethodBuilder<VoidTaskResult>.CreateWeaklyTypedStateMachineBox();
}
}
Loading