Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable - #126621

Merged
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix
Apr 11, 2026
Merged

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable#126621
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix

Conversation

@jkoritzinsky

@jkoritzinskyjkoritzinsky commented Apr 7, 2026

Copy link
Copy Markdown
Member

Fixes#126608

Move the marshalling info to be cached on the type itself instead of in a ConditionalWeakTable.

Move some of the validation (in particular the "has layout" validation) to be done only at "create" time so we don't have to rerun it for every time someone calls the Marshal methods with the same (valid) type.

Use the blittable fallbacks on StructureMarshaler<T> and LayoutClassMarshaler<T> instead of inlining the calls. The cost may go up slightly (delegate call to a method that calls SpanHelpers.Memmove instead of direct call to SpanHelpers.Memmove) but still much faster than the existing implementation that does the "is blittable" checks each time.

CopilotAI review requested due to automatic review settings April 7, 2026 23:32
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI 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.

Pull request overview

This PR moves the cache for Marshal.LayoutTypeMarshalerMethods from a ConditionalWeakTable<Type, …> to the per-RuntimeType generic cache infrastructure to address the reported interop performance regressions (issue #126608).

Changes:

  • Adds LayoutTypeMarshalerMethods storage to RuntimeType.CompositeCacheEntry so it can live in the existing RuntimeType generic cache.
  • Refactors LayoutTypeMarshalerMethods to implement RuntimeType.IGenericCacheEntry<T> and retrieves it via RuntimeType.GetOrCreateCacheEntry<T>().
  • Switches from delegate creation (CreateDelegate) to caching raw function pointers (MethodHandle.GetFunctionPointer) for the marshaler methods.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.csAdds a new composite-cache field to hold the marshaler-methods cache on RuntimeType.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.csReworks LayoutTypeMarshalerMethods to use RuntimeType cache entries and cached function pointers instead of a ConditionalWeakTable.

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 2.07 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain53.46 ns0.045 ns1.0024 B1.00
MarshalStructureToPtrPR41.83 ns0.073 ns0.7824 B1.00
MarshalPtrToStructureMain57.27 ns0.111 ns1.0024 B1.00
MarshalPtrToStructurePR43.72 ns0.169 ns0.7624 B1.00
MarshalDestroyStructureMain109.19 ns0.141 ns1.0096 B1.00
MarshalDestroyStructurePR87.02 ns0.241 ns0.8096 B1.00

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 1.78 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain64.20 ns0.578 ns1.0024 B1.00
MarshalStructureToPtrPR30.70 ns0.567 ns0.4824 B1.00
MarshalPtrToStructureMain62.55 ns0.117 ns1.0024 B1.00
MarshalPtrToStructurePR37.14 ns0.026 ns0.5924 B1.00
MarshalDestroyStructureMain109.97 ns0.152 ns1.0096 B1.00
MarshalDestroyStructurePR68.63 ns0.158 ns0.6296 B1.00

@jkoritzinsky
jkoritzinsky marked this pull request as ready for review April 8, 2026 18:18
CopilotAI review requested due to automatic review settings April 8, 2026 18:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs
CopilotAI review requested due to automatic review settings April 10, 2026 19:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

@jkoritzinsky
jkoritzinsky merged commit e45ecce into dotnet:mainApr 11, 2026
100 of 111 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Perf] Windows/x64: 3 Regressions on 4/2/2026 1:30:32 AM +00:00

4 participants

@jkoritzinsky@MihuBot@AaronRobinsonMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable - #126621

Merged
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix
Apr 11, 2026
Merged

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable#126621
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix

Conversation

@jkoritzinsky

@jkoritzinskyjkoritzinsky commented Apr 7, 2026

Copy link
Copy Markdown
Member

Fixes#126608

Move the marshalling info to be cached on the type itself instead of in a ConditionalWeakTable.

Move some of the validation (in particular the "has layout" validation) to be done only at "create" time so we don't have to rerun it for every time someone calls the Marshal methods with the same (valid) type.

Use the blittable fallbacks on StructureMarshaler<T> and LayoutClassMarshaler<T> instead of inlining the calls. The cost may go up slightly (delegate call to a method that calls SpanHelpers.Memmove instead of direct call to SpanHelpers.Memmove) but still much faster than the existing implementation that does the "is blittable" checks each time.

CopilotAI review requested due to automatic review settings April 7, 2026 23:32
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI 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.

Pull request overview

This PR moves the cache for Marshal.LayoutTypeMarshalerMethods from a ConditionalWeakTable<Type, …> to the per-RuntimeType generic cache infrastructure to address the reported interop performance regressions (issue #126608).

Changes:

  • Adds LayoutTypeMarshalerMethods storage to RuntimeType.CompositeCacheEntry so it can live in the existing RuntimeType generic cache.
  • Refactors LayoutTypeMarshalerMethods to implement RuntimeType.IGenericCacheEntry<T> and retrieves it via RuntimeType.GetOrCreateCacheEntry<T>().
  • Switches from delegate creation (CreateDelegate) to caching raw function pointers (MethodHandle.GetFunctionPointer) for the marshaler methods.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.csAdds a new composite-cache field to hold the marshaler-methods cache on RuntimeType.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.csReworks LayoutTypeMarshalerMethods to use RuntimeType cache entries and cached function pointers instead of a ConditionalWeakTable.

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 2.07 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain53.46 ns0.045 ns1.0024 B1.00
MarshalStructureToPtrPR41.83 ns0.073 ns0.7824 B1.00
MarshalPtrToStructureMain57.27 ns0.111 ns1.0024 B1.00
MarshalPtrToStructurePR43.72 ns0.169 ns0.7624 B1.00
MarshalDestroyStructureMain109.19 ns0.141 ns1.0096 B1.00
MarshalDestroyStructurePR87.02 ns0.241 ns0.8096 B1.00

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 1.78 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain64.20 ns0.578 ns1.0024 B1.00
MarshalStructureToPtrPR30.70 ns0.567 ns0.4824 B1.00
MarshalPtrToStructureMain62.55 ns0.117 ns1.0024 B1.00
MarshalPtrToStructurePR37.14 ns0.026 ns0.5924 B1.00
MarshalDestroyStructureMain109.97 ns0.152 ns1.0096 B1.00
MarshalDestroyStructurePR68.63 ns0.158 ns0.6296 B1.00

@jkoritzinsky
jkoritzinsky marked this pull request as ready for review April 8, 2026 18:18
CopilotAI review requested due to automatic review settings April 8, 2026 18:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs
CopilotAI review requested due to automatic review settings April 10, 2026 19:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

@jkoritzinsky
jkoritzinsky merged commit e45ecce into dotnet:mainApr 11, 2026
100 of 111 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Perf] Windows/x64: 3 Regressions on 4/2/2026 1:30:32 AM +00:00

4 participants

@jkoritzinsky@MihuBot@AaronRobinsonMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable - #126621

Merged
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix
Apr 11, 2026
Merged

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable#126621
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix

Conversation

@jkoritzinsky

@jkoritzinskyjkoritzinsky commented Apr 7, 2026

Copy link
Copy Markdown
Member

Fixes#126608

Move the marshalling info to be cached on the type itself instead of in a ConditionalWeakTable.

Move some of the validation (in particular the "has layout" validation) to be done only at "create" time so we don't have to rerun it for every time someone calls the Marshal methods with the same (valid) type.

Use the blittable fallbacks on StructureMarshaler<T> and LayoutClassMarshaler<T> instead of inlining the calls. The cost may go up slightly (delegate call to a method that calls SpanHelpers.Memmove instead of direct call to SpanHelpers.Memmove) but still much faster than the existing implementation that does the "is blittable" checks each time.

CopilotAI review requested due to automatic review settings April 7, 2026 23:32
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI 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.

Pull request overview

This PR moves the cache for Marshal.LayoutTypeMarshalerMethods from a ConditionalWeakTable<Type, …> to the per-RuntimeType generic cache infrastructure to address the reported interop performance regressions (issue #126608).

Changes:

  • Adds LayoutTypeMarshalerMethods storage to RuntimeType.CompositeCacheEntry so it can live in the existing RuntimeType generic cache.
  • Refactors LayoutTypeMarshalerMethods to implement RuntimeType.IGenericCacheEntry<T> and retrieves it via RuntimeType.GetOrCreateCacheEntry<T>().
  • Switches from delegate creation (CreateDelegate) to caching raw function pointers (MethodHandle.GetFunctionPointer) for the marshaler methods.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.csAdds a new composite-cache field to hold the marshaler-methods cache on RuntimeType.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.csReworks LayoutTypeMarshalerMethods to use RuntimeType cache entries and cached function pointers instead of a ConditionalWeakTable.

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 2.07 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain53.46 ns0.045 ns1.0024 B1.00
MarshalStructureToPtrPR41.83 ns0.073 ns0.7824 B1.00
MarshalPtrToStructureMain57.27 ns0.111 ns1.0024 B1.00
MarshalPtrToStructurePR43.72 ns0.169 ns0.7624 B1.00
MarshalDestroyStructureMain109.19 ns0.141 ns1.0096 B1.00
MarshalDestroyStructurePR87.02 ns0.241 ns0.8096 B1.00

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 1.78 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain64.20 ns0.578 ns1.0024 B1.00
MarshalStructureToPtrPR30.70 ns0.567 ns0.4824 B1.00
MarshalPtrToStructureMain62.55 ns0.117 ns1.0024 B1.00
MarshalPtrToStructurePR37.14 ns0.026 ns0.5924 B1.00
MarshalDestroyStructureMain109.97 ns0.152 ns1.0096 B1.00
MarshalDestroyStructurePR68.63 ns0.158 ns0.6296 B1.00

@jkoritzinsky
jkoritzinsky marked this pull request as ready for review April 8, 2026 18:18
CopilotAI review requested due to automatic review settings April 8, 2026 18:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs
CopilotAI review requested due to automatic review settings April 10, 2026 19:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

@jkoritzinsky
jkoritzinsky merged commit e45ecce into dotnet:mainApr 11, 2026
100 of 111 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Perf] Windows/x64: 3 Regressions on 4/2/2026 1:30:32 AM +00:00

4 participants

@jkoritzinsky@MihuBot@AaronRobinsonMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable - #126621

Merged
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix
Apr 11, 2026
Merged

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable#126621
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix

Conversation

@jkoritzinsky

@jkoritzinskyjkoritzinsky commented Apr 7, 2026

Copy link
Copy Markdown
Member

Fixes#126608

Move the marshalling info to be cached on the type itself instead of in a ConditionalWeakTable.

Move some of the validation (in particular the "has layout" validation) to be done only at "create" time so we don't have to rerun it for every time someone calls the Marshal methods with the same (valid) type.

Use the blittable fallbacks on StructureMarshaler<T> and LayoutClassMarshaler<T> instead of inlining the calls. The cost may go up slightly (delegate call to a method that calls SpanHelpers.Memmove instead of direct call to SpanHelpers.Memmove) but still much faster than the existing implementation that does the "is blittable" checks each time.

CopilotAI review requested due to automatic review settings April 7, 2026 23:32
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI 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.

Pull request overview

This PR moves the cache for Marshal.LayoutTypeMarshalerMethods from a ConditionalWeakTable<Type, …> to the per-RuntimeType generic cache infrastructure to address the reported interop performance regressions (issue #126608).

Changes:

  • Adds LayoutTypeMarshalerMethods storage to RuntimeType.CompositeCacheEntry so it can live in the existing RuntimeType generic cache.
  • Refactors LayoutTypeMarshalerMethods to implement RuntimeType.IGenericCacheEntry<T> and retrieves it via RuntimeType.GetOrCreateCacheEntry<T>().
  • Switches from delegate creation (CreateDelegate) to caching raw function pointers (MethodHandle.GetFunctionPointer) for the marshaler methods.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.csAdds a new composite-cache field to hold the marshaler-methods cache on RuntimeType.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.csReworks LayoutTypeMarshalerMethods to use RuntimeType cache entries and cached function pointers instead of a ConditionalWeakTable.

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 2.07 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain53.46 ns0.045 ns1.0024 B1.00
MarshalStructureToPtrPR41.83 ns0.073 ns0.7824 B1.00
MarshalPtrToStructureMain57.27 ns0.111 ns1.0024 B1.00
MarshalPtrToStructurePR43.72 ns0.169 ns0.7624 B1.00
MarshalDestroyStructureMain109.19 ns0.141 ns1.0096 B1.00
MarshalDestroyStructurePR87.02 ns0.241 ns0.8096 B1.00

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 1.78 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain64.20 ns0.578 ns1.0024 B1.00
MarshalStructureToPtrPR30.70 ns0.567 ns0.4824 B1.00
MarshalPtrToStructureMain62.55 ns0.117 ns1.0024 B1.00
MarshalPtrToStructurePR37.14 ns0.026 ns0.5924 B1.00
MarshalDestroyStructureMain109.97 ns0.152 ns1.0096 B1.00
MarshalDestroyStructurePR68.63 ns0.158 ns0.6296 B1.00

@jkoritzinsky
jkoritzinsky marked this pull request as ready for review April 8, 2026 18:18
CopilotAI review requested due to automatic review settings April 8, 2026 18:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs
CopilotAI review requested due to automatic review settings April 10, 2026 19:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

@jkoritzinsky
jkoritzinsky merged commit e45ecce into dotnet:mainApr 11, 2026
100 of 111 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Perf] Windows/x64: 3 Regressions on 4/2/2026 1:30:32 AM +00:00

4 participants

@jkoritzinsky@MihuBot@AaronRobinsonMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable - #126621

Merged
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix
Apr 11, 2026
Merged

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable#126621
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix

Conversation

@jkoritzinsky

@jkoritzinskyjkoritzinsky commented Apr 7, 2026

Copy link
Copy Markdown
Member

Fixes#126608

Move the marshalling info to be cached on the type itself instead of in a ConditionalWeakTable.

Move some of the validation (in particular the "has layout" validation) to be done only at "create" time so we don't have to rerun it for every time someone calls the Marshal methods with the same (valid) type.

Use the blittable fallbacks on StructureMarshaler<T> and LayoutClassMarshaler<T> instead of inlining the calls. The cost may go up slightly (delegate call to a method that calls SpanHelpers.Memmove instead of direct call to SpanHelpers.Memmove) but still much faster than the existing implementation that does the "is blittable" checks each time.

CopilotAI review requested due to automatic review settings April 7, 2026 23:32
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI 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.

Pull request overview

This PR moves the cache for Marshal.LayoutTypeMarshalerMethods from a ConditionalWeakTable<Type, …> to the per-RuntimeType generic cache infrastructure to address the reported interop performance regressions (issue #126608).

Changes:

  • Adds LayoutTypeMarshalerMethods storage to RuntimeType.CompositeCacheEntry so it can live in the existing RuntimeType generic cache.
  • Refactors LayoutTypeMarshalerMethods to implement RuntimeType.IGenericCacheEntry<T> and retrieves it via RuntimeType.GetOrCreateCacheEntry<T>().
  • Switches from delegate creation (CreateDelegate) to caching raw function pointers (MethodHandle.GetFunctionPointer) for the marshaler methods.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.csAdds a new composite-cache field to hold the marshaler-methods cache on RuntimeType.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.csReworks LayoutTypeMarshalerMethods to use RuntimeType cache entries and cached function pointers instead of a ConditionalWeakTable.

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 2.07 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain53.46 ns0.045 ns1.0024 B1.00
MarshalStructureToPtrPR41.83 ns0.073 ns0.7824 B1.00
MarshalPtrToStructureMain57.27 ns0.111 ns1.0024 B1.00
MarshalPtrToStructurePR43.72 ns0.169 ns0.7624 B1.00
MarshalDestroyStructureMain109.19 ns0.141 ns1.0096 B1.00
MarshalDestroyStructurePR87.02 ns0.241 ns0.8096 B1.00

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 1.78 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain64.20 ns0.578 ns1.0024 B1.00
MarshalStructureToPtrPR30.70 ns0.567 ns0.4824 B1.00
MarshalPtrToStructureMain62.55 ns0.117 ns1.0024 B1.00
MarshalPtrToStructurePR37.14 ns0.026 ns0.5924 B1.00
MarshalDestroyStructureMain109.97 ns0.152 ns1.0096 B1.00
MarshalDestroyStructurePR68.63 ns0.158 ns0.6296 B1.00

@jkoritzinsky
jkoritzinsky marked this pull request as ready for review April 8, 2026 18:18
CopilotAI review requested due to automatic review settings April 8, 2026 18:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs
CopilotAI review requested due to automatic review settings April 10, 2026 19:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

@jkoritzinsky
jkoritzinsky merged commit e45ecce into dotnet:mainApr 11, 2026
100 of 111 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Perf] Windows/x64: 3 Regressions on 4/2/2026 1:30:32 AM +00:00

4 participants

@jkoritzinsky@MihuBot@AaronRobinsonMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable - #126621

Merged
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix
Apr 11, 2026
Merged

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable#126621
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix

Conversation

@jkoritzinsky

@jkoritzinskyjkoritzinsky commented Apr 7, 2026

Copy link
Copy Markdown
Member

Fixes#126608

Move the marshalling info to be cached on the type itself instead of in a ConditionalWeakTable.

Move some of the validation (in particular the "has layout" validation) to be done only at "create" time so we don't have to rerun it for every time someone calls the Marshal methods with the same (valid) type.

Use the blittable fallbacks on StructureMarshaler<T> and LayoutClassMarshaler<T> instead of inlining the calls. The cost may go up slightly (delegate call to a method that calls SpanHelpers.Memmove instead of direct call to SpanHelpers.Memmove) but still much faster than the existing implementation that does the "is blittable" checks each time.

CopilotAI review requested due to automatic review settings April 7, 2026 23:32
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI 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.

Pull request overview

This PR moves the cache for Marshal.LayoutTypeMarshalerMethods from a ConditionalWeakTable<Type, …> to the per-RuntimeType generic cache infrastructure to address the reported interop performance regressions (issue #126608).

Changes:

  • Adds LayoutTypeMarshalerMethods storage to RuntimeType.CompositeCacheEntry so it can live in the existing RuntimeType generic cache.
  • Refactors LayoutTypeMarshalerMethods to implement RuntimeType.IGenericCacheEntry<T> and retrieves it via RuntimeType.GetOrCreateCacheEntry<T>().
  • Switches from delegate creation (CreateDelegate) to caching raw function pointers (MethodHandle.GetFunctionPointer) for the marshaler methods.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.csAdds a new composite-cache field to hold the marshaler-methods cache on RuntimeType.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.csReworks LayoutTypeMarshalerMethods to use RuntimeType cache entries and cached function pointers instead of a ConditionalWeakTable.

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 2.07 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain53.46 ns0.045 ns1.0024 B1.00
MarshalStructureToPtrPR41.83 ns0.073 ns0.7824 B1.00
MarshalPtrToStructureMain57.27 ns0.111 ns1.0024 B1.00
MarshalPtrToStructurePR43.72 ns0.169 ns0.7624 B1.00
MarshalDestroyStructureMain109.19 ns0.141 ns1.0096 B1.00
MarshalDestroyStructurePR87.02 ns0.241 ns0.8096 B1.00

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 1.78 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain64.20 ns0.578 ns1.0024 B1.00
MarshalStructureToPtrPR30.70 ns0.567 ns0.4824 B1.00
MarshalPtrToStructureMain62.55 ns0.117 ns1.0024 B1.00
MarshalPtrToStructurePR37.14 ns0.026 ns0.5924 B1.00
MarshalDestroyStructureMain109.97 ns0.152 ns1.0096 B1.00
MarshalDestroyStructurePR68.63 ns0.158 ns0.6296 B1.00

@jkoritzinsky
jkoritzinsky marked this pull request as ready for review April 8, 2026 18:18
CopilotAI review requested due to automatic review settings April 8, 2026 18:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs
CopilotAI review requested due to automatic review settings April 10, 2026 19:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

@jkoritzinsky
jkoritzinsky merged commit e45ecce into dotnet:mainApr 11, 2026
100 of 111 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Perf] Windows/x64: 3 Regressions on 4/2/2026 1:30:32 AM +00:00

4 participants

@jkoritzinsky@MihuBot@AaronRobinsonMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable - #126621

Merged
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix
Apr 11, 2026
Merged

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable#126621
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix

Conversation

@jkoritzinsky

@jkoritzinskyjkoritzinsky commented Apr 7, 2026

Copy link
Copy Markdown
Member

Fixes#126608

Move the marshalling info to be cached on the type itself instead of in a ConditionalWeakTable.

Move some of the validation (in particular the "has layout" validation) to be done only at "create" time so we don't have to rerun it for every time someone calls the Marshal methods with the same (valid) type.

Use the blittable fallbacks on StructureMarshaler<T> and LayoutClassMarshaler<T> instead of inlining the calls. The cost may go up slightly (delegate call to a method that calls SpanHelpers.Memmove instead of direct call to SpanHelpers.Memmove) but still much faster than the existing implementation that does the "is blittable" checks each time.

CopilotAI review requested due to automatic review settings April 7, 2026 23:32
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI 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.

Pull request overview

This PR moves the cache for Marshal.LayoutTypeMarshalerMethods from a ConditionalWeakTable<Type, …> to the per-RuntimeType generic cache infrastructure to address the reported interop performance regressions (issue #126608).

Changes:

  • Adds LayoutTypeMarshalerMethods storage to RuntimeType.CompositeCacheEntry so it can live in the existing RuntimeType generic cache.
  • Refactors LayoutTypeMarshalerMethods to implement RuntimeType.IGenericCacheEntry<T> and retrieves it via RuntimeType.GetOrCreateCacheEntry<T>().
  • Switches from delegate creation (CreateDelegate) to caching raw function pointers (MethodHandle.GetFunctionPointer) for the marshaler methods.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.csAdds a new composite-cache field to hold the marshaler-methods cache on RuntimeType.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.csReworks LayoutTypeMarshalerMethods to use RuntimeType cache entries and cached function pointers instead of a ConditionalWeakTable.

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 2.07 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain53.46 ns0.045 ns1.0024 B1.00
MarshalStructureToPtrPR41.83 ns0.073 ns0.7824 B1.00
MarshalPtrToStructureMain57.27 ns0.111 ns1.0024 B1.00
MarshalPtrToStructurePR43.72 ns0.169 ns0.7624 B1.00
MarshalDestroyStructureMain109.19 ns0.141 ns1.0096 B1.00
MarshalDestroyStructurePR87.02 ns0.241 ns0.8096 B1.00

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 1.78 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain64.20 ns0.578 ns1.0024 B1.00
MarshalStructureToPtrPR30.70 ns0.567 ns0.4824 B1.00
MarshalPtrToStructureMain62.55 ns0.117 ns1.0024 B1.00
MarshalPtrToStructurePR37.14 ns0.026 ns0.5924 B1.00
MarshalDestroyStructureMain109.97 ns0.152 ns1.0096 B1.00
MarshalDestroyStructurePR68.63 ns0.158 ns0.6296 B1.00

@jkoritzinsky
jkoritzinsky marked this pull request as ready for review April 8, 2026 18:18
CopilotAI review requested due to automatic review settings April 8, 2026 18:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs
CopilotAI review requested due to automatic review settings April 10, 2026 19:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

@jkoritzinsky
jkoritzinsky merged commit e45ecce into dotnet:mainApr 11, 2026
100 of 111 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Perf] Windows/x64: 3 Regressions on 4/2/2026 1:30:32 AM +00:00

4 participants

@jkoritzinsky@MihuBot@AaronRobinsonMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable - #126621

Merged
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix
Apr 11, 2026
Merged

Move the cache for the marshaler methods to be stored on the RuntimeType instead of in a ConditionalWeakTable#126621
jkoritzinsky merged 6 commits into
dotnet:mainfrom
jkoritzinsky:marshal-regression-fix

Conversation

@jkoritzinsky

@jkoritzinskyjkoritzinsky commented Apr 7, 2026

Copy link
Copy Markdown
Member

Fixes#126608

Move the marshalling info to be cached on the type itself instead of in a ConditionalWeakTable.

Move some of the validation (in particular the "has layout" validation) to be done only at "create" time so we don't have to rerun it for every time someone calls the Marshal methods with the same (valid) type.

Use the blittable fallbacks on StructureMarshaler<T> and LayoutClassMarshaler<T> instead of inlining the calls. The cost may go up slightly (delegate call to a method that calls SpanHelpers.Memmove instead of direct call to SpanHelpers.Memmove) but still much faster than the existing implementation that does the "is blittable" checks each time.

CopilotAI review requested due to automatic review settings April 7, 2026 23:32
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

CopilotAI 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.

Pull request overview

This PR moves the cache for Marshal.LayoutTypeMarshalerMethods from a ConditionalWeakTable<Type, …> to the per-RuntimeType generic cache infrastructure to address the reported interop performance regressions (issue #126608).

Changes:

  • Adds LayoutTypeMarshalerMethods storage to RuntimeType.CompositeCacheEntry so it can live in the existing RuntimeType generic cache.
  • Refactors LayoutTypeMarshalerMethods to implement RuntimeType.IGenericCacheEntry<T> and retrieves it via RuntimeType.GetOrCreateCacheEntry<T>().
  • Switches from delegate creation (CreateDelegate) to caching raw function pointers (MethodHandle.GetFunctionPointer) for the marshaler methods.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

FileDescription
src/coreclr/System.Private.CoreLib/src/System/RuntimeType.GenericCache.csAdds a new composite-cache field to hold the marshaler-methods cache on RuntimeType.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.CoreCLR.csReworks LayoutTypeMarshalerMethods to use RuntimeType cache entries and cached function pointers instead of a ConditionalWeakTable.

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 2.07 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain53.46 ns0.045 ns1.0024 B1.00
MarshalStructureToPtrPR41.83 ns0.073 ns0.7824 B1.00
MarshalPtrToStructureMain57.27 ns0.111 ns1.0024 B1.00
MarshalPtrToStructurePR43.72 ns0.169 ns0.7624 B1.00
MarshalDestroyStructureMain109.19 ns0.141 ns1.0096 B1.00
MarshalDestroyStructurePR87.02 ns0.241 ns0.8096 B1.00

@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

@MihuBot benchmark Interop.StructureToPtr

@MihuBot

Copy link
Copy Markdown
Interop.StructureToPtr
BenchmarkDotNet v0.16.0-nightly.20260320.467, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 8 logical and 4 physical cores
Memory: 31.34 GB Total, 1.78 GB Available
Job-TPEJOW : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
Job-HKHXHK : .NET 11.0.0 (11.0.0-dev, 42.42.42.42424), X64 RyuJIT x86-64-v4
EvaluateOverhead=False OutlierMode=Default PowerPlanMode=
IterationTime=250ms MaxIterationCount=20 MemoryRandomization=Default
MinIterationCount=15 WarmupCount=1
MethodToolchainMeanErrorRatioAllocatedAlloc Ratio
MarshalStructureToPtrMain64.20 ns0.578 ns1.0024 B1.00
MarshalStructureToPtrPR30.70 ns0.567 ns0.4824 B1.00
MarshalPtrToStructureMain62.55 ns0.117 ns1.0024 B1.00
MarshalPtrToStructurePR37.14 ns0.026 ns0.5924 B1.00
MarshalDestroyStructureMain109.97 ns0.152 ns1.0096 B1.00
MarshalDestroyStructurePR68.63 ns0.158 ns0.6296 B1.00

@jkoritzinsky
jkoritzinsky marked this pull request as ready for review April 8, 2026 18:18
CopilotAI review requested due to automatic review settings April 8, 2026 18:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comment threadsrc/coreclr/System.Private.CoreLib/src/System/StubHelpers.cs
CopilotAI review requested due to automatic review settings April 10, 2026 19:18

CopilotAI 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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

@jkoritzinsky
jkoritzinsky merged commit e45ecce into dotnet:mainApr 11, 2026
100 of 111 checks passed
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 11, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Perf] Windows/x64: 3 Regressions on 4/2/2026 1:30:32 AM +00:00

4 participants

@jkoritzinsky@MihuBot@AaronRobinsonMSFT