Skip to content

Speed up RuntimeType.GetHashCode - #134783

Merged
EgorBo merged 8 commits into
dotnet:mainfrom
EgorBo:runtime-type-hashcode
Sep 29, 2026
Merged

EgorBo merged 8 commits into
dotnet:mainfrom
EgorBo:runtime-type-hashcode

Conversation

@EgorBo

@EgorBo EgorBo commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Use mixed type-handle hashes in CoreCLR, preserving COM identity hashing (Windows only).

using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);

public class Bench {
    private readonly Dictionary<Type, int> _dictionary = new() { [typeof(string)] = 42 };

    [Benchmark]
    public int Lookup() => _dictionary[typeof(string)];
}

Results

Win-x64:

Method Toolchain Mean Error Ratio
Lookup main 5.961 ns 0.0108 ns 1.38
Lookup PR #134783 4.323 ns 0.0078 ns 1.00

osx-arm64:

Method Toolchain Mean Error Ratio
Lookup main 3.647 ns 0.0717 ns 1.27
Lookup PR #134783 2.881 ns 0.0009 ns 1.00

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3ed54863-8a13-437b-aa0a-a6f356b253ed
@EgorBo

This comment was marked as outdated.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-runtime
See info in area-owners.md if you want to be subscribed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 3ed54863-8a13-437b-aa0a-a6f356b253ed
@EgorBo

EgorBo commented Sep 28, 2026

Copy link
Copy Markdown
Member Author

@EgorBot -windows_intel -linux_amd -osx_arm -linux_arm

using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);

public class Bench {
    private readonly Dictionary<Type, int> _dictionary = new() { [typeof(string)] = 42 };

    [Benchmark]
    public int Lookup() => _dictionary[typeof(string)];
}

Note

Benchmark generated with GitHub Copilot.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The performance claim lacks measurements, and the new hash paths lack targeted tests.

Review effort: Balanced
Findings: 1 Medium severity · 1 Low severity

Open (2)
What changed in this PR

Optimizes CoreCLR RuntimeType.GetHashCode while retaining existing behavior for Mono and COM identity types.

Changes:

  • Uses CoreCLR type handles for hashing.
  • Preserves identity hashing for CLSID-backed COM types.
  • Moves Mono’s existing identity hash implementation to its runtime-specific file.
File Description
System/​RuntimeType.Mono.cs Retains Mono identity hashing.
System/​RuntimeType.cs Removes the shared implementation.
System/​RuntimeType.CoreCLR.cs Adds handle-based hashing with a COM exception.

Comment thread src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs Outdated
Comment thread src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs Outdated
@EgorBo

This comment was marked as outdated.

For unconstrained T, 'obj?.GetHashCode()' makes Roslyn emit ldarga/ldloca with a byref merge, which makes the JIT address-expose both locals after inlining and blocks devirtualization and constant folding (e.g. Dictionary<Type, T> lookups with typeof(X) keys).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 033db876-d232-451a-94ab-ed732694508a
@EgorBo

EgorBo commented Sep 28, 2026

Copy link
Copy Markdown
Member Author

@EgorBot -windows_intel -linux_amd -osx_arm -linux_arm -profiler

using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Bench).Assembly).Run(args);

public class Bench {
    private readonly Dictionary<Type, int> _dictionary = new() { [typeof(string)] = 42 };

    [Benchmark]
    public int Lookup() => _dictionary[typeof(string)];
}

Note

Benchmark generated with GitHub Copilot.

@EgorBo

EgorBo commented Sep 29, 2026

Copy link
Copy Markdown
Member Author

PTAL @jkotas, it seems to speed up _dictionary[typeof(string)] by 20-25% EgorBot/Benchmarks#623

Mainly because JIT constant folds the entire thing into 32bit constant (e.g. mov r14d, 0xd1e70ebc). Had to do some massage for obj?.GetHashCode() ?? 0 (we have a known issue for the root cause behind it against JIT).

@EgorBo

EgorBo commented Sep 29, 2026

Copy link
Copy Markdown
Member Author
{D5D9245B-9F15-4EA1-8946-43785E344DC4}

This kind of dictionaries seem popular

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 033db876-d232-451a-94ab-ed732694508a
Comment thread src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 033db876-d232-451a-94ab-ed732694508a
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 033db876-d232-451a-94ab-ed732694508a
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 033db876-d232-451a-94ab-ed732694508a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants