improved test design

added perf. profiling for StrictlyOrderedAwaitTask
This commit is contained in:
Christian Köllner 2020-04-21 18:54:16 +02:00
parent debe76be89
commit 65e87e5aa9
6 changed files with 112 additions and 26 deletions

View File

@ -12,8 +12,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Capnp.Net.Runtime" Version="1.3.88-g218ad92525" />
<PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.3.88-g218ad92525" />
<PackageReference Include="Capnp.Net.Runtime" Version="1.3.89-gdebe76be89" />
<PackageReference Include="CapnpC.CSharp.MsBuild.Generation" Version="1.3.89-gdebe76be89" />
</ItemGroup>
</Project>

View File

@ -18,6 +18,10 @@
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netcoreapp2.1|AnyCPU'">
<DefineConstants>TRACE;SOTASK_PERF</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />

View File

@ -53,7 +53,6 @@ namespace Capnp.Net.Runtime.Tests
public void Cancel()
{
var t = new TcpRpcPorted();
t.InitConsoleLogging();
Repeat(1000, t.Cancel);
}
@ -61,7 +60,6 @@ namespace Capnp.Net.Runtime.Tests
public void Embargo()
{
var t = new TcpRpcPorted();
t.InitConsoleLogging();
Repeat(100,
() =>
NewLocalhostTcpTestbed(TcpRpcTestOptions.ClientTracer | TcpRpcTestOptions.ClientFluctStream)
@ -72,7 +70,6 @@ namespace Capnp.Net.Runtime.Tests
public void EmbargoServer()
{
var t2 = new TcpRpcInterop();
t2.InitConsoleLogging();
Repeat(20, t2.EmbargoServer);
}
@ -82,11 +79,9 @@ namespace Capnp.Net.Runtime.Tests
// Some code paths are really rare during this test, therefore increased repetition count.
var t = new TcpRpcPorted();
t.InitConsoleLogging();
Repeat(1000, t.EmbargoNull);
var t2 = new TcpRpcInterop();
t2.InitConsoleLogging();
Repeat(100, t2.EmbargoNullServer);
}
@ -94,7 +89,6 @@ namespace Capnp.Net.Runtime.Tests
public void RetainAndRelease()
{
var t = new TcpRpcPorted();
t.InitConsoleLogging();
Repeat(100, t.RetainAndRelease);
}
@ -102,7 +96,6 @@ namespace Capnp.Net.Runtime.Tests
public void PipelineAfterReturn()
{
var t = new TcpRpc();
t.InitConsoleLogging();
Repeat(100, t.PipelineAfterReturn);
}

View File

@ -1,5 +1,6 @@
using Capnp.Net.Runtime.Tests.Util;
using Capnp.Rpc;
using Capnp.Util;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
@ -351,6 +352,32 @@ namespace Capnp.Net.Runtime.Tests
protected ILogger Logger { get; set; }
protected TestBase()
{
Logging.LoggerFactory?.Dispose();
#pragma warning disable CS0618
Logging.LoggerFactory = new LoggerFactory().AddConsole((msg, level) => true);
#pragma warning restore CS0618
Logger = Logging.CreateLogger<TcpRpcStress>();
if (Thread.CurrentThread.Name == null)
Thread.CurrentThread.Name = $"Test Thread {Thread.CurrentThread.ManagedThreadId}";
#if SOTASK_PERF
StrictlyOrderedTaskExtensions.Stats.Reset();
#endif
}
[TestCleanup]
public void TestCleanup()
{
#if SOTASK_PERF
Console.WriteLine($"StrictlyOrderedTask performance statistics:");
Console.WriteLine($"AwaitInternal: max. {StrictlyOrderedTaskExtensions.Stats.AwaitInternalMaxOuterIterations} outer iterations");
Console.WriteLine($"AwaitInternal: max. {StrictlyOrderedTaskExtensions.Stats.AwaitInternalMaxInnerIterations} inner iterations");
Console.WriteLine($"OnCompleted: max. {StrictlyOrderedTaskExtensions.Stats.OnCompletedMaxSpins} iterations");
#endif
}
protected static TcpRpcClient SetupClient(IPAddress addr, int port, TcpRpcTestOptions options = TcpRpcTestOptions.None)
{
var client = new TcpRpcClient();
@ -404,18 +431,6 @@ namespace Capnp.Net.Runtime.Tests
protected static LocalTestbed NewLocalTestbed() => new LocalTestbed();
[TestInitialize]
public void InitConsoleLogging()
{
Logging.LoggerFactory?.Dispose();
#pragma warning disable CS0618
Logging.LoggerFactory = new LoggerFactory().AddConsole((msg, level) => true);
#pragma warning restore CS0618
Logger = Logging.CreateLogger<TcpRpcStress>();
if (Thread.CurrentThread.Name == null)
Thread.CurrentThread.Name = $"Test Thread {Thread.CurrentThread.ManagedThreadId}";
}
/// <summary>
/// Somewhat ugly helper method which ensures that both Tcp client and server
/// are waiting for data reception from each other. This is a "balanced" state, meaning

View File

@ -33,7 +33,7 @@
</PropertyGroup>
<PropertyGroup>
<DefineConstants></DefineConstants>
<DefineConstants>SOTASK_PERF</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Release|netstandard2.0|AnyCPU'">
@ -43,6 +43,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
</Project>

View File

@ -53,18 +53,37 @@ namespace Capnp.Util
}
finally
{
#if SOTASK_PERF
long outerCount = 0, innerCount = 0;
#endif
SpinWait.SpinUntil(() =>
{
Action? continuations;
do
while (true)
{
#if SOTASK_PERF
++innerCount;
#endif
continuations = (Action?)Interlocked.Exchange(ref _state, null);
continuations?.Invoke();
} while (continuations != null);
if (continuations != null)
continuations();
else
break;
}
#if SOTASK_PERF
++outerCount;
#endif
return Interlocked.CompareExchange(ref _state, s_seal, null) == null;
});
#if SOTASK_PERF
StrictlyOrderedTaskExtensions.Stats.UpdateAwaitInternal(outerCount, innerCount);
#endif
}
}
@ -75,7 +94,16 @@ namespace Capnp.Util
{
bool first = false;
#if SOTASK_PERF
long spinCount = 0;
#endif
SpinWait.SpinUntil(() => {
#if SOTASK_PERF
++spinCount;
#endif
object? cur, next;
cur = Volatile.Read(ref _state);
first = false;
@ -102,6 +130,10 @@ namespace Capnp.Util
return Interlocked.CompareExchange(ref _state, next, cur) == cur;
});
#if SOTASK_PERF
StrictlyOrderedTaskExtensions.Stats.UpdateOnCompleted(spinCount);
#endif
if (first)
{
AwaitInternal();
@ -168,6 +200,48 @@ namespace Capnp.Util
/// </summary>
public static class StrictlyOrderedTaskExtensions
{
#if SOTASK_PERF
public class Statistics
{
internal long _awaitInternalMaxOuterIterations;
internal long _awaitInternalMaxInnerIterations;
internal long _onCompletedMaxSpins;
public long AwaitInternalMaxOuterIterations => Volatile.Read(ref _awaitInternalMaxOuterIterations);
public long AwaitInternalMaxInnerIterations => Volatile.Read(ref _awaitInternalMaxInnerIterations);
public long OnCompletedMaxSpins => Volatile.Read(ref _onCompletedMaxSpins);
public void Reset()
{
Volatile.Write(ref _awaitInternalMaxOuterIterations, 0);
Volatile.Write(ref _awaitInternalMaxInnerIterations, 0);
Volatile.Write(ref _onCompletedMaxSpins, 0);
}
internal static void InterlockedMax(ref long current, long value)
{
long existing;
do
{
existing = Volatile.Read(ref current);
if (value <= existing) return;
} while (Interlocked.CompareExchange(ref current, value, existing) != existing);
}
internal void UpdateAwaitInternal(long outerCount, long innerCount)
{
InterlockedMax(ref _awaitInternalMaxOuterIterations, outerCount);
InterlockedMax(ref _awaitInternalMaxInnerIterations, innerCount);
}
internal void UpdateOnCompleted(long spinCount)
{
InterlockedMax(ref _onCompletedMaxSpins, spinCount);
}
}
public static readonly Statistics Stats = new Statistics();
#endif
/// <summary>
/// Converts the task to a task-like object which enforces that all await operations from the same thread leave in the exact order they were issued.
/// </summary>