Last active
December 22, 2016 21:37
-
-
Save ReubenBond/445a7cd47bf403b323f6 to your computer and use it in GitHub Desktop.
Perf test for comparing await vs. ContinueWith in Orleans GrainMethodInvokers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Threading.Tasks; | |
namespace AwaitVersusContinueWith | |
{ | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using BenchmarkDotNet; | |
public static class Program | |
{ | |
public delegate Task<object> Invoker( | |
IReminderTableGrain grain, | |
int interfaceId, | |
int methodId, | |
object[] arguments); | |
public class Benchmarks | |
{ | |
private static readonly List<KeyValuePair<string, Invoker>> Tests = | |
new Dictionary<string, Invoker> | |
{ | |
{ "await", InvokeAwait }, | |
{ "await in Box", InvokeBoxAwait }, | |
{ "Box ExecuteSynchronously", InvokeBoxSync }, | |
{ "Box optimized", InvokeBoxMaySync }, | |
{ "Inlined ContinueWith", InvokeContinueWith }, | |
{ "Box (Current Orleans)", InvokeBox } | |
}.ToList(); | |
[Params(1, 2, 3, 4, 5, 6, 7, 8/*, 9, 10*/)] | |
public int methodId; | |
private static readonly ReminderTableGrain Grain = new ReminderTableGrain(); | |
[Benchmark] | |
public void InvokeAwaitTest() | |
{ | |
InvokeAwait(Grain, 0, this.methodId, null).Wait(); | |
} | |
[Benchmark] | |
public void InvokeBoxAwaitTest() | |
{ | |
InvokeBoxAwait(Grain, 0, this.methodId, null).Wait(); | |
} | |
[Benchmark] | |
public void InvokeBoxSyncTest() | |
{ | |
InvokeBoxSync(Grain, 0, this.methodId, null).Wait(); | |
} | |
[Benchmark] | |
public void InvokeBoxMaySyncTest() | |
{ | |
InvokeBoxMaySync(Grain, 0, this.methodId, null).Wait(); | |
} | |
[Benchmark] | |
public void InvokeContinueWithTest() | |
{ | |
InvokeContinueWith(Grain, 0, this.methodId, null).Wait(); | |
} | |
[Benchmark] | |
public void InvokeBoxTest() | |
{ | |
InvokeBox(Grain, 0, this.methodId, null).Wait(); | |
} | |
} | |
public static void Main(string[] args) | |
{ | |
new BenchmarkRunner().RunCompetition(new Benchmarks()); | |
/* | |
var tests = new Dictionary<string, Invoker> | |
{ | |
{ "await", InvokeAwait }, | |
{ "await in Box", InvokeBoxAwait }, | |
{ "Box ExecuteSynchronously", InvokeBoxSync }, | |
{ "Box optimized", InvokeBoxMaySync }, | |
{ "Inlined ContinueWith", InvokeContinueWith }, | |
{ "Box (Current Orleans)", InvokeBox } | |
}; | |
var methods = new Dictionary<string, bool> | |
{ | |
{ "completed Task", false }, | |
{ "completed Task<int>", false }, | |
{ "completed Task<string>", false }, | |
{ "completed Task<object>", false }, | |
{ "yielded Task", false }, | |
{ "yielded Task<int>", false }, | |
{ "yielded Task<string>", false }, | |
{ "yielded Task<object>", false }, | |
{ "Faulted", true }, | |
{ "Cancelled", true } | |
}; | |
var grain = new ReminderTableGrain(); | |
Console.WriteLine($",{string.Join(",", tests.Keys)}"); | |
var i = 0; | |
foreach (var method in methods) | |
{ | |
Console.Write($"{method.Key}"); | |
var methodId = ++i; | |
foreach (var test in tests) | |
{ | |
var elapsed = Tester(() => test.Value(grain, 0, methodId, null), method.Value); | |
Console.Write($",{elapsed}"); | |
} | |
Console.WriteLine(); | |
}*/ | |
//Console.ReadKey(); | |
} | |
static Task<object> Box<T>(Task<T> task) | |
{ | |
return task.ContinueWith(t => (object)t.GetAwaiter().GetResult()); | |
} | |
static Task<object> Box(Task task) | |
{ | |
return task.ContinueWith( | |
t => | |
{ | |
t.GetAwaiter().GetResult(); | |
return default(object); | |
}); | |
} | |
static Task<object> BoxSync<T>(Task<T> task) | |
{ | |
return task.ContinueWith( | |
t => (object)t.GetAwaiter().GetResult(), | |
TaskContinuationOptions.ExecuteSynchronously); | |
} | |
static Task<object> BoxSync(Task task) | |
{ | |
return task.ContinueWith( | |
t => | |
{ | |
t.GetAwaiter().GetResult(); | |
return default(object); | |
}, | |
TaskContinuationOptions.ExecuteSynchronously); | |
} | |
static Task<object> BoxMaySync(Task<object> task) | |
{ | |
return task; | |
} | |
static Task<object> BoxMaySync<T>(Task<T> task) | |
{ | |
switch (task.Status) | |
{ | |
case TaskStatus.RanToCompletion: | |
return Task.FromResult((object)task.GetAwaiter().GetResult()); | |
case TaskStatus.Faulted: | |
{ | |
var completion = new TaskCompletionSource<object>(); | |
completion.SetException(task.Exception); | |
return completion.Task; | |
} | |
case TaskStatus.Canceled: | |
{ | |
var completion = new TaskCompletionSource<object>(); | |
completion.SetCanceled(); | |
return completion.Task; | |
} | |
default: | |
return BoxAwait(task); | |
} | |
} | |
static Task<object> BoxMaySync(Task task) | |
{ | |
switch (task.Status) | |
{ | |
case TaskStatus.RanToCompletion: | |
return Task.FromResult(default(object)); | |
case TaskStatus.Faulted: | |
{ | |
var completion = new TaskCompletionSource<object>(); | |
completion.SetException(task.Exception); | |
return completion.Task; | |
} | |
case TaskStatus.Canceled: | |
{ | |
var completion = new TaskCompletionSource<object>(); | |
completion.SetCanceled(); | |
return completion.Task; | |
} | |
default: | |
return BoxAwait(task); | |
} | |
} | |
static async Task<object> BoxAwait<T>(Task<T> task) | |
{ | |
return await task; | |
} | |
static async Task<object> BoxAwait(Task task) | |
{ | |
await task; | |
return null; | |
} | |
static long Tester(Func<Task> func, bool shouldThrow) | |
{ | |
GC.Collect(); | |
var sw = new Stopwatch(); | |
const int WarmupIterations = 10; | |
const int NormalIterations = 10000000 + WarmupIterations; | |
const int BadIterations = 100000 + WarmupIterations; | |
var iterations = shouldThrow ? BadIterations : NormalIterations; | |
for (var i = 0; i < iterations; i++) | |
{ | |
if (i == WarmupIterations) | |
{ | |
sw.Start(); | |
} | |
try | |
{ | |
func().GetAwaiter().GetResult(); | |
if (shouldThrow) | |
{ | |
throw new Exception("FAILED TO THROW"); | |
} | |
} | |
catch | |
{ | |
if (!shouldThrow) | |
{ | |
throw; | |
} | |
} | |
} | |
sw.Stop(); | |
return sw.ElapsedMilliseconds; | |
} | |
// Define other methods and classes here | |
public static async Task<object> InvokeAwait( | |
IReminderTableGrain grain, | |
int interfaceId, | |
int methodId, | |
object[] arguments) | |
{ | |
if (grain == null) throw new ArgumentNullException("grain"); | |
switch (interfaceId) | |
{ | |
case 0: | |
switch (methodId) | |
{ | |
case 1: | |
await grain.Go(); | |
return null; | |
case 2: | |
return await grain.GoInt(); | |
case 3: | |
return await grain.GoString(); | |
case 4: | |
return await grain.GoObject(); | |
case 5: | |
await grain.Go2(); | |
return null; | |
case 6: | |
return await grain.GoInt2(); | |
case 7: | |
return await grain.GoString2(); | |
case 8: | |
return await grain.GoObject2(); | |
case 9: | |
return await grain.GoFault(); | |
case 10: | |
return await grain.GoCancel(); | |
default: | |
throw new NotImplementedException("interfaceId=" + -1135060418 + ",methodId=" + methodId); | |
} | |
default: | |
throw new NotImplementedException("interfaceId=" + interfaceId); | |
} | |
} | |
public static Task<object> InvokeContinueWith( | |
IReminderTableGrain grain, | |
int interfaceId, | |
int methodId, | |
object[] arguments) | |
{ | |
if (grain == null) throw new ArgumentNullException("grain"); | |
switch (interfaceId) | |
{ | |
case 0: | |
switch (methodId) | |
{ | |
case 1: | |
return | |
grain.Go() | |
.ContinueWith(_ => | |
{ | |
_.Wait(); | |
return default(object); | |
}); | |
case 2: | |
return grain.GoInt().ContinueWith(_ => (object)_.Result); | |
case 3: | |
return grain.GoString().ContinueWith(_ => (object)_.Result); | |
case 4: | |
return grain.GoObject().ContinueWith(_ => (object)_.Result); | |
case 5: | |
return grain.Go2() | |
.ContinueWith(_ => | |
{ | |
_.Wait(); | |
return default(object); | |
}); | |
case 6: | |
return grain.GoInt2().ContinueWith(_ => (object)_.Result); | |
case 7: | |
return grain.GoString2().ContinueWith(_ => (object)_.Result); | |
case 8: | |
return grain.GoObject2().ContinueWith(_ => (object)_.Result); | |
case 9: | |
return grain.GoFault().ContinueWith(_ => (object)_.Result); | |
case 10: | |
return grain.GoCancel().ContinueWith(_ => (object)_.Result); | |
default: | |
throw new NotImplementedException("interfaceId=" + -1135060418 + ",methodId=" + methodId); | |
} | |
default: | |
throw new NotImplementedException("interfaceId=" + interfaceId); | |
} | |
} | |
public static Task<object> InvokeBox( | |
IReminderTableGrain grain, | |
int interfaceId, | |
int methodId, | |
object[] arguments) | |
{ | |
if (grain == null) throw new ArgumentNullException("grain"); | |
switch (interfaceId) | |
{ | |
case 0: | |
switch (methodId) | |
{ | |
case 1: | |
return Box(grain.Go()); | |
case 2: | |
return Box(grain.GoInt()); | |
case 3: | |
return Box(grain.GoString()); | |
case 4: | |
return Box(grain.GoObject()); | |
case 5: | |
return Box(grain.Go2()); | |
case 6: | |
return Box(grain.GoInt2()); | |
case 7: | |
return Box(grain.GoString2()); | |
case 8: | |
return Box(grain.GoObject2()); | |
case 9: | |
return Box(grain.GoFault()); | |
case 10: | |
return Box(grain.GoCancel()); | |
default: | |
throw new NotImplementedException("interfaceId=" + -1135060418 + ",methodId=" + methodId); | |
} | |
default: | |
throw new NotImplementedException("interfaceId=" + interfaceId); | |
} | |
} | |
public static Task<object> InvokeBoxMaySync( | |
IReminderTableGrain grain, | |
int interfaceId, | |
int methodId, | |
object[] arguments) | |
{ | |
if (grain == null) throw new ArgumentNullException("grain"); | |
switch (interfaceId) | |
{ | |
case 0: | |
switch (methodId) | |
{ | |
case 1: | |
return BoxMaySync(grain.Go()); | |
case 2: | |
return BoxMaySync(grain.GoInt()); | |
case 3: | |
return BoxMaySync(grain.GoString()); | |
case 4: | |
return BoxMaySync(grain.GoObject()); | |
case 5: | |
return BoxMaySync(grain.Go2()); | |
case 6: | |
return BoxMaySync(grain.GoInt2()); | |
case 7: | |
return BoxMaySync(grain.GoString2()); | |
case 8: | |
return BoxMaySync(grain.GoObject2()); | |
case 9: | |
return BoxMaySync(grain.GoFault()); | |
case 10: | |
return BoxMaySync(grain.GoCancel()); | |
default: | |
throw new NotImplementedException("interfaceId=" + -1135060418 + ",methodId=" + methodId); | |
} | |
default: | |
throw new NotImplementedException("interfaceId=" + interfaceId); | |
} | |
} | |
public static Task<object> InvokeBoxSync( | |
IReminderTableGrain grain, | |
int interfaceId, | |
int methodId, | |
object[] arguments) | |
{ | |
if (grain == null) throw new ArgumentNullException("grain"); | |
switch (interfaceId) | |
{ | |
case 0: | |
switch (methodId) | |
{ | |
case 1: | |
return BoxSync(grain.Go()); | |
case 2: | |
return BoxSync(grain.GoInt()); | |
case 3: | |
return BoxSync(grain.GoString()); | |
case 4: | |
return BoxSync(grain.GoObject()); | |
case 5: | |
return BoxSync(grain.Go2()); | |
case 6: | |
return BoxSync(grain.GoInt2()); | |
case 7: | |
return BoxSync(grain.GoString2()); | |
case 8: | |
return BoxSync(grain.GoObject2()); | |
case 9: | |
return BoxSync(grain.GoFault()); | |
case 10: | |
return BoxSync(grain.GoCancel()); | |
default: | |
throw new NotImplementedException("interfaceId=" + -1135060418 + ",methodId=" + methodId); | |
} | |
default: | |
throw new NotImplementedException("interfaceId=" + interfaceId); | |
} | |
} | |
public static Task<object> InvokeBoxAwait( | |
IReminderTableGrain grain, | |
int interfaceId, | |
int methodId, | |
object[] arguments) | |
{ | |
if (grain == null) throw new ArgumentNullException("grain"); | |
switch (interfaceId) | |
{ | |
case 0: | |
switch (methodId) | |
{ | |
case 1: | |
return BoxAwait(grain.Go()); | |
case 2: | |
return BoxAwait(grain.GoInt()); | |
case 3: | |
return BoxAwait(grain.GoString()); | |
case 4: | |
return BoxAwait(grain.GoObject()); | |
case 5: | |
return BoxAwait(grain.Go2()); | |
case 6: | |
return BoxAwait(grain.GoInt2()); | |
case 7: | |
return BoxAwait(grain.GoString2()); | |
case 8: | |
return BoxAwait(grain.GoObject2()); | |
case 9: | |
return BoxAwait(grain.GoFault()); | |
case 10: | |
return BoxAwait(grain.GoCancel()); | |
default: | |
throw new NotImplementedException("interfaceId=" + -1135060418 + ",methodId=" + methodId); | |
} | |
default: | |
throw new NotImplementedException("interfaceId=" + interfaceId); | |
} | |
} | |
public interface IReminderTableGrain | |
{ | |
Task Go(); | |
Task<int> GoInt(); | |
Task<string> GoString(); | |
Task<object> GoObject(); | |
Task Go2(); | |
Task<int> GoInt2(); | |
Task<string> GoString2(); | |
Task<object> GoObject2(); | |
Task<string> GoFault(); | |
Task<object> GoCancel(); | |
} | |
public class ReminderTableGrain : IReminderTableGrain | |
{ | |
private static readonly TaskCompletionSource<string> faulted; | |
private static readonly TaskCompletionSource<object> cancelled; | |
static ReminderTableGrain() | |
{ | |
faulted = new TaskCompletionSource<string>(); | |
faulted.TrySetException(new Exception("test")); | |
cancelled = new TaskCompletionSource<object>(); | |
cancelled.SetCanceled(); | |
} | |
public Task Go() | |
{ | |
return Task.FromResult(0); | |
} | |
public Task<int> GoInt() | |
{ | |
return Task.FromResult(0); | |
} | |
public Task<string> GoString() | |
{ | |
return Task.FromResult("test"); | |
} | |
public Task<object> GoObject() | |
{ | |
return Task.FromResult<object>("test"); | |
} | |
public Task Go2() | |
{ | |
return Task.Factory.StartNew(() => 0); | |
} | |
public Task<int> GoInt2() | |
{ | |
return Task.Factory.StartNew(() => 0); | |
} | |
public Task<string> GoString2() | |
{ | |
return Task.Factory.StartNew(() => "test"); | |
} | |
public Task<object> GoObject2() | |
{ | |
return Task.Factory.StartNew(() => (object)"test"); | |
} | |
public Task<string> GoFault() | |
{ | |
return faulted.Task; | |
} | |
public Task<object> GoCancel() | |
{ | |
return cancelled.Task; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment