Skip to content

Commit

Permalink
Fix mimalloc callback, add remaining API and tests/benches
Browse files Browse the repository at this point in the history
  • Loading branch information
buybackoff committed Feb 16, 2020
1 parent d191805 commit 4b7bf65
Show file tree
Hide file tree
Showing 13 changed files with 1,311 additions and 208 deletions.
2 changes: 1 addition & 1 deletion dotnet/build/common.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<QVersion>1</QVersion>

<!-- Overrides -->
<MMDDVersion>215</MMDDVersion>
<MMDDVersion>216</MMDDVersion>
<PackageHHMMSSSuffix></PackageHHMMSSSuffix>

</PropertyGroup>
Expand Down
367 changes: 313 additions & 54 deletions dotnet/src/Spreads.Native/Mem.cs

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dotnet/tests/Spreads.Native.Run/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ private static void Main(string[] args)

// var summary = BenchmarkRunner.Run<Benchmark>();

var test = new Tests.VecTests();
test.ForEachBench();
var test = new Tests.MemTests();
test.MimallocAllocFreePerf();

//var offset = UnsafeExTests.Helper<int>.ElemOffset;
//var size = UnsafeExTests.Helper<int>.ElemSize;
Expand Down
2 changes: 1 addition & 1 deletion dotnet/tests/Spreads.Native.Run/Spreads.Native.Run.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>7.2</LangVersion>
<Prefer32Bit>false</Prefer32Bit>
<Platforms>AnyCPU</Platforms>
<Platforms>x64</Platforms>
<!--<RestoreProjectStyle>PackageReference</RestoreProjectStyle>-->
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<OutputType>Exe</OutputType>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="HdrHistogram" Version="2.5.0" />
<PackageReference Include="Spreads.Native" Version="2020.1.*" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
Expand Down
154 changes: 99 additions & 55 deletions dotnet/tests/Spreads.Native.Tests/Benchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@

namespace Spreads.Native.Tests
{
// Copy from Spreads.Core, do not edit here
// TODO median for Dump() MOPS

/// <summary>
/// A utility to benchmark code snippets inside a using block.
/// </summary>
public static class Benchmark
{
/// <summary>
Expand All @@ -23,7 +26,9 @@ public static class Benchmark

private static Stopwatch _sw;
private static bool _headerIsPrinted;
private static readonly ConcurrentDictionary<string, List<Stat>> Stats = new ConcurrentDictionary<string, List<Stat>>();

private static readonly ConcurrentDictionary<string, List<Stat>> Stats =
new ConcurrentDictionary<string, List<Stat>>();

/// <summary>
/// Returns an <see cref="IDisposable"/> structure that starts benchmarking and stops it when its Dispose method is called.
Expand All @@ -43,15 +48,42 @@ public static Stat Run(string caseName, long innerLoopCount = 1, bool silent = f
return stat;
}

private static double NaiveMedian(ArraySegment<double> sourceNumbers)
{
//Framework 2.0 version of this method. there is an easier way in F4
if (sourceNumbers == null || sourceNumbers.Count == 0)
throw new Exception("Median of empty array not defined.");

//make sure the list is sorted, but use a new array
double[] sortedPNumbers = sourceNumbers.ToArray();
Array.Sort(sortedPNumbers);

//get the median
int size = sortedPNumbers.Length;
int mid = size / 2;
double median = (size % 2 != 0)
? (double) sortedPNumbers[mid]
: ((double) sortedPNumbers[mid] + (double) sortedPNumbers[mid - 1]) / 2;
return median;
}

private static void PrintHeader(string summary, string caller, int? caseLength = null, string unit = null)
{

var len = caseLength ?? 20;
var caseDahes = new string('-', len + 1);
var dashes = $"{caseDahes,-21}|{new string('-', 8),8}:|{new string('-', 9),9}:|{new string('-', 6),6}:|{new string('-', 6),6}:|{new string('-', 6),6}:|{new string('-', 8),8}:";
var dashes =
$"{caseDahes,-21}|{new string('-', 8),8}:|{new string('-', 9),9}:|{new string('-', 6),6}:|{new string('-', 6),6}:|{new string('-', 6),6}:|{new string('-', 8),8}:";
Console.WriteLine();
if (!string.IsNullOrWhiteSpace(caller)) { Console.WriteLine($"**{caller}**"); }
if (!string.IsNullOrWhiteSpace(summary)) { Console.WriteLine($"*{summary}*"); }
if (!string.IsNullOrWhiteSpace(caller))
{
Console.WriteLine($"**{caller}**");
}

if (!string.IsNullOrWhiteSpace(summary))
{
Console.WriteLine($"*{summary}*");
}

Console.WriteLine();
Console.WriteLine(GetHeader(caseLength, unit));
Console.WriteLine(dashes);
Expand All @@ -71,7 +103,7 @@ internal static string GetHeader(int? caseLength = null, string unit = null)
/// <param name="summary"></param>
/// <param name="caller">A description of the benchmark that is printed above the table.</param>
/// <param name="unit">Overwrite default MOPS unit of measure</param>
public static void Dump(string summary = "", [CallerMemberName]string caller = "", string unit = null)
public static void Dump(string summary = "", [CallerMemberName] string caller = "", string unit = null)
{
var maxLength = Stats.Keys.Select(k => k.Length).Max();

Expand All @@ -91,25 +123,28 @@ public static void Dump(string summary = "", [CallerMemberName]string caller = "
Stat GetAverages(KeyValuePair<string, List<Stat>> kvp)
{
if (kvp.Value == null) throw new ArgumentException($"Null stat list for the case: {kvp.Key}");
if (kvp.Value.Count == 0) throw new InvalidOperationException($"Empty stat list for the case: {kvp.Key}");
if (kvp.Value.Count == 0)
throw new InvalidOperationException($"Empty stat list for the case: {kvp.Key}");
var skip = kvp.Value.Count > 1
? (kvp.Value.Count >= 10 ? 3 : 1)
: 0;
var values = kvp.Value.Skip(skip).ToList();

var elapsed = values.Select(l => l._statSnapshot._elapsed).Average();
var gc0 = values.Select(l => l._statSnapshot._gc0).Average();
var gc1 = values.Select(l => l._statSnapshot._gc1).Average();
var gc2 = values.Select(l => l._statSnapshot._gc2).Average();
var memory = values.Select(l => l._statSnapshot._memory).Average();
var elapsed =
NaiveMedian(
new ArraySegment<double>(values.Select(l => (double) l._statSnapshot.Elapsed).ToArray()));
var gc0 = values.Select(l => l._statSnapshot.Gc0).Average();
var gc1 = values.Select(l => l._statSnapshot.Gc1).Average();
var gc2 = values.Select(l => l._statSnapshot.Gc2).Average();
var memory = values.Select(l => l._statSnapshot.Memory).Average();

var result = kvp.Value.First();

result._statSnapshot._elapsed = (long)elapsed;
result._statSnapshot._gc0 = gc0;
result._statSnapshot._gc1 = gc1;
result._statSnapshot._gc2 = gc2;
result._statSnapshot._memory = memory;
result._statSnapshot.Elapsed = (long) elapsed;
result._statSnapshot.Gc0 = gc0;
result._statSnapshot.Gc1 = gc1;
result._statSnapshot.Gc2 = gc2;
result._statSnapshot.Memory = memory;

return result;
}
Expand All @@ -120,37 +155,37 @@ Stat GetAverages(KeyValuePair<string, List<Stat>> kvp)
/// </summary>
public struct Stat : IDisposable
{
internal readonly string _caseName;
internal Stopwatch _stopwatch;
internal long _innerLoopCount;
internal StatSnapshot _statSnapshot;
public string CaseName { get; }
public Stopwatch Stopwatch { get; }
public long InnerLoopCount { get; }
public StatSnapshot _statSnapshot;
internal bool _silent;
private readonly string _unit;

internal Stat(string caseName, Stopwatch sw, long innerLoopCount, bool silent = false, string unit = null)
{
_caseName = caseName;
_stopwatch = sw;
_innerLoopCount = innerLoopCount;
CaseName = caseName;
Stopwatch = sw;
InnerLoopCount = innerLoopCount;
_silent = silent;
_unit = unit;

_statSnapshot = new StatSnapshot(_stopwatch, true);
_statSnapshot = new StatSnapshot(Stopwatch, true);
}

/// <inheritdoc />
public void Dispose()
{
var statEntry = new StatSnapshot(_stopwatch, false);
Interlocked.Exchange(ref _sw, _stopwatch);
var statEntry = new StatSnapshot(Stopwatch, false);
Interlocked.Exchange(ref _sw, Stopwatch);

_statSnapshot._elapsed = statEntry._elapsed;
_statSnapshot._gc0 = statEntry._gc0 - _statSnapshot._gc0 - 2;
_statSnapshot._gc1 = statEntry._gc1 - _statSnapshot._gc1 - 2;
_statSnapshot._gc2 = statEntry._gc2 - _statSnapshot._gc2 - 2;
_statSnapshot._memory = statEntry._memory - _statSnapshot._memory;
_statSnapshot.Elapsed = statEntry.Elapsed;
_statSnapshot.Gc0 = statEntry.Gc0 - _statSnapshot.Gc0;
_statSnapshot.Gc1 = statEntry.Gc1 - _statSnapshot.Gc1;
_statSnapshot.Gc2 = statEntry.Gc2 - _statSnapshot.Gc2;
_statSnapshot.Memory = statEntry.Memory - _statSnapshot.Memory;

var list = Stats.GetOrAdd(_caseName, (s1) => new List<Stat>());
var list = Stats.GetOrAdd(CaseName, (s1) => new List<Stat>());
list.Add(this);

if (!_silent && !ForceSilence)
Expand All @@ -160,6 +195,7 @@ public void Dispose()
PrintHeader(null, null, unit: _unit);
_headerIsPrinted = true;
}

Console.WriteLine(ToString());
}
}
Expand All @@ -168,29 +204,36 @@ public void Dispose()
/// Million operations per second.
/// </summary>
// ReSharper disable once InconsistentNaming
public double MOPS => Math.Round((_innerLoopCount * 0.001) / _statSnapshot._elapsed, 3);
public double MOPS => Math.Round((InnerLoopCount * 0.001) / (_statSnapshot.Elapsed / 10000.0), 3);

internal StatSnapshot StatSnapshot
{
get { return _statSnapshot; }
}

/// <inheritdoc />
public override string ToString()
{
var trimmedCaseName = _caseName.Length > 20 ? _caseName.Substring(0, 17) + "..." : _caseName;
return $"{trimmedCaseName,-20} |{MOPS,8:f2} | {_statSnapshot._elapsed,5} ms | {_statSnapshot._gc0,5:f1} | {_statSnapshot._gc1,5:f1} | {_statSnapshot._gc2,5:f1} | {_statSnapshot._memory / (1024 * 1024.0),5:f3} MB";
var trimmedCaseName = CaseName.Length > 20 ? CaseName.Substring(0, 17) + "..." : CaseName;
return
$"{trimmedCaseName,-20} |{MOPS,8:f2} | {_statSnapshot.Elapsed / 10000.0:N0} ms | {_statSnapshot.Gc0,5:f1} | {_statSnapshot.Gc1,5:f1} | {_statSnapshot.Gc2,5:f1} | {_statSnapshot.Memory / (1024 * 1024.0),5:f3} MB";
}

internal string ToString(int caseAlignmentLength)
{
var paddedCaseName = _caseName.PadRight(caseAlignmentLength);
return $"{paddedCaseName,-20} |{MOPS,8:f2} | {_statSnapshot._elapsed,5} ms | {_statSnapshot._gc0,5:f1} | {_statSnapshot._gc1,5:f1} | {_statSnapshot._gc2,5:f1} | {_statSnapshot._memory / (1024 * 1024.0),5:f3} MB";
var paddedCaseName = CaseName.PadRight(caseAlignmentLength);
return
$"{paddedCaseName,-20} |{MOPS,8:f2} | {_statSnapshot.Elapsed / 10000.0:N0} ms | {_statSnapshot.Gc0,5:f1} | {_statSnapshot.Gc1,5:f1} | {_statSnapshot.Gc2,5:f1} | {_statSnapshot.Memory / (1024 * 1024.0),5:f3} MB";
}
}

internal struct StatSnapshot
public struct StatSnapshot
{
internal long _elapsed;
internal double _gc0;
internal double _gc1;
internal double _gc2;
internal double _memory;
public long Elapsed;
public double Gc0;
public double Gc1;
public double Gc2;
public double Memory;

public StatSnapshot(Stopwatch sw, bool start)
{
Expand All @@ -200,26 +243,27 @@ public StatSnapshot(Stopwatch sw, bool start)
{
// end of measurement, first stop timer then collect/count
sw.Stop();
_elapsed = sw.ElapsedMilliseconds;
Elapsed = sw.Elapsed.Ticks;

// NB we exclude forced GC from counters,
// by measuring memory before forced GC we could
// calculate uncollected garbage
_memory = GC.GetTotalMemory(false);
Memory = GC.GetTotalMemory(false);
}

GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
GC.Collect(2, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
//GC.Collect(2, GCCollectionMode.Forced, true);
//GC.WaitForPendingFinalizers();
//GC.Collect(2, GCCollectionMode.Forced, true);
//GC.WaitForPendingFinalizers();


_gc0 = GC.CollectionCount(0);
_gc1 = GC.CollectionCount(1);
_gc2 = GC.CollectionCount(2);
Gc0 = GC.CollectionCount(0);
Gc1 = GC.CollectionCount(1);
Gc2 = GC.CollectionCount(2);

if (start)
{
_memory = GC.GetTotalMemory(false);
Memory = GC.GetTotalMemory(false);
// start timer after collecting GC stat
sw.Restart();
}
Expand Down
Loading

0 comments on commit 4b7bf65

Please sign in to comment.