From f7e4445bc0c4f1bf56c63f38b82e583d7f33e45b Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 24 May 2016 17:31:58 +0100 Subject: [PATCH] Use cached tasks for Task.FromResult Use cached tasks for both FromResult and AsyncTaskMethodBuilder rather than just AsyncTaskMethodBuilder Move GetTaskForResult from AsyncMethodBuilder to Task Move AsyncTaskCache from AsyncMethodBuilder to future Call Task.GetTaskForResult(result) from FromResult rather than creating new Task Call Task.FromResult(result) from AsyncTaskMethodBuilder rather than GetTaskForResult(result) --- .../CompilerServices/AsyncMethodBuilder.cs | 139 +----------------- .../src/System/Threading/Tasks/Task.cs | 2 +- .../src/System/Threading/Tasks/future.cs | 139 +++++++++++++++++- 3 files changed, 140 insertions(+), 140 deletions(-) diff --git a/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs b/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs index 05850605b841..1c0392a27cd1 100644 --- a/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs +++ b/src/mscorlib/src/System/Runtime/CompilerServices/AsyncMethodBuilder.cs @@ -277,7 +277,7 @@ private Task Task public struct AsyncTaskMethodBuilder { /// A cached VoidTaskResult task used for builders that complete synchronously. - private readonly static Task s_cachedCompleted = AsyncTaskMethodBuilder.s_defaultResultTask; + private readonly static Task s_cachedCompleted = Task.s_defaultResultTask; /// The generic builder object to which this non-generic instance delegates. private AsyncTaskMethodBuilder m_builder; // mutable struct: must not be readonly @@ -424,9 +424,6 @@ internal void SetNotificationForWaitCompletion(bool enabled) [HostProtection(Synchronization = true, ExternalThreading = true)] public struct AsyncTaskMethodBuilder { - /// A cached task for default(TResult). - internal readonly static Task s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult)); - // WARNING: For performance reasons, the m_task field is lazily initialized. // For correct results, the struct AsyncTaskMethodBuilder must // always be used from the same location/copy, at least until m_task is @@ -594,8 +591,8 @@ public void SetResult(TResult result) var task = m_task; if (task == null) { - m_task = GetTaskForResult(result); - Contract.Assert(m_task != null, "GetTaskForResult should never return null"); + m_task = System.Threading.Tasks.Task.FromResult(result); + Contract.Assert(m_task != null, "Task.FromResult should never return null"); } // Slow path: complete the existing task. else @@ -706,136 +703,6 @@ internal void SetNotificationForWaitCompletion(bool enabled) /// when no other threads are in the middle of accessing this property or this.Task. /// private object ObjectIdForDebugger { get { return this.Task; } } - - /// - /// Gets a task for the specified result. This will either - /// be a cached or new task, never null. - /// - /// The result for which we need a task. - /// The completed task containing the result. - [SecuritySafeCritical] // for JitHelpers.UnsafeCast - private Task GetTaskForResult(TResult result) - { - Contract.Ensures( - EqualityComparer.Default.Equals(result, Contract.Result>().Result), - "The returned task's Result must return the same value as the specified result value."); - - // The goal of this function is to be give back a cached task if possible, - // or to otherwise give back a new task. To give back a cached task, - // we need to be able to evaluate the incoming result value, and we need - // to avoid as much overhead as possible when doing so, as this function - // is invoked as part of the return path from every async method. - // Most tasks won't be cached, and thus we need the checks for those that are - // to be as close to free as possible. This requires some trickiness given the - // lack of generic specialization in .NET. - // - // Be very careful when modifying this code. It has been tuned - // to comply with patterns recognized by both 32-bit and 64-bit JITs. - // If changes are made here, be sure to look at the generated assembly, as - // small tweaks can have big consequences for what does and doesn't get optimized away. - // - // Note that this code only ever accesses a static field when it knows it'll - // find a cached value, since static fields (even if readonly and integral types) - // require special access helpers in this NGEN'd and domain-neutral. - - if (null != (object)default(TResult)) // help the JIT avoid the value type branches for ref types - { - // Special case simple value types: - // - Boolean - // - Byte, SByte - // - Char - // - Decimal - // - Int32, UInt32 - // - Int64, UInt64 - // - Int16, UInt16 - // - IntPtr, UIntPtr - // As of .NET 4.5, the (Type)(object)result pattern used below - // is recognized and optimized by both 32-bit and 64-bit JITs. - - // For Boolean, we cache all possible values. - if (typeof(TResult) == typeof(Boolean)) // only the relevant branches are kept for each value-type generic instantiation - { - Boolean value = (Boolean)(object)result; - Task task = value ? AsyncTaskCache.TrueTask : AsyncTaskCache.FalseTask; - return JitHelpers.UnsafeCast>(task); // UnsafeCast avoids type check we know will succeed - } - // For Int32, we cache a range of common values, e.g. [-1,4). - else if (typeof(TResult) == typeof(Int32)) - { - // Compare to constants to avoid static field access if outside of cached range. - // We compare to the upper bound first, as we're more likely to cache miss on the upper side than on the - // lower side, due to positive values being more common than negative as return values. - Int32 value = (Int32)(object)result; - if (value < AsyncTaskCache.EXCLUSIVE_INT32_MAX && - value >= AsyncTaskCache.INCLUSIVE_INT32_MIN) - { - Task task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN]; - return JitHelpers.UnsafeCast>(task); // UnsafeCast avoids a type check we know will succeed - } - } - // For other known value types, we only special-case 0 / default(TResult). - else if ( - (typeof(TResult) == typeof(UInt32) && default(UInt32) == (UInt32)(object)result) || - (typeof(TResult) == typeof(Byte) && default(Byte) == (Byte)(object)result) || - (typeof(TResult) == typeof(SByte) && default(SByte) == (SByte)(object)result) || - (typeof(TResult) == typeof(Char) && default(Char) == (Char)(object)result) || - (typeof(TResult) == typeof(Decimal) && default(Decimal) == (Decimal)(object)result) || - (typeof(TResult) == typeof(Int64) && default(Int64) == (Int64)(object)result) || - (typeof(TResult) == typeof(UInt64) && default(UInt64) == (UInt64)(object)result) || - (typeof(TResult) == typeof(Int16) && default(Int16) == (Int16)(object)result) || - (typeof(TResult) == typeof(UInt16) && default(UInt16) == (UInt16)(object)result) || - (typeof(TResult) == typeof(IntPtr) && default(IntPtr) == (IntPtr)(object)result) || - (typeof(TResult) == typeof(UIntPtr) && default(UIntPtr) == (UIntPtr)(object)result)) - { - return s_defaultResultTask; - } - } - else if (result == null) // optimized away for value types - { - return s_defaultResultTask; - } - - // No cached task is available. Manufacture a new one for this result. - return new Task(result); - } - } - - /// Provides a cache of closed generic tasks for async methods. - internal static class AsyncTaskCache - { - // All static members are initialized inline to ensure type is beforefieldinit - - /// A cached Task{Boolean}.Result == true. - internal readonly static Task TrueTask = CreateCacheableTask(true); - /// A cached Task{Boolean}.Result == false. - internal readonly static Task FalseTask = CreateCacheableTask(false); - - /// The cache of Task{Int32}. - internal readonly static Task[] Int32Tasks = CreateInt32Tasks(); - /// The minimum value, inclusive, for which we want a cached task. - internal const Int32 INCLUSIVE_INT32_MIN = -1; - /// The maximum value, exclusive, for which we want a cached task. - internal const Int32 EXCLUSIVE_INT32_MAX = 9; - /// Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). - private static Task[] CreateInt32Tasks() - { - Contract.Assert(EXCLUSIVE_INT32_MAX >= INCLUSIVE_INT32_MIN, "Expected max to be at least min"); - var tasks = new Task[EXCLUSIVE_INT32_MAX - INCLUSIVE_INT32_MIN]; - for (int i = 0; i < tasks.Length; i++) - { - tasks[i] = CreateCacheableTask(i + INCLUSIVE_INT32_MIN); - } - return tasks; - } - - /// Creates a non-disposable task. - /// Specifies the result type. - /// The result for the task. - /// The cacheable task. - internal static Task CreateCacheableTask(TResult result) - { - return new Task(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken)); - } } /// Holds state related to the builder's IAsyncStateMachine. diff --git a/src/mscorlib/src/System/Threading/Tasks/Task.cs b/src/mscorlib/src/System/Threading/Tasks/Task.cs index 6031457c5b4a..f72166057f14 100644 --- a/src/mscorlib/src/System/Threading/Tasks/Task.cs +++ b/src/mscorlib/src/System/Threading/Tasks/Task.cs @@ -5521,7 +5521,7 @@ public static int WaitAny(Task[] tasks, int millisecondsTimeout, CancellationTok /// The successfully completed task. public static Task FromResult(TResult result) { - return new Task(result); + return Task.GetTaskForResult(result); } /// Creates a that's completed exceptionally with the specified exception. diff --git a/src/mscorlib/src/System/Threading/Tasks/future.cs b/src/mscorlib/src/System/Threading/Tasks/future.cs index 120782853e91..49171a9ad218 100644 --- a/src/mscorlib/src/System/Threading/Tasks/future.cs +++ b/src/mscorlib/src/System/Threading/Tasks/future.cs @@ -1575,7 +1575,102 @@ ref stackMark #endregion #endregion - + + /// A cached task for default(TResult). + internal readonly static Task s_defaultResultTask = AsyncTaskCache.CreateCacheableTask(default(TResult)); + + /// + /// Gets a task for the specified result. This will either + /// be a cached or new task, never null. + /// + /// The result for which we need a task. + /// The completed task containing the result. + [SecuritySafeCritical] // for JitHelpers.UnsafeCast + internal static Task GetTaskForResult(TResult result) + { + Contract.Ensures( + EqualityComparer.Default.Equals(result, Contract.Result>().Result), + "The returned task's Result must return the same value as the specified result value."); + + // The goal of this function is to be give back a cached task if possible, + // or to otherwise give back a new task. To give back a cached task, + // we need to be able to evaluate the incoming result value, and we need + // to avoid as much overhead as possible when doing so, as this function + // is invoked as part of the return path from every async method. + // Most tasks won't be cached, and thus we need the checks for those that are + // to be as close to free as possible. This requires some trickiness given the + // lack of generic specialization in .NET. + // + // Be very careful when modifying this code. It has been tuned + // to comply with patterns recognized by both 32-bit and 64-bit JITs. + // If changes are made here, be sure to look at the generated assembly, as + // small tweaks can have big consequences for what does and doesn't get optimized away. + // + // Note that this code only ever accesses a static field when it knows it'll + // find a cached value, since static fields (even if readonly and integral types) + // require special access helpers in this NGEN'd and domain-neutral. + + if (null != (object)default(TResult)) // help the JIT avoid the value type branches for ref types + { + // Special case simple value types: + // - Boolean + // - Byte, SByte + // - Char + // - Decimal + // - Int32, UInt32 + // - Int64, UInt64 + // - Int16, UInt16 + // - IntPtr, UIntPtr + // As of .NET 4.5, the (Type)(object)result pattern used below + // is recognized and optimized by both 32-bit and 64-bit JITs. + + // For Boolean, we cache all possible values. + if (typeof(TResult) == typeof(Boolean)) // only the relevant branches are kept for each value-type generic instantiation + { + Boolean value = (Boolean)(object)result; + Task task = value ? AsyncTaskCache.TrueTask : AsyncTaskCache.FalseTask; + return JitHelpers.UnsafeCast>(task); // UnsafeCast avoids type check we know will succeed + } + // For Int32, we cache a range of common values, e.g. [-1,4). + else if (typeof(TResult) == typeof(Int32)) + { + // Compare to constants to avoid static field access if outside of cached range. + // We compare to the upper bound first, as we're more likely to cache miss on the upper side than on the + // lower side, due to positive values being more common than negative as return values. + Int32 value = (Int32)(object)result; + if (value < AsyncTaskCache.EXCLUSIVE_INT32_MAX && + value >= AsyncTaskCache.INCLUSIVE_INT32_MIN) + { + Task task = AsyncTaskCache.Int32Tasks[value - AsyncTaskCache.INCLUSIVE_INT32_MIN]; + return JitHelpers.UnsafeCast>(task); // UnsafeCast avoids a type check we know will succeed + } + } + // For other known value types, we only special-case 0 / default(TResult). + else if ( + (typeof(TResult) == typeof(UInt32) && default(UInt32) == (UInt32)(object)result) || + (typeof(TResult) == typeof(Byte) && default(Byte) == (Byte)(object)result) || + (typeof(TResult) == typeof(SByte) && default(SByte) == (SByte)(object)result) || + (typeof(TResult) == typeof(Char) && default(Char) == (Char)(object)result) || + (typeof(TResult) == typeof(Decimal) && default(Decimal) == (Decimal)(object)result) || + (typeof(TResult) == typeof(Int64) && default(Int64) == (Int64)(object)result) || + (typeof(TResult) == typeof(UInt64) && default(UInt64) == (UInt64)(object)result) || + (typeof(TResult) == typeof(Int16) && default(Int16) == (Int16)(object)result) || + (typeof(TResult) == typeof(UInt16) && default(UInt16) == (UInt16)(object)result) || + (typeof(TResult) == typeof(IntPtr) && default(IntPtr) == (IntPtr)(object)result) || + (typeof(TResult) == typeof(UIntPtr) && default(UIntPtr) == (UIntPtr)(object)result)) + { + return s_defaultResultTask; + } + } + else if (result == null) // optimized away for value types + { + return s_defaultResultTask; + } + + // No cached task is available. Manufacture a new one for this result. + return new Task(result); + } + /// /// Subscribes an to receive notification of the final state of this . /// @@ -1618,6 +1713,44 @@ IDisposable IObservable.Subscribe(IObserver observer) #endif } + /// Provides a cache of closed generic tasks for async methods. + internal static class AsyncTaskCache + { + // All static members are initialized inline to ensure type is beforefieldinit + + /// A cached Task{Boolean}.Result == true. + internal readonly static Task TrueTask = CreateCacheableTask(true); + /// A cached Task{Boolean}.Result == false. + internal readonly static Task FalseTask = CreateCacheableTask(false); + + /// The cache of Task{Int32}. + internal readonly static Task[] Int32Tasks = CreateInt32Tasks(); + /// The minimum value, inclusive, for which we want a cached task. + internal const Int32 INCLUSIVE_INT32_MIN = -1; + /// The maximum value, exclusive, for which we want a cached task. + internal const Int32 EXCLUSIVE_INT32_MAX = 9; + /// Creates an array of cached tasks for the values in the range [INCLUSIVE_MIN,EXCLUSIVE_MAX). + private static Task[] CreateInt32Tasks() + { + Contract.Assert(EXCLUSIVE_INT32_MAX >= INCLUSIVE_INT32_MIN, "Expected max to be at least min"); + var tasks = new Task[EXCLUSIVE_INT32_MAX - INCLUSIVE_INT32_MIN]; + for (int i = 0; i < tasks.Length; i++) + { + tasks[i] = CreateCacheableTask(i + INCLUSIVE_INT32_MIN); + } + return tasks; + } + + /// Creates a non-disposable task. + /// Specifies the result type. + /// The result for the task. + /// The cacheable task. + internal static Task CreateCacheableTask(TResult result) + { + return new Task(false, result, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, default(CancellationToken)); + } + } + #if SUPPORT_IOBSERVABLE // Class that calls RemoveContinuation if Dispose() is called before task completion internal class DisposableSubscription : IDisposable @@ -1644,8 +1777,8 @@ void IDisposable.Dispose() } #endif - // Proxy class for better debugging experience - internal class SystemThreadingTasks_FutureDebugView + // Proxy class for better debugging experience + internal class SystemThreadingTasks_FutureDebugView { private Task m_task;