Skip to content

Optimize EventSource.GetGuid (NetEventSource) #120352

Description

@EgorBo

When I was looking at #120288 rerpo which is just a simple console app with some Socket server & listener, I noticed a huge chunk of time is spent in NetEventSource::.cctor (Linux-x64 and I run the app without dotnet-trace or any env.vars). In that cctor most of the time is spent inside EventSource.GetGuid that is obtaining custom attributes (presumably over this type)

Image

Here is the GetGuid impl:

public static Guid GetGuid(Type eventSourceType)
{
ArgumentNullException.ThrowIfNull(eventSourceType);
EventSourceAttribute? attrib = (EventSourceAttribute?)GetCustomAttributeHelper(eventSourceType, typeof(EventSourceAttribute));
string name = eventSourceType.Name;
if (attrib != null)
{
if (attrib.Guid != null)
{
if (Guid.TryParse(attrib.Guid, out Guid g))
return g;
}

Is this something we can avoid "GetCustomAttributes" for and replace with some static map for BCL types?

PS: this is not the root cause of #120288

Activity

  1. added
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Oct 3, 2025
  2. jkotas commented on Oct 3, 2025

    @jkotas
    Member

    Duplicate of #28290

  3. added and removed
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Oct 3, 2025
  4. dotnet-policy-service commented on Oct 3, 2025

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @tarekgh, @tommcdon, @steveisok, @pjanotti
    See info in area-owners.md if you want to be subscribed.

  5. EgorBo commented on Oct 3, 2025

    @EgorBo
    MemberAuthor

    Ah, indeed!
    cc @MihaZupan in case if you're interested in duplicates where your bot didn't mark it as such

  6. MihaZupan commented on Oct 3, 2025

    @MihaZupan
    Member

    Looks like it flagged these as candidates, but all fell below the confidence heuristics.

    Could be something to improve in the prompts

  7. EgorBo commented on Oct 3, 2025

    @EgorBo
    MemberAuthor

    @jkotas this call seems to trigger ICU, I wonder if we can avoid it for ASCII-only type names

    Image

    Presumably, ICU will most likely be loaded during the startup anyway for most apps, but seems like in this particular case it has no need

  8. jkotas commented on Oct 3, 2025

    @jkotas
    Member

    I wonder if we can avoid it for ASCII-only type names

    It would be safe to avoid it only if IsAsciiCasingSameAsInvariant returns true. IsAsciiCasingSameAsInvariant requires ICU...

    ICU will most likely be loaded during the startup anyway for most apps

    Right.

    I think it is more interesting to figure out what needs to happen to generate this guid at build time.

  9. EgorBo commented on Oct 3, 2025

    @EgorBo
    MemberAuthor

    Another bottleneck (unrelated): https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Net/Sockets/SocketErrorPal.Unix.cs#L34-L130

    Such static Dictionary initializations lead to a slow cctor with tons of Add() calls.
    It's not the first time I see this problem, perhaps, we can do something for it e.g. on the Roslyn side?

  10. EgorBo commented on Oct 3, 2025

    @EgorBo
    MemberAuthor

    I wonder if we can avoid it for ASCII-only type names

    It would be safe to avoid it only if IsAsciiCasingSameAsInvariant returns true. IsAsciiCasingSameAsInvariant requires ICU...

    IsAsciiCasingSameAsInvariant is unconditionally "true" for invariant operations like this

    Image

    but CultureData.Invariant triggers ICU, right

  11. EgorBo commented on Oct 3, 2025

    @EgorBo
    MemberAuthor

    I'm not sure it's worth creating an issue but seems like we might want to expect the following code snippet to produce less JIT compilations?

    using System.Net;
    using System.Net.Sockets;
    
    using Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    listener.Bind(new IPEndPoint(IPAddress.Loopback, 7878));
    listener.Listen(1);
    listener.Close();

    Linux-x64 DOTNET_JitDisasmSummary=1:

       1: JIT compiled Program:<Main>$(System.String[]) [Tier0, IL size=56, code size=246]
       2: JIT compiled System.RuntimeType+IGenericCacheEntry`1[System.__Canon]:CreateAndCache(System.RuntimeType) [Instrumented Tier0, IL size=165, code size=1066]
       3: JIT compiled (dynamicClass):InvokeStub_EventSourceAttribute.set_Name(System.Object,System.Object,ptr) [FullOpts, IL size=25, code size=25]
       4: JIT compiled System.Collections.Concurrent.ConcurrentQueue`1[System.Net.Sockets.SocketAsyncEngine+SocketIOEvent]:.ctor() [Tier0, IL size=44, code size=169]
       5: JIT compiled System.Collections.Concurrent.ConcurrentQueueSegment`1[System.Net.Sockets.SocketAsyncEngine+SocketIOEvent]:.ctor(int) [Instrumented Tier0, IL size=65, code size=215]
       6: JIT compiled System.Net.IPAddress:.ctor(System.ReadOnlySpan`1[byte]) [Tier0, IL size=69, code size=220]
       7: JIT compiled System.Net.IPAddress:.ctor(System.ReadOnlySpan`1[byte],long) [Tier0, IL size=67, code size=215]
       8: JIT compiled System.Net.IPAddress:ReadUInt16NumbersFromBytes(System.ReadOnlySpan`1[byte]) [Instrumented Tier0, IL size=96, code size=161]
       9: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:.ctor(int) [Tier0, IL size=9, code size=39]
      10: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:.ctor(int,System.Collections.Generic.IEqualityComparer`1[int]) [Tier0, IL size=136, code size=116]
      11: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:Initialize(int) [Tier0, IL size=56, code size=156]
      12: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:Add(int,int) [Tier0, IL size=11, code size=48]
      13: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:TryInsert(int,int,byte) [Instrumented Tier0, IL size=565, code size=1502]
      14: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:GetBucket(uint) [Tier0, IL size=29, code size=90]
      15: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:.ctor(int) [Tier0, IL size=9, code size=39]
      16: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:.ctor(int,System.Collections.Generic.IEqualityComparer`1[int]) [Tier0, IL size=136, code size=116]
      17: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:Initialize(int) [Tier0, IL size=56, code size=156]
      18: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:Add(int,int) [Tier0, IL size=11, code size=48]
      19: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:TryInsert(int,int,byte) [Instrumented Tier0, IL size=565, code size=1502]
      20: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:GetBucket(uint) [Tier0, IL size=29, code size=90]
      21: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:TryGetValue(int,byref) [Tier0, IL size=39, code size=90]
      22: JIT compiled System.Collections.Generic.Dictionary`2[int,int]:FindValue(int) [Instrumented Tier0, IL size=297, code size=1079]
      23: JIT compiled System.Collections.Generic.EqualityComparer`1[int]:get_Default() [Tier0, IL size=6, code size=35]
      24: JIT compiled System.Collections.Generic.EqualityComparer`1[int]:.cctor() [Tier0, IL size=26, code size=81]
      25: JIT compiled System.Collections.Generic.EnumEqualityComparer`1[int]:.ctor() [Tier0, IL size=7, code size=31]
      26: JIT compiled System.Collections.Generic.EqualityComparer`1[int]:.ctor() [Tier0, IL size=7, code size=31]
      27: JIT compiled System.Collections.Generic.EnumEqualityComparer`1[int]:GetHashCode(int) [Tier0, IL size=14, code size=34]
      28: JIT compiled System.Collections.Generic.EnumEqualityComparer`1[int]:Equals(int,int) [Tier0, IL size=8, code size=39]
      29: JIT compiled System.Runtime.CompilerServices.RuntimeHelpers:EnumEquals[int](int,int) [Tier0, IL size=5, code size=34]
      30: JIT compiled System.Threading.Thread:GetThreadStaticsBase() [Tier0, IL size=18, code size=24]
    

    I guess these [int,int] in fact are cross-module (SPC-System.Net.Sockets) enums.
    It's not clear why IPAddress is not prejitted but it's not VectorT related

  12. jkotas commented on Oct 3, 2025

    @jkotas
    Member

    Such static Dictionary initializations lead to a slow cctor with tons of Add() calls.
    It's not the first time I see this problem, perhaps, we can do something for it e.g. on the Roslyn side?

    I am not sure what can be done about this on Roslyn side. It is part of the Linq family of convenience features that are known to have less than ideal performance characteristics.

    This specific case should better be a switch statement: #120364

    It's not clear why IPAddress is not prejitted but it's not VectorT related

    My guess is that the SIMD intrinsics use in ReadUInt16NumbersFromBytes derails use of R2R code. We seem to be more defensive than we need to be. I think it may be worth opening issue about this since this does not appear to be one-off problem.

  13. jkotas commented on Oct 3, 2025

    @jkotas
    Member

    I'm not sure it's worth creating an issue

    We may want to create a user issue to track all startup improvement issues.

    System.RuntimeType+IGenericCacheEntry`

    This looks like a bug in prejiting interface static methods.

    (dynamicClass):InvokeStub_EventSourceAttribute

    This know issue that was expected to be fixed by #115345

  14. jkotas commented on Oct 3, 2025

    @jkotas
    Member

    IsAsciiCasingSameAsInvariant is unconditionally "true" for invariant operations like this

    Ok, I have missed that. If it the case, we may be able to short-circuit ICU loading by duplicating TextInfo.ToUpperInvariant into string.ToUpperInvariant. I am not sure whether it is worth the trouble...

  15. EgorBo commented on Oct 3, 2025

    @EgorBo
    MemberAuthor

    My guess is that the SIMD intrinsics use in ReadUInt16NumbersFromBytes derails use of R2R code. We seem to be more defensive than we need to be. I think it may be worth opening issue about this since this does not appear to be one-off problem.

    Filed #120367 with a minimal repro

  16. am11 commented on Oct 3, 2025

    @am11
    Member

    I think it is more interesting to figure out what needs to happen to generate this guid at build time.

    Something like [GeneratedEventSource] which would generate GUID object at build-time, like [GeneratedRegex] and Environment.Version? 🙂

  17. jkotas commented on Oct 4, 2025

    @jkotas
    Member

    Something like [GeneratedEventSource] which would generate GUID object at build-time, like [GeneratedRegex] and Environment.Version?

    #28290 has discussion about it. I think would need to a combination of a new public API and a source generator.

  18. locked and limited conversation to collaborators on Nov 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions