Skip to content

Instantly share code, notes, and snippets.

@EgorBo
Last active June 17, 2022 08:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save EgorBo/b40299ed613540e8441ce4be7c031d27 to your computer and use it in GitHub Desktop.
Save EgorBo/b40299ed613540e8441ce4be7c031d27 to your computer and use it in GitHub Desktop.
GDV_for_delegates.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
//
// Results:
//
// | Method | array | predicate | Mean | Error | StdDev | Ratio |
// |--------------- |------------- |--------------------- |----------:|----------:|----------:|------:|
// | Count | Int32[10000] | Syste(...)lean] [42] | 22.472 us | 0.1792 us | 0.1677 us | 1.00 |
// | Count_Profiled | Int32[10000] | Syste(...)lean] [42] | 6.431 us | 0.0430 us | 0.0382 us | 0.29 |
//
[Config(typeof(ConfigWithCustomEnvVars))]
class Program
{
static void Main(string[] args) =>
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);
// can be a lambda, but I'm going to use "Method Group" syntax for simplicity
static bool MyFilter(int item) => item % 2 == 0;
public IEnumerable<object[]> TestData()
{
yield return new object[]
{
/*test array*/ Enumerable.Range(0, 10000).ToArray(),
/*test predicate*/ (Func<int, bool>)MyFilter
};
}
// Case 1: No GDV, just an ordinary code to implement Count with a predicate
[Benchmark(Baseline = true)]
[ArgumentsSource(nameof(TestData))]
public int Count(int[] array, Func<int, bool> predicate)
{
int count = 0;
foreach (int item in array)
if (predicate(item))
count++;
return count;
}
// Case 2: Guarded devirtualization of 'predicate' (assumes it's MyFilter)
[Benchmark]
[ArgumentsSource(nameof(TestData))]
public int Count_Profiled(int[] array, Func<int, bool> predicate)
{
int count = 0;
foreach (int item in array)
{
// is predicated actually 'MyFilter'?
if (Unsafe.As<MyMultiDelegate>(predicate)._methodPtrAux == MyFilterFPtr)
{
// direct invoke
if (MyFilter(item)) // will be inlined.
{
// NOTE: in this sample we don't benefit from inlining as no additional optimizations/foldings
// will be kicked in. So the benchmark could have even more impressive numbers.
count++;
}
}
else
{
// cold fallback. item is not MyFilter method
if (predicate(item))
{
count++;
}
}
}
return count;
}
// JIT can obtain the actual constant, but I am going to use a readonly field here
// this field will be converted into a constant by JIT in tier1
static readonly unsafe nuint MyFilterFPtr = (nuint)(delegate*<int, bool>)&MyFilter;
// this is needed because those fields are private in corelib
// it mimics MultiDelegate's layout
class MyMultiDelegate
{
public nuint _target;
public nuint _methodBase;
public nuint _methodPtr;
public nuint _methodPtrAux;
public nuint _invocationList;
public nuint _invocationCount;
}
class ConfigWithCustomEnvVars : ManualConfig
{
public ConfigWithCustomEnvVars()
{
// FullPGO mode
AddJob(Job.Default
.WithEnvironmentVariables(
new EnvironmentVariable("DOTNET_TieredPGO", "1"),
new EnvironmentVariable("DOTNET_TC_QuickJitForLoops", "1"),
new EnvironmentVariable("DOTNET_ReadyToRun", "0")));
}
}
}
@manofstick
Copy link

@EgorBo

Hi! Is there any chance that this (in some form) will be in the proper PGO release? (i.e. I'm playing around with the .net6 PGO and it does a good job with virtual calls, but delegates are obviously not included there)

Will be really helpful for me if it is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment