Sample: IntroNativeMemory
The NativeMemoryProfiler
uses EtwProfiler
to profile the code using ETW and adds the extra columns Allocated native memory
and Native memory leak
to the benchmark results table.
Source code
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Diagnostics.Windows.Configs;
namespace BenchmarkDotNet.Samples
{
[ShortRunJob]
[NativeMemoryProfiler]
[MemoryDiagnoser]
public class IntroNativeMemory
{
[Benchmark]
public void BitmapWithLeaks()
{
var flag = new Bitmap(200, 100);
var graphics = Graphics.FromImage(flag);
var blackPen = new Pen(Color.Black, 3);
graphics.DrawLine(blackPen, 100, 100, 500, 100);
}
[Benchmark]
public void Bitmap()
{
using (var flag = new Bitmap(200, 100))
{
using (var graphics = Graphics.FromImage(flag))
{
using (var blackPen = new Pen(Color.Black, 3))
{
graphics.DrawLine(blackPen, 100, 100, 500, 100);
}
}
}
}
private const int Size = 20; // Greater value could cause System.OutOfMemoryException for test with memory leaks.
private int ArraySize = Size * Marshal.SizeOf(typeof(int));
[Benchmark]
public unsafe void AllocHGlobal()
{
IntPtr unmanagedHandle = Marshal.AllocHGlobal(ArraySize);
Span<byte> unmanaged = new Span<byte>(unmanagedHandle.ToPointer(), ArraySize);
Marshal.FreeHGlobal(unmanagedHandle);
}
[Benchmark]
public unsafe void AllocHGlobalWithLeaks()
{
IntPtr unmanagedHandle = Marshal.AllocHGlobal(ArraySize);
Span<byte> unmanaged = new Span<byte>(unmanagedHandle.ToPointer(), ArraySize);
}
}
}
Output
Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | Allocated native memory | Native memory leak |
---|---|---|---|---|---|---|---|---|---|
BitmapWithLeaks | 73,456.43 ns | 22,498.10 ns | 1,233.197 ns | - | - | - | 177 B | 13183 B | 11615 B |
Bitmap | 91,590.08 ns | 101,468.12 ns | 5,561.810 ns | - | - | - | 180 B | 12624 B | - |
AllocHGlobal | 79.91 ns | 43.93 ns | 2.408 ns | - | - | - | - | 80 B | - |
AllocHGlobalWithLeaks | 103.50 ns | 153.21 ns | 8.398 ns | - | - | - | - | 80 B | 80 B |
Profiling memory leaks
The BenchmarkDotNet repeats benchmarking function many times. Sometimes it can cause a memory overflow. In this case, the BenchmarkDotNet shows the message:
OutOfMemoryException!
BenchmarkDotNet continues to run additional iterations until desired accuracy level is achieved. It's possible only if the benchmark method doesn't have any side-effects.
If your benchmark allocates memory and keeps it alive, you are creating a memory leak.
You should redesign your benchmark and remove the side-effects. You can use `OperationsPerInvoke`, `IterationSetup` and `IterationCleanup` to do that.
In this case, you should try to reduce the number of invocation, by adding [ShortRunJob]
attribute or using Job.Short
for custom configuration.
Links
- Diagnosers
- The permanent link to this sample: BenchmarkDotNet.Samples.IntroNativeMemory