Skip to content
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

mono AOT compiler cannot resolve and inline static virtual calls in gshared methods #75801

Closed
vargaz opened this issue Sep 17, 2022 · 9 comments · Fixed by #76033
Closed

mono AOT compiler cannot resolve and inline static virtual calls in gshared methods #75801

vargaz opened this issue Sep 17, 2022 · 9 comments · Fixed by #76033
Assignees
Labels
Milestone

Comments

@vargaz
Copy link
Contributor

vargaz commented Sep 17, 2022

Testcase:

using System;
using System.Diagnostics;
using System.Numerics;

public class Tests
{
        private interface INegator<T> where T : struct
        {
            static abstract bool NegateIfNeeded(bool equals);
        }

        private readonly struct DontNegate<T> : INegator<T> where T : struct
        {
            public static bool NegateIfNeeded(bool equals) => equals;
        }

        private readonly struct Negate<T> : INegator<T> where T : struct
        {
            public static bool NegateIfNeeded(bool equals) => !equals;
        }

    private static int IndexOfValueType<TValue, TNegator>()
            where TValue : struct, INumber<TValue>
            where TNegator : struct, INegator<TValue>
    {
        for (int j = 0; j < 1000; ++j) {
            for (int i = 0; i < 100000; ++i)
                TNegator.NegateIfNeeded (true);
        }
        return 0;
    }

    public static void Main () {
        var w = Stopwatch.StartNew ();
        IndexOfValueType<byte, DontNegate<byte>> ();
        w.Stop ();
        Console.WriteLine (w.ElapsedMilliseconds);
    }
}

When calling IndexOfValueType<byte, DontNegate<byte>>, the AOT compiler will compile a shared instance for bytes/enums with byte basetype, but inside the shared method it can't resolve the

constrained. !!TNegator
 call       bool class Tests/INegator`1<!!TValue>::NegateIfNeeded(bool)

call to DontNegate so it doesn't get inlined.

This is hit with the recent BCL changes to SpanHelpers.

@vargaz vargaz added the tenet-performance Performance related issue label Sep 17, 2022
@dotnet-issue-labeler
Copy link

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Sep 17, 2022
@vargaz vargaz added area-Codegen-AOT-mono and removed untriaged New issue has not been triaged by the area owner labels Sep 17, 2022
@vargaz vargaz self-assigned this Sep 18, 2022
@SamMonoRT
Copy link
Member

cc @BrzVlad

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Sep 19, 2022
@vargaz vargaz added this to the 8.0.0 milestone Sep 19, 2022
@adamsitnik
Copy link
Member

@vargaz As you know I am working on the 7.0 fix and I wonder whether I could avoid copying the old code back. I have two questions:

  1. Would constraining T help?
- private interface INegator<T> where T : struct
+ private interface INegator<T> where T : struct, INumber<T>
  1. Would using typeof instead help?
- equals = TNegator.NegateIfNeeded(Vector256.Equals(values, Vector256.LoadUnsafe(ref currentSearchSpace)));
+ equals = Vector256.Equals(values, Vector256.LoadUnsafe(ref currentSearchSpace));
+ if (typeof(TNegator) == typeof(Negate)) // assuming I would remove T from INegator
+ {
+     equals = ~equals;
+ }

@vargaz
Copy link
Contributor Author

vargaz commented Sep 19, 2022

I think the lowest risk for net 7.0 is to copy the code back and put it inside ifdefs.

@adamsitnik
Copy link
Member

I think the lowest risk for net 7.0 is to copy the code back and put it inside ifdefs.

@vargaz I assume that it means that the two workarounds posted above would not help? I have started copying the code and it's a lot of code (hundreds of lines) so it's not minimal risk.

@jkotas
Copy link
Member

jkotas commented Sep 19, 2022

You can add copy of the old implementation files verbatim under a different name, and just do minimal changes on top of it to get things compile.

vargaz added a commit to vargaz/runtime that referenced this issue Sep 21, 2022
Mostly fixes dotnet#75801.

Some restrictions still remain, mostly methods with generic arguments like
INegator`1<T>:NegateIfNeeded<Vector128<T>>.
vargaz added a commit to vargaz/runtime that referenced this issue Sep 26, 2022
…containing static virtual calls.

These calls cannot be resolved at compile time in gshared methods, so they cannot be inlined etc.
They are used in perf sensitive BCL code like SpanHelpers. To fix this, modify the AOT compiler
so in addition to the gshared versions, it emits specific instances of these methods if possible.
This only affects a small subset of gshared methods so it doesn't lead to a noticable code size increase.

Fixes dotnet#75801.
vargaz added a commit that referenced this issue Sep 27, 2022
…containing static virtual calls. (#76033)

These calls cannot be resolved at compile time in gshared methods, so they cannot be inlined etc.
They are used in perf sensitive BCL code like SpanHelpers. To fix this, modify the AOT compiler
so in addition to the gshared versions, it emits specific instances of these methods if possible.
This only affects a small subset of gshared methods so it doesn't lead to a noticable code size increase.

Fixes #75801.
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Sep 27, 2022
@adamsitnik
Copy link
Member

@vargaz Can I revert #75917 now?

@vargaz
Copy link
Contributor Author

vargaz commented Sep 27, 2022

This only solves (part of) the AOT slowdowns, the interpreter slowdowns are still there.

@vargaz
Copy link
Contributor Author

vargaz commented Sep 28, 2022

So for a simple test case:

        var arr = new int [100000];
        var w = Stopwatch.StartNew ();
        var span = new Span<int> (arr);
        for (int i = 0; i < 100000; ++i)
            span.IndexOf ((int)50);
        w.Stop ();
        Console.WriteLine (w.ElapsedMilliseconds);
  • Without the span revert: 80s
  • Without span revert + this PR: 8.4s
  • With span revert: 8s

So there is still about a 5% slowdown, but it's not that bad anymore.

@ghost ghost locked as resolved and limited conversation to collaborators Nov 26, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.