Last active
June 17, 2022 08:02
-
-
Save EgorBo/b40299ed613540e8441ce4be7c031d27 to your computer and use it in GitHub Desktop.
GDV_for_delegates.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"))); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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.