Skip to content

Instantly share code, notes, and snippets.

@GeeLaw
Created October 4, 2020 15:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GeeLaw/c141c3da69a27337ab5ddb25b85e597e to your computer and use it in GitHub Desktop.
Save GeeLaw/c141c3da69a27337ab5ddb25b85e597e to your computer and use it in GitHub Desktop.
CRTP benchmark (C#)
/**** Benchmarking CRTP without virtual dispatch in C#.
See https://geelaw.blog/entries/csharp-crtp-static-polymorphism-friendship/
Results on my Surface Book 2:
| milliseconds | struct, non-virtual | struct, virtual | virtual |
| :----------: | :-----------------: | :-------------: | :-----: |
| x86 Core 3.1 | 1065 | 1405 | 1431 |
| x64 Core 3.1 | 1015 | 1371 | 1418 |
| x86 Fx 4.7.2 | 1160 | 1501 | 1490 |
| x64 Fx 4.7.2 | 1051 | 1364 | 1418 |
The MIT License (MIT)
Copyright (c) 2020 by Gee Law
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the “Software”),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****/
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace CrtpBenchmark
{
public abstract class For<TFor, TForOverrides>
where TFor : For<TFor, TForOverrides>
where TForOverrides : struct, For<TFor, TForOverrides>.IOverrides
{
public interface IOverrides
{
TFor That { get; }
void SetThat(TFor that);
void Initialize();
bool Condition();
void Iteration();
/* true = continue, false = break */
bool LoopBody();
}
protected TForOverrides MyOverrides;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected For() { MyOverrides.SetThat((TFor)this); }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute()
{
for (MyOverrides.Initialize();
MyOverrides.Condition() && MyOverrides.LoopBody();
MyOverrides.Iteration())
;
}
}
public abstract class For<TFor> where TFor : For<TFor>
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected For() { }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Execute()
{
for (Initialize(); Condition() && LoopBody(); Iteration())
;
}
protected abstract void Initialize();
protected abstract bool Condition();
protected abstract void Iteration();
protected abstract bool LoopBody();
}
/// <summary>
/// Implements Newton's method for computing square root in a for-loop,
/// using CRTP with struct and non-virtual calls.
/// </summary>
public class Newton1 : For<Newton1, Newton1.Overrides>
{
public struct Overrides : IOverrides
{
public Newton1 That
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private set;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetThat(Newton1 that) { that.MyOverrides.That = that; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Initialize() { That.Initialize(); }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Condition() { return That.Condition(); }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Iteration() { That.Iteration(); }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool LoopBody() { return That.LoopBody(); }
}
private double sq, sqrt, delta;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Newton1(double x) { sq = x; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Initialize() { sqrt = 1; delta = 1; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool Condition() { return delta > 1e-9 || delta < -1e-9; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Iteration() { }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool LoopBody()
{
double newsqrt = (sq / sqrt + sqrt) / 2;
delta = sqrt - newsqrt;
sqrt = newsqrt;
return true;
}
}
/// <summary>
/// Implements Newton's method for computing square root in a for-loop,
/// using CRTP with struct and virtual calls.
/// </summary>
public class Newton2 : For<Newton2, Newton2.Overrides>
{
public struct Overrides : IOverrides
{
public Newton2 That
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private set;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void SetThat(Newton2 that) { that.MyOverrides.That = that; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Initialize() { That.Initialize(); }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Condition() { return That.Condition(); }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Iteration() { That.Iteration(); }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool LoopBody() { return That.LoopBody(); }
}
private double sq, sqrt, delta;
public Newton2(double x) { sq = x; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual void Initialize() { sqrt = 1; delta = 1; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual bool Condition() { return delta > 1e-9 || delta < -1e-9; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual void Iteration() { }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual bool LoopBody()
{
double newsqrt = (sq / sqrt + sqrt) / 2;
delta = sqrt - newsqrt;
sqrt = newsqrt;
return true;
}
}
/// <summary>
/// Implements Newton's method for computing square root in a for-loop,
/// using CRTP without struct and with virtual calls.
/// </summary>
public class Newton3 : For<Newton3>
{
private double sq, sqrt, delta;
public Newton3(double x) { sq = x; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void Initialize() { sqrt = 1; delta = 1; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override bool Condition() { return delta > 1e-9 || delta < -1e-9; }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override void Iteration() { }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected override bool LoopBody()
{
double newsqrt = (sq / sqrt + sqrt) / 2;
delta = sqrt - newsqrt;
sqrt = newsqrt;
return true;
}
}
class Program
{
const int Preheat = 1000;
const int Repeat = 1000000;
const double Square = 9237598425.139847934;
static long N1;
static long N2;
static long N3;
/* The benchmark methods are explicitly disallowed
/* for inlining for consistency. One might argue that
/* this prevents the JIT compiler from exploiting the
/* concrete (runtime) type information of the arguments
/* passed into it. In fact, changing it into aggressive
/* inlining does not alter the relative performance.
/* Moreover, it's more realistic that after a few levels
/* of parameter passing, the compiler has lost track of
/* concrete types. */
[MethodImpl(MethodImplOptions.NoInlining)]
static void Test(Stopwatch sw, Newton1 n)
{
for (int i = 0; i != Preheat; ++i)
n.Execute();
sw.Reset();
sw.Start();
for (int i = 0; i != Repeat; ++i)
n.Execute();
sw.Stop();
N1 += sw.ElapsedMilliseconds;
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void Test(Stopwatch sw, Newton2 n)
{
for (int i = 0; i != Preheat; ++i)
n.Execute();
sw.Reset();
sw.Start();
for (int i = 0; i != Repeat; ++i)
n.Execute();
sw.Stop();
N2 += sw.ElapsedMilliseconds;
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void Test(Stopwatch sw, Newton3 n)
{
for (int i = 0; i != Preheat; ++i)
n.Execute();
sw.Reset();
sw.Start();
for (int i = 0; i != Repeat; ++i)
n.Execute();
sw.Stop();
N3 += sw.ElapsedMilliseconds;
}
static void WriteReport()
{
Console.WriteLine(
"struct + !virt " + N1.ToString()
+ "ms, struct + virt " + N2.ToString()
+ "ms, virt " + N3.ToString() + "ms");
}
static void Main()
{
Stopwatch sw = new Stopwatch();
Newton1 n1 = new Newton1(Square);
Newton2 n2 = new Newton2(Square);
Newton3 n3 = new Newton3(Square);
/* A "large preheat" is needed, otherwise
/* the performance of 2/3 gets greatly degraded
/* if 2/3 is the first to be tested between them. */
Test(sw, n1);
Test(sw, n2);
Test(sw, n3);
N1 = N2 = N3 = 0;
/* 123 */
Test(sw, n1);
Test(sw, n2);
Test(sw, n3);
/* 132 */
Test(sw, n1);
Test(sw, n3);
Test(sw, n2);
/* 213 */
Test(sw, n2);
Test(sw, n1);
Test(sw, n3);
/* 231 */
Test(sw, n2);
Test(sw, n3);
Test(sw, n1);
/* 312 */
Test(sw, n3);
Test(sw, n1);
Test(sw, n2);
/* 321 */
Test(sw, n3);
Test(sw, n2);
Test(sw, n1);
WriteReport();
Console.ReadLine();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment