-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Use resolve helper for calling interface methods #123252
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
base: main
Are you sure you want to change the base?
Conversation
|
Test program: Details// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
class Program
{
static void Main()
{
for (int it = 0; it < 6; it++)
{
IFace c0 = new C0();
var sw = Stopwatch.StartNew();
for (int i = 0; i < 10_000_000; i++)
{
DoCallFixed(c0, 1, 2);
}
Console.WriteLine(sw.ElapsedMilliseconds);
IFace[] faces = [
new C0(), new C1(), new C2(), new C3(),
new C4(), new C5(), new C6(), new C7(),
new C8(), new C9(), new CA(), new CB(),
new CC(), new CD(), new CE(), new CF(),
];
sw = Stopwatch.StartNew();
for (int i = 0; i < 10_000_000; i++)
{
DoCall2(faces, 1, 2);
}
Console.WriteLine(sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew();
for (int i = 0; i < 10_000_000; i++)
{
DoCall4(faces, 1, 2);
}
Console.WriteLine(sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew();
for (int i = 0; i < 10_000_000; i++)
{
DoCall8(faces, 1, 2);
}
Console.WriteLine(sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew();
for (int i = 0; i < 10_000_000; i++)
{
DoCall16(faces, 1, 2);
}
Console.WriteLine(sw.ElapsedMilliseconds);
for (int i = 0; i < faces.Length; i++)
{
DoCallFixed(faces[i], 1, 2);
}
sw = Stopwatch.StartNew();
IFace cf = new CF();
for (int i = 0; i < 10_000_000; i++)
{
DoCallFixed(cf, 1, 2);
}
Console.WriteLine(sw.ElapsedMilliseconds);
Console.WriteLine("---------------------------");
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void DoCallFixed(IFace i, int x, int y)
{
i.Call(x, y);
i.Call(x, y);
i.Call(x, y);
i.Call(x, y);
i.Call(x, y);
i.Call(x, y);
i.Call(x, y);
i.Call(x, y);
}
[MethodImpl(MethodImplOptions.NoInlining)]
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void DoCall2(IFace[] i, int x, int y)
{
i[0].Call(x, y);
i[1].Call(x, y);
i[0].Call(x, y);
i[1].Call(x, y);
i[0].Call(x, y);
i[1].Call(x, y);
i[0].Call(x, y);
i[1].Call(x, y);
}
[MethodImpl(MethodImplOptions.NoInlining)]
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void DoCall4(IFace[] i, int x, int y)
{
i[0].Call(x, y);
i[1].Call(x, y);
i[2].Call(x, y);
i[3].Call(x, y);
i[0].Call(x, y);
i[1].Call(x, y);
i[2].Call(x, y);
i[3].Call(x, y);
}
[MethodImpl(MethodImplOptions.NoInlining)]
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void DoCall8(IFace[] i, int x, int y)
{
i[0].Call(x, y);
i[1].Call(x, y);
i[2].Call(x, y);
i[3].Call(x, y);
i[4].Call(x, y);
i[5].Call(x, y);
i[6].Call(x, y);
i[7].Call(x, y);
}
[MethodImpl(MethodImplOptions.NoInlining)]
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
static void DoCall16(IFace[] i, int x, int y)
{
i[0].Call(x, y);
i[1].Call(x, y);
i[2].Call(x, y);
i[3].Call(x, y);
i[4].Call(x, y);
i[5].Call(x, y);
i[6].Call(x, y);
i[7].Call(x, y);
i[8].Call(x, y);
i[9].Call(x, y);
i[10].Call(x, y);
i[11].Call(x, y);
i[12].Call(x, y);
i[13].Call(x, y);
i[14].Call(x, y);
i[15].Call(x, y);
}
}
interface IFace
{
int Call(int x, int y);
}
class C0 : IFace { public int Call(int x, int y) => x + y; }
class C1 : IFace { public int Call(int x, int y) => x + y; }
class C2 : IFace { public int Call(int x, int y) => x + y; }
class C3 : IFace { public int Call(int x, int y) => x + y; }
class C4 : IFace { public int Call(int x, int y) => x + y; }
class C5 : IFace { public int Call(int x, int y) => x + y; }
class C6 : IFace { public int Call(int x, int y) => x + y; }
class C7 : IFace { public int Call(int x, int y) => x + y; }
class C8 : IFace { public int Call(int x, int y) => x + y; }
class C9 : IFace { public int Call(int x, int y) => x + y; }
class CA : IFace { public int Call(int x, int y) => x + y; }
class CB : IFace { public int Call(int x, int y) => x + y; }
class CC : IFace { public int Call(int x, int y) => x + y; }
class CD : IFace { public int Call(int x, int y) => x + y; }
class CE : IFace { public int Call(int x, int y) => x + y; }
class CF : IFace { public int Call(int x, int y) => x + y; }Main: DetailsPR: DetailsAs expected, right now this is a regression when the level of polymorphism is small. We start to get wins when the old cache size grows beyond a dozen entries or so. |
It's probably not that we consider it clobbered, but LSRA has rather decided that |
Still very much WIP, don't look at implementation. This works just enough that I can do perf measurements.
This is changing interface dispatch from this shape:
To this shape:
This is the CFG dispatch shape that we recently added.
What this is also changing is the contents of the dispatch cell. The dispatch cell is now two pointers: a cached this pointer and a cached target method address. The dispatch cell is currently also prefixed by a pointer to the interface MethodTable and slot number (that are necessary to compute the dispatch if not cached). But this information can potentially be stored out-of-line, making the cache eligible to be stored as
.bssand the composition as readonly data. This part is not done yet.On a first dispatch, we call the slow resolution helper that will decompose the dispatch cell to MethodTable+slot, compute the result of lookup and store it in the dispatch cell itself. This is the fastest, monomorphic case.
If we later see dispatches with different kind of
this, we cache them in a global hashtable. The key of the global hashtable is thethisMethodTable address and the dispatch cell address. We use this as the key instead of interface MethodTable+slot+this MethodTable because it's faster to hash/compare and touches less memory.Because the contents/shape of the dispatch cell is now fixed, we can inline the monomorphic case in the invoke sequence. This is also not done yet.
Cc @dotnet/ilc-contrib