Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

built-in accurate and cross platform Memory Diagnoser #284

Merged
merged 14 commits into from
Nov 25, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
update to netcoreapp1.1 in order to get universal cross platform memo…
…ry diagnoser
  • Loading branch information
adamsitnik committed Nov 13, 2016
commit 1e2d381b6a7ba34d53140f28f4481d3d829b6260
4 changes: 2 additions & 2 deletions samples/BenchmarkDotNet.Samples.FSharp.Core/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"Microsoft.FSharp.Core.netcore": "1.0.0-*",
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"
"version": "1.1.0-preview1-001100-00"
},
"BenchmarkDotNet": {
"target": "project"
Expand All @@ -31,7 +31,7 @@
}
},
"frameworks": {
"netcoreapp1.0": {
"netcoreapp1.1": {
"imports": [
"portable-net45+win8",
"dnxcore50",
Expand Down
4 changes: 2 additions & 2 deletions samples/BenchmarkDotNet.Samples/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@
}
}
},
"netcoreapp1.0": {
"netcoreapp1.1": {
"buildOptions": {
"define": [ "CORE" ]
},
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"
"version": "1.1.0-preview1-001100-00"
},
"System.ComponentModel.EventBasedAsync": "4.0.11"
}
Expand Down
4 changes: 0 additions & 4 deletions src/BenchmarkDotNet.Core/Diagnosers/MemoryDiagnoser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,12 @@ public AllocationColumn(Dictionary<Benchmark, GcStats> results)

public string GetValue(Summary summary, Benchmark benchmark)
{
#if CORE
return "?";
#else
if (RuntimeInformation.IsMono())
return "?";
if (!results.ContainsKey(benchmark))
return "N/A";

return results[benchmark].BytesAllocatedPerOperation.ToString("N0", HostEnvironmentInfo.MainCultureInfo);
#endif
}
}

Expand Down
31 changes: 23 additions & 8 deletions src/BenchmarkDotNet.Core/Engines/GcStats.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Reflection;
using BenchmarkDotNet.Portability;

namespace BenchmarkDotNet.Engines
Expand All @@ -7,6 +8,8 @@ public struct GcStats
{
internal const string ResultsLinePrefix = "GC: ";

private static readonly Func<long> getAllocatedBytesForCurrentThread = GetAllocatedBytesForCurrentThread();

private GcStats(int gen0Collections, int gen1Collections, int gen2Collections, long allocatedBytes, long totalOperations)
{
Gen0Collections = gen0Collections;
Expand Down Expand Up @@ -50,7 +53,7 @@ public GcStats WithTotalOperations(long totalOperationsCount)

internal static GcStats ReadInitial(bool isDiagnosticsEnabled)
{
// this might force GC.Collect, so we want to do this before collecting collections counts
// this will force GC.Collect, so we want to do this before collecting collections counts
long allocatedBytes = GetAllocatedBytes(isDiagnosticsEnabled);

return new GcStats(
Expand All @@ -68,7 +71,7 @@ internal static GcStats ReadFinal(bool isDiagnosticsEnabled)
GC.CollectionCount(1),
GC.CollectionCount(2),

// this might force GC.Collect, so we want to do this after collecting collections counts
// this will force GC.Collect, so we want to do this after collecting collections counts
// to exclude this single full forced collection from results
GetAllocatedBytes(isDiagnosticsEnabled),
0);
Expand All @@ -79,22 +82,34 @@ public static GcStats FromForced(int forcedFullGarbageCollections)

private static long GetAllocatedBytes(bool isDiagnosticsEnabled)
{
#if NETCOREAPP11 // when MS releases new version of .NET Runtime to nuget.org
return GC.GetAllocatedBytesForCurrentThread(); // https://github.com/dotnet/corefx/pull/12489
#elif CLASSIC
if (!isDiagnosticsEnabled || RuntimeInformation.IsMono()) // Monitoring is not available in Mono, see http://stackoverflow.com/questions/40234948/how-to-get-the-number-of-allocated-bytes-
if (!isDiagnosticsEnabled
|| RuntimeInformation.IsMono()) // Monitoring is not available in Mono, see http://stackoverflow.com/questions/40234948/how-to-get-the-number-of-allocated-bytes-
return 0;

// "This instance Int64 property returns the number of bytes that have been allocated by a specific
// AppDomain. The number is accurate as of the last garbage collection." - CLR via C#
// so we enforce GC.Collect here just to make sure we get accurate results
GC.Collect();
#if CORE
return getAllocatedBytesForCurrentThread.Invoke();
#elif CLASSIC

return AppDomain.CurrentDomain.MonitoringTotalAllocatedMemorySize;

This comment was marked as spam.

This comment was marked as spam.

#else
return 0; // currently for .NET Core
#endif
}

private static Func<long> GetAllocatedBytesForCurrentThread()
{
// for some versions of .NET Core this method is internal,
// for some public and for others public and exposed ;)

This comment was marked as spam.

This comment was marked as spam.

This comment was marked as spam.

var method = typeof(GC).GetTypeInfo().GetMethod("GetAllocatedBytesForCurrentThread",
BindingFlags.Public | BindingFlags.Static)
?? typeof(GC).GetTypeInfo().GetMethod("GetAllocatedBytesForCurrentThread",
BindingFlags.NonPublic | BindingFlags.Static);

return () => (long)method.Invoke(null, null);
}

internal string ToOutputLine()
=> $"{ResultsLinePrefix} {Gen0Collections} {Gen1Collections} {Gen2Collections} {AllocatedBytes} {TotalOperations}";

Expand Down
4 changes: 1 addition & 3 deletions src/BenchmarkDotNet.Core/Toolchains/Core/CoreToolchain.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
using BenchmarkDotNet.Characteristics;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Extensions;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Portability;
Expand All @@ -12,7 +10,7 @@ namespace BenchmarkDotNet.Toolchains.Core
{
public class CoreToolchain : Toolchain
{
private const string TargetFrameworkMoniker = "netcoreapp1.0";
private const string TargetFrameworkMoniker = "netcoreapp1.1";

public static readonly IToolchain Instance = new CoreToolchain();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ internal static bool ExecuteCommand(string commandWithArguments, string workingD

// don't forget to call, otherwise logger will not get any events
process.BeginErrorReadLine();
process.BeginOutputReadLine();

process.WaitForExit((int)timeout.TotalMilliseconds);

Expand Down
28 changes: 6 additions & 22 deletions src/BenchmarkDotNet.Core/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,34 +51,18 @@
"System.Threading.Tasks.Extensions": "4.0.0"
}
},
"netstandard1.5": {
"netcoreapp1.1": {
"buildOptions": {
"define": [ "CORE" ]
},
"dependencies": {
"System.Linq": "4.1.0",
"System.Resources.ResourceManager": "4.0.1",
"Microsoft.CSharp": "4.0.1",
"Microsoft.Win32.Primitives": "4.0.1",
"System.Console": "4.0.0",
"System.Text.RegularExpressions": "4.1.0",
"System.Threading": "4.0.11",
"System.Reflection": "4.1.0",
"System.Reflection.Primitives": "4.0.1",
"System.Reflection.TypeExtensions": "4.1.0",
"System.Threading.Tasks.Extensions": "4.0.0",
"System.Threading.Thread": "4.0.0",
"System.Diagnostics.Process": "4.1.0",
"System.IO.FileSystem": "4.0.1",
"System.Runtime.InteropServices.RuntimeInformation": "4.0.0",
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0-preview1-001100-00"
},
"System.Runtime.Serialization.Primitives": "4.1.1",
"System.Diagnostics.Tools": "4.0.1",
"System.Runtime.InteropServices": "4.1.0",
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
"System.Reflection.Extensions": "4.0.1",
"System.Diagnostics.Debug": "4.0.11",
"System.Xml.XPath.XmlDocument": "4.0.1",
"System.Collections.Concurrent": "4.0.12"
"System.Xml.XPath.XmlDocument": "4.0.1"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/BenchmarkDotNet/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
}
}
},
"netstandard1.5": {
"netcoreapp1.1": {
"buildOptions": {
"define": [ "CORE" ]
},
Expand Down
35 changes: 11 additions & 24 deletions tests/BenchmarkDotNet.IntegrationTests/MemoryDiagnoserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,21 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Engines;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Tests.Loggers;
using Xunit;
using Xunit.Abstractions;
using BenchmarkDotNet.Reports;

namespace BenchmarkDotNet.IntegrationTests
{
public class MemoryDiagnoserTests
{
private const string SkipAllocationsTests
#if CORE
= "Not supported for .NET Core yet";
#else
= null;
#endif

private readonly ITestOutputHelper output;

public MemoryDiagnoserTests(ITestOutputHelper outputHelper)
Expand All @@ -40,19 +29,17 @@ public class AccurateAllocations
[Benchmark]public void Empty() { }
[Benchmark]public byte[] EightBytes() => new byte[8];
[Benchmark]public byte[] SixtyFourBytes() => new byte[64];
[Benchmark]public byte[] ThousandBytes() => new byte[1000];
}

[Fact(Skip = SkipAllocationsTests)]
[Fact]
public void MemoryDiagnoserIsAccurate()
{
double objectAllocationOverhead = IntPtr.Size * 3; // pointer to method table + object header word + pointer to the object
AssertAllocations(typeof(AccurateAllocations), 100, new Dictionary<string, Predicate<double>>
long objectAllocationOverhead = IntPtr.Size * 3; // pointer to method table + object header word + pointer to the object
AssertAllocations(typeof(AccurateAllocations), 200, new Dictionary<string, Predicate<long>>
{
{ "Empty", allocatedBytes => allocatedBytes == 0 },
{ "EightBytes", allocatedBytes => allocatedBytes == 8 + objectAllocationOverhead },
{ "SixtyFourBytes", allocatedBytes => allocatedBytes == 64 + objectAllocationOverhead },
{ "ThousandBytes", allocatedBytes => allocatedBytes == 1000 + objectAllocationOverhead }
});
}

Expand All @@ -74,10 +61,10 @@ private void AllocateUntilGcWakesUp()
}
}

[Fact(Skip = SkipAllocationsTests)]
[Fact]
public void MemoryDiagnoserDoesNotIncludeAllocationsFromSetupAndCleanup()
{
AssertAllocations(typeof(AllocatingSetupAndCleanup), 5, new Dictionary<string, Predicate<double>>
AssertAllocations(typeof(AllocatingSetupAndCleanup), 100, new Dictionary<string, Predicate<long>>
{
{ "AllocateNothing", allocatedBytes => allocatedBytes == 0 }
});
Expand All @@ -88,17 +75,17 @@ public class NoAllocationsAtAll
[Benchmark]public void EmptyMethod() { }
}

[Fact(Skip = SkipAllocationsTests)]
[Fact]
public void EngineShouldNotInterfereAllocationResults()
{
AssertAllocations(typeof(NoAllocationsAtAll), 100, new Dictionary<string, Predicate<double>>
AssertAllocations(typeof(NoAllocationsAtAll), 100, new Dictionary<string, Predicate<long>>
{
{ "EmptyMethod", allocatedBytes => allocatedBytes == 0 }
});
}

private void AssertAllocations(Type benchmarkType, int targetCount,
Dictionary<string, Predicate<double>> benchmarksAllocationsValidators)
Dictionary<string, Predicate<long>> benchmarksAllocationsValidators)
{
var memoryDiagnoser = MemoryDiagnoser.Default;
var config = CreateConfig(memoryDiagnoser, targetCount);
Expand Down Expand Up @@ -140,10 +127,10 @@ private IConfig CreateConfig(IDiagnoser diagnoser, int targetCount)
private static T[] GetColumns<T>(MemoryDiagnoser memoryDiagnoser)
=> memoryDiagnoser.GetColumnProvider().GetColumns(null).OfType<T>().ToArray();

private static void AssertParsed(string text, Predicate<double> condition)
private static void AssertParsed(string text, Predicate<long> condition)
{
double value;
if (double.TryParse(text, NumberStyles.Number, HostEnvironmentInfo.MainCultureInfo, out value))
long value;
if (long.TryParse(text, NumberStyles.Number, HostEnvironmentInfo.MainCultureInfo, out value))
{
Assert.True(condition(value), $"Failed for value {value}");
}
Expand Down
4 changes: 2 additions & 2 deletions tests/BenchmarkDotNet.IntegrationTests/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"allowUnsafe": true
},
"frameworks": {
"netcoreapp1.0": {
"netcoreapp1.1": {
"buildOptions": {
"define": [ "CORE" ]
},
Expand All @@ -33,7 +33,7 @@
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"
"version": "1.1.0-preview1-001100-00"
},
"System.ComponentModel.EventBasedAsync": "4.0.11"
}
Expand Down
4 changes: 2 additions & 2 deletions tests/BenchmarkDotNet.Tests/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"copyToOutput": [ "xunit.runner.json" ]
},
"frameworks": {
"netcoreapp1.0": {
"netcoreapp1.1": {
"imports": [
"dnxcore50",
"portable-net45+win8",
Expand All @@ -29,7 +29,7 @@
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"
"version": "1.1.0-preview1-001100-00"
},
"System.ComponentModel.EventBasedAsync": "4.0.11"
}
Expand Down
2 changes: 1 addition & 1 deletion tests/runCoreTests.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ echo -----------------------------
echo Running Core tests
echo -----------------------------

call dotnet test "BenchmarkDotNet.IntegrationTests/" --configuration Release --framework netcoreapp1.0 2> failedCoreTests.txt
call dotnet test "BenchmarkDotNet.IntegrationTests/" --configuration Release --framework netcoreapp1.1 2> failedCoreTests.txt

if NOT %ERRORLEVEL% == 0 (
echo CORE tests has failed
Expand Down