-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Cache the value for IsCollectible #119739
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR improves performance by caching the IsCollectible property value for RuntimeType instances. The change addresses a performance issue where the IsCollectible property was being called frequently on a hot path but wasn't fast enough due to its underlying P/Invoke call.
Key changes:
- Adds lazy caching of the
IsCollectiblevalue in theRuntimeTypeCacheclass - Replaces the direct P/Invoke implementation with a cached property accessor
| if (!m_isCollectible.HasValue) | ||
| { | ||
| RuntimeType type = m_runtimeType; | ||
| m_isCollectible = RuntimeTypeHandle.IsCollectible(new QCallTypeHandle(ref type)) != Interop.BOOL.FALSE; | ||
| } |
Copilot
AI
Sep 16, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The lazy initialization of m_isCollectible is not thread-safe. Multiple threads could simultaneously check HasValue, find it false, and then race to set the value. Consider using Volatile.Read/Volatile.Write or a lock to ensure thread safety, or accept that the P/Invoke might be called multiple times initially if that's acceptable for this use case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is valid feedback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The usual way to deal with this problem is a tri-state enum like this:
runtime/src/libraries/System.Private.CoreLib/src/System/Globalization/TextInfo.cs
Lines 22 to 27 in 8e80e90
| private enum Tristate : byte | |
| { | |
| NotInitialized = 0, | |
| False = 1, | |
| True = 2 | |
| } |
|
Tagging subscribers to this area: @dotnet/area-system-reflection |
This comment was marked as resolved.
This comment was marked as resolved.
I would rather fix this by making the FCall faster. |
|
|
@EgorBot -intel using BenchmarkDotNet.Attributes;
public class Bench
{
[Benchmark]
public bool IsCollectible() => typeof(object).IsCollectible;
} |
|
#119743 makes the implementation even faster than the reflection cache while avoiding the extra memory footprint of the reflection cache. |
|
Ok. I will close this for now. I'll benchmark your PR in the impacted TypeConverter benchmark this was fixing. |
Fixes #119499
@jkotas let me know what you think. It turns out
IsCollectiblewasn't fast enough to put on this hot path. I'm assuming it's safe to cache it for a type and I found other fields were already cached. I made it cached lazily so that we don't impact every user of reflection that hits this cache.CC @alexey-zakharov