Reduce some allocations in SslStream handshake. - #103814

Merged
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc
Jun 24, 2024
Merged

Reduce some allocations in SslStream handshake.#103814
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc

Conversation

@rzikm

@rzikmrzikm commented Jun 21, 2024

Copy link
Copy Markdown
Member

This removes some unnecessary allocations on TLS handshakes, mostly on Linux.


BenchmarkDotNet v0.13.13-nightly.20240311.145, Ubuntu 22.04.4 LTS (Jammy Jellyfish)
Intel Core i9-10900K CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK 9.0.100-preview.5.24307.3
[Host] : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
Job-QJUXKM : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-OFJRMC : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
DefaultHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.089 ms0.0267 ms0.0297 ms1.083 ms1.048 ms1.146 ms1.030.035.81 KB0.98
DefaultHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.058 ms0.0147 ms0.0137 ms1.059 ms1.034 ms1.079 ms1.000.025.96 KB1.00
DefaultMutualHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.432 ms0.0217 ms0.0181 ms1.432 ms1.395 ms1.459 ms1.010.028.06 KB0.75
DefaultMutualHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.424 ms0.0262 ms0.0269 ms1.430 ms1.362 ms1.468 ms1.000.0310.73 KB1.00
DefaultHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun6.375 ms0.3801 ms0.3904 ms6.319 ms5.923 ms7.178 ms1.070.079.67 KB1.00
DefaultHandshakeIPv4AsyncJob-OFJRMC/main/corerun5.961 ms0.1159 ms0.1084 ms5.973 ms5.755 ms6.219 ms1.000.029.69 KB1.00
DefaultMutualHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun11.515 ms0.4361 ms0.5022 ms11.386 ms10.935 ms12.586 ms1.020.0514.56 KB0.84
DefaultMutualHandshakeIPv4AsyncJob-OFJRMC/main/corerun11.323 ms0.2131 ms0.1994 ms11.356 ms11.020 ms11.657 ms1.000.0217.37 KB1.00

Comment on lines +162 to +171
byte[]? hash = null;

if (sslAuthenticationOptions.CertificateContext?.TargetCertificate is X509Certificate2 cert)
{
// This is equivalent to cert.GetCertHash(HashAlgorithmName.Sha256), but this
// way the code does not allocate a new byte[] with raw cert contents.every time
hash = SHA256.HashData(cert.RawDataMemory.Span);
}

var key = new SslContextCacheKey(protocols, hash);

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@bartonjs is the X509Certificate.GetCertHash(...) behavior by design? looks like something that could potentially help in multiple places if we can change the implementation to use the internaly cached RawData.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Not Jeremy, but...) I don't think so. We can probably make some improvements here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since it's using certPal.RawData instead of this.RawData, I think it's trying to avoid the allocation; but clearly some PALs are caching that and others are not, because we started off with a weak PAL contract and haven't ever cleaned it up.

In this case, it looks like just switching from certPal.RawData to this.RawDataMemory.Span will mean we know exactly what caching is involved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With #103828 merged I am not sure this is needed anymore.

@rzikm
rzikm requested a review from wfurtJune 21, 2024 12:37
private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols protocols)
{
if (protocols.HasFlag(SslProtocols.Tls12) || protocols.HasFlag(SslProtocols.Tls13))
if ((protocols & (SslProtocols.Tls12 | SslProtocols.Tls13)) != SslProtocols.None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The change is fine, but did you see this allocating?

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I collected a trace via dotnet trace and saw an entry for this. The signature is apparently

publicboolHasFlag(Enumflag);

so I assume it is boxing the argument. The trace was in Debug configuration, I am not sure if it happens in Release as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not sure if it happens in Release as well.

The JIT special-cases HasFlag. It shouldn't be allocating in optimized code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The trace was in Debug configuration

You generally don't want to do any perf analysis based on debug. Optimizations are disabled, both at the JIT level and also the C# level (e.g. async methods get emitted as classes rather than structs), so it provides a skewed view of the world.

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I got unsure and rechecked the trace, it indeed comes from a different place. But I don't see it in the most recent traces for some reason.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@rzikm@vcsjones@stephentoub@bartonjs@karelz
, '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

Reduce some allocations in SslStream handshake. - #103814

Merged
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc
Jun 24, 2024
Merged

Reduce some allocations in SslStream handshake.#103814
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc

Conversation

@rzikm

@rzikmrzikm commented Jun 21, 2024

Copy link
Copy Markdown
Member

This removes some unnecessary allocations on TLS handshakes, mostly on Linux.


BenchmarkDotNet v0.13.13-nightly.20240311.145, Ubuntu 22.04.4 LTS (Jammy Jellyfish)
Intel Core i9-10900K CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK 9.0.100-preview.5.24307.3
[Host] : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
Job-QJUXKM : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-OFJRMC : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
DefaultHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.089 ms0.0267 ms0.0297 ms1.083 ms1.048 ms1.146 ms1.030.035.81 KB0.98
DefaultHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.058 ms0.0147 ms0.0137 ms1.059 ms1.034 ms1.079 ms1.000.025.96 KB1.00
DefaultMutualHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.432 ms0.0217 ms0.0181 ms1.432 ms1.395 ms1.459 ms1.010.028.06 KB0.75
DefaultMutualHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.424 ms0.0262 ms0.0269 ms1.430 ms1.362 ms1.468 ms1.000.0310.73 KB1.00
DefaultHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun6.375 ms0.3801 ms0.3904 ms6.319 ms5.923 ms7.178 ms1.070.079.67 KB1.00
DefaultHandshakeIPv4AsyncJob-OFJRMC/main/corerun5.961 ms0.1159 ms0.1084 ms5.973 ms5.755 ms6.219 ms1.000.029.69 KB1.00
DefaultMutualHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun11.515 ms0.4361 ms0.5022 ms11.386 ms10.935 ms12.586 ms1.020.0514.56 KB0.84
DefaultMutualHandshakeIPv4AsyncJob-OFJRMC/main/corerun11.323 ms0.2131 ms0.1994 ms11.356 ms11.020 ms11.657 ms1.000.0217.37 KB1.00

Comment on lines +162 to +171
byte[]? hash = null;

if (sslAuthenticationOptions.CertificateContext?.TargetCertificate is X509Certificate2 cert)
{
// This is equivalent to cert.GetCertHash(HashAlgorithmName.Sha256), but this
// way the code does not allocate a new byte[] with raw cert contents.every time
hash = SHA256.HashData(cert.RawDataMemory.Span);
}

var key = new SslContextCacheKey(protocols, hash);

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@bartonjs is the X509Certificate.GetCertHash(...) behavior by design? looks like something that could potentially help in multiple places if we can change the implementation to use the internaly cached RawData.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Not Jeremy, but...) I don't think so. We can probably make some improvements here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since it's using certPal.RawData instead of this.RawData, I think it's trying to avoid the allocation; but clearly some PALs are caching that and others are not, because we started off with a weak PAL contract and haven't ever cleaned it up.

In this case, it looks like just switching from certPal.RawData to this.RawDataMemory.Span will mean we know exactly what caching is involved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With #103828 merged I am not sure this is needed anymore.

@rzikm
rzikm requested a review from wfurtJune 21, 2024 12:37
private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols protocols)
{
if (protocols.HasFlag(SslProtocols.Tls12) || protocols.HasFlag(SslProtocols.Tls13))
if ((protocols & (SslProtocols.Tls12 | SslProtocols.Tls13)) != SslProtocols.None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The change is fine, but did you see this allocating?

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I collected a trace via dotnet trace and saw an entry for this. The signature is apparently

publicboolHasFlag(Enumflag);

so I assume it is boxing the argument. The trace was in Debug configuration, I am not sure if it happens in Release as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not sure if it happens in Release as well.

The JIT special-cases HasFlag. It shouldn't be allocating in optimized code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The trace was in Debug configuration

You generally don't want to do any perf analysis based on debug. Optimizations are disabled, both at the JIT level and also the C# level (e.g. async methods get emitted as classes rather than structs), so it provides a skewed view of the world.

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I got unsure and rechecked the trace, it indeed comes from a different place. But I don't see it in the most recent traces for some reason.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@rzikm@vcsjones@stephentoub@bartonjs@karelz
, '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

Reduce some allocations in SslStream handshake. - #103814

Merged
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc
Jun 24, 2024
Merged

Reduce some allocations in SslStream handshake.#103814
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc

Conversation

@rzikm

@rzikmrzikm commented Jun 21, 2024

Copy link
Copy Markdown
Member

This removes some unnecessary allocations on TLS handshakes, mostly on Linux.


BenchmarkDotNet v0.13.13-nightly.20240311.145, Ubuntu 22.04.4 LTS (Jammy Jellyfish)
Intel Core i9-10900K CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK 9.0.100-preview.5.24307.3
[Host] : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
Job-QJUXKM : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-OFJRMC : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
DefaultHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.089 ms0.0267 ms0.0297 ms1.083 ms1.048 ms1.146 ms1.030.035.81 KB0.98
DefaultHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.058 ms0.0147 ms0.0137 ms1.059 ms1.034 ms1.079 ms1.000.025.96 KB1.00
DefaultMutualHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.432 ms0.0217 ms0.0181 ms1.432 ms1.395 ms1.459 ms1.010.028.06 KB0.75
DefaultMutualHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.424 ms0.0262 ms0.0269 ms1.430 ms1.362 ms1.468 ms1.000.0310.73 KB1.00
DefaultHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun6.375 ms0.3801 ms0.3904 ms6.319 ms5.923 ms7.178 ms1.070.079.67 KB1.00
DefaultHandshakeIPv4AsyncJob-OFJRMC/main/corerun5.961 ms0.1159 ms0.1084 ms5.973 ms5.755 ms6.219 ms1.000.029.69 KB1.00
DefaultMutualHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun11.515 ms0.4361 ms0.5022 ms11.386 ms10.935 ms12.586 ms1.020.0514.56 KB0.84
DefaultMutualHandshakeIPv4AsyncJob-OFJRMC/main/corerun11.323 ms0.2131 ms0.1994 ms11.356 ms11.020 ms11.657 ms1.000.0217.37 KB1.00

Comment on lines +162 to +171
byte[]? hash = null;

if (sslAuthenticationOptions.CertificateContext?.TargetCertificate is X509Certificate2 cert)
{
// This is equivalent to cert.GetCertHash(HashAlgorithmName.Sha256), but this
// way the code does not allocate a new byte[] with raw cert contents.every time
hash = SHA256.HashData(cert.RawDataMemory.Span);
}

var key = new SslContextCacheKey(protocols, hash);

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@bartonjs is the X509Certificate.GetCertHash(...) behavior by design? looks like something that could potentially help in multiple places if we can change the implementation to use the internaly cached RawData.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Not Jeremy, but...) I don't think so. We can probably make some improvements here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since it's using certPal.RawData instead of this.RawData, I think it's trying to avoid the allocation; but clearly some PALs are caching that and others are not, because we started off with a weak PAL contract and haven't ever cleaned it up.

In this case, it looks like just switching from certPal.RawData to this.RawDataMemory.Span will mean we know exactly what caching is involved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With #103828 merged I am not sure this is needed anymore.

@rzikm
rzikm requested a review from wfurtJune 21, 2024 12:37
private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols protocols)
{
if (protocols.HasFlag(SslProtocols.Tls12) || protocols.HasFlag(SslProtocols.Tls13))
if ((protocols & (SslProtocols.Tls12 | SslProtocols.Tls13)) != SslProtocols.None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The change is fine, but did you see this allocating?

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I collected a trace via dotnet trace and saw an entry for this. The signature is apparently

publicboolHasFlag(Enumflag);

so I assume it is boxing the argument. The trace was in Debug configuration, I am not sure if it happens in Release as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not sure if it happens in Release as well.

The JIT special-cases HasFlag. It shouldn't be allocating in optimized code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The trace was in Debug configuration

You generally don't want to do any perf analysis based on debug. Optimizations are disabled, both at the JIT level and also the C# level (e.g. async methods get emitted as classes rather than structs), so it provides a skewed view of the world.

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I got unsure and rechecked the trace, it indeed comes from a different place. But I don't see it in the most recent traces for some reason.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@rzikm@vcsjones@stephentoub@bartonjs@karelz
, '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

Reduce some allocations in SslStream handshake. - #103814

Merged
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc
Jun 24, 2024
Merged

Reduce some allocations in SslStream handshake.#103814
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc

Conversation

@rzikm

@rzikmrzikm commented Jun 21, 2024

Copy link
Copy Markdown
Member

This removes some unnecessary allocations on TLS handshakes, mostly on Linux.


BenchmarkDotNet v0.13.13-nightly.20240311.145, Ubuntu 22.04.4 LTS (Jammy Jellyfish)
Intel Core i9-10900K CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK 9.0.100-preview.5.24307.3
[Host] : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
Job-QJUXKM : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-OFJRMC : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
DefaultHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.089 ms0.0267 ms0.0297 ms1.083 ms1.048 ms1.146 ms1.030.035.81 KB0.98
DefaultHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.058 ms0.0147 ms0.0137 ms1.059 ms1.034 ms1.079 ms1.000.025.96 KB1.00
DefaultMutualHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.432 ms0.0217 ms0.0181 ms1.432 ms1.395 ms1.459 ms1.010.028.06 KB0.75
DefaultMutualHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.424 ms0.0262 ms0.0269 ms1.430 ms1.362 ms1.468 ms1.000.0310.73 KB1.00
DefaultHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun6.375 ms0.3801 ms0.3904 ms6.319 ms5.923 ms7.178 ms1.070.079.67 KB1.00
DefaultHandshakeIPv4AsyncJob-OFJRMC/main/corerun5.961 ms0.1159 ms0.1084 ms5.973 ms5.755 ms6.219 ms1.000.029.69 KB1.00
DefaultMutualHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun11.515 ms0.4361 ms0.5022 ms11.386 ms10.935 ms12.586 ms1.020.0514.56 KB0.84
DefaultMutualHandshakeIPv4AsyncJob-OFJRMC/main/corerun11.323 ms0.2131 ms0.1994 ms11.356 ms11.020 ms11.657 ms1.000.0217.37 KB1.00

Comment on lines +162 to +171
byte[]? hash = null;

if (sslAuthenticationOptions.CertificateContext?.TargetCertificate is X509Certificate2 cert)
{
// This is equivalent to cert.GetCertHash(HashAlgorithmName.Sha256), but this
// way the code does not allocate a new byte[] with raw cert contents.every time
hash = SHA256.HashData(cert.RawDataMemory.Span);
}

var key = new SslContextCacheKey(protocols, hash);

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@bartonjs is the X509Certificate.GetCertHash(...) behavior by design? looks like something that could potentially help in multiple places if we can change the implementation to use the internaly cached RawData.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Not Jeremy, but...) I don't think so. We can probably make some improvements here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since it's using certPal.RawData instead of this.RawData, I think it's trying to avoid the allocation; but clearly some PALs are caching that and others are not, because we started off with a weak PAL contract and haven't ever cleaned it up.

In this case, it looks like just switching from certPal.RawData to this.RawDataMemory.Span will mean we know exactly what caching is involved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With #103828 merged I am not sure this is needed anymore.

@rzikm
rzikm requested a review from wfurtJune 21, 2024 12:37
private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols protocols)
{
if (protocols.HasFlag(SslProtocols.Tls12) || protocols.HasFlag(SslProtocols.Tls13))
if ((protocols & (SslProtocols.Tls12 | SslProtocols.Tls13)) != SslProtocols.None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The change is fine, but did you see this allocating?

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I collected a trace via dotnet trace and saw an entry for this. The signature is apparently

publicboolHasFlag(Enumflag);

so I assume it is boxing the argument. The trace was in Debug configuration, I am not sure if it happens in Release as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not sure if it happens in Release as well.

The JIT special-cases HasFlag. It shouldn't be allocating in optimized code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The trace was in Debug configuration

You generally don't want to do any perf analysis based on debug. Optimizations are disabled, both at the JIT level and also the C# level (e.g. async methods get emitted as classes rather than structs), so it provides a skewed view of the world.

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I got unsure and rechecked the trace, it indeed comes from a different place. But I don't see it in the most recent traces for some reason.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@rzikm@vcsjones@stephentoub@bartonjs@karelz
, '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

Reduce some allocations in SslStream handshake. - #103814

Merged
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc
Jun 24, 2024
Merged

Reduce some allocations in SslStream handshake.#103814
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc

Conversation

@rzikm

@rzikmrzikm commented Jun 21, 2024

Copy link
Copy Markdown
Member

This removes some unnecessary allocations on TLS handshakes, mostly on Linux.


BenchmarkDotNet v0.13.13-nightly.20240311.145, Ubuntu 22.04.4 LTS (Jammy Jellyfish)
Intel Core i9-10900K CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK 9.0.100-preview.5.24307.3
[Host] : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
Job-QJUXKM : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-OFJRMC : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
DefaultHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.089 ms0.0267 ms0.0297 ms1.083 ms1.048 ms1.146 ms1.030.035.81 KB0.98
DefaultHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.058 ms0.0147 ms0.0137 ms1.059 ms1.034 ms1.079 ms1.000.025.96 KB1.00
DefaultMutualHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.432 ms0.0217 ms0.0181 ms1.432 ms1.395 ms1.459 ms1.010.028.06 KB0.75
DefaultMutualHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.424 ms0.0262 ms0.0269 ms1.430 ms1.362 ms1.468 ms1.000.0310.73 KB1.00
DefaultHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun6.375 ms0.3801 ms0.3904 ms6.319 ms5.923 ms7.178 ms1.070.079.67 KB1.00
DefaultHandshakeIPv4AsyncJob-OFJRMC/main/corerun5.961 ms0.1159 ms0.1084 ms5.973 ms5.755 ms6.219 ms1.000.029.69 KB1.00
DefaultMutualHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun11.515 ms0.4361 ms0.5022 ms11.386 ms10.935 ms12.586 ms1.020.0514.56 KB0.84
DefaultMutualHandshakeIPv4AsyncJob-OFJRMC/main/corerun11.323 ms0.2131 ms0.1994 ms11.356 ms11.020 ms11.657 ms1.000.0217.37 KB1.00

Comment on lines +162 to +171
byte[]? hash = null;

if (sslAuthenticationOptions.CertificateContext?.TargetCertificate is X509Certificate2 cert)
{
// This is equivalent to cert.GetCertHash(HashAlgorithmName.Sha256), but this
// way the code does not allocate a new byte[] with raw cert contents.every time
hash = SHA256.HashData(cert.RawDataMemory.Span);
}

var key = new SslContextCacheKey(protocols, hash);

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@bartonjs is the X509Certificate.GetCertHash(...) behavior by design? looks like something that could potentially help in multiple places if we can change the implementation to use the internaly cached RawData.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Not Jeremy, but...) I don't think so. We can probably make some improvements here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since it's using certPal.RawData instead of this.RawData, I think it's trying to avoid the allocation; but clearly some PALs are caching that and others are not, because we started off with a weak PAL contract and haven't ever cleaned it up.

In this case, it looks like just switching from certPal.RawData to this.RawDataMemory.Span will mean we know exactly what caching is involved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With #103828 merged I am not sure this is needed anymore.

@rzikm
rzikm requested a review from wfurtJune 21, 2024 12:37
private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols protocols)
{
if (protocols.HasFlag(SslProtocols.Tls12) || protocols.HasFlag(SslProtocols.Tls13))
if ((protocols & (SslProtocols.Tls12 | SslProtocols.Tls13)) != SslProtocols.None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The change is fine, but did you see this allocating?

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I collected a trace via dotnet trace and saw an entry for this. The signature is apparently

publicboolHasFlag(Enumflag);

so I assume it is boxing the argument. The trace was in Debug configuration, I am not sure if it happens in Release as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not sure if it happens in Release as well.

The JIT special-cases HasFlag. It shouldn't be allocating in optimized code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The trace was in Debug configuration

You generally don't want to do any perf analysis based on debug. Optimizations are disabled, both at the JIT level and also the C# level (e.g. async methods get emitted as classes rather than structs), so it provides a skewed view of the world.

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I got unsure and rechecked the trace, it indeed comes from a different place. But I don't see it in the most recent traces for some reason.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@rzikm@vcsjones@stephentoub@bartonjs@karelz
, '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

Reduce some allocations in SslStream handshake. - #103814

Merged
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc
Jun 24, 2024
Merged

Reduce some allocations in SslStream handshake.#103814
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc

Conversation

@rzikm

@rzikmrzikm commented Jun 21, 2024

Copy link
Copy Markdown
Member

This removes some unnecessary allocations on TLS handshakes, mostly on Linux.


BenchmarkDotNet v0.13.13-nightly.20240311.145, Ubuntu 22.04.4 LTS (Jammy Jellyfish)
Intel Core i9-10900K CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK 9.0.100-preview.5.24307.3
[Host] : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
Job-QJUXKM : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-OFJRMC : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
DefaultHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.089 ms0.0267 ms0.0297 ms1.083 ms1.048 ms1.146 ms1.030.035.81 KB0.98
DefaultHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.058 ms0.0147 ms0.0137 ms1.059 ms1.034 ms1.079 ms1.000.025.96 KB1.00
DefaultMutualHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.432 ms0.0217 ms0.0181 ms1.432 ms1.395 ms1.459 ms1.010.028.06 KB0.75
DefaultMutualHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.424 ms0.0262 ms0.0269 ms1.430 ms1.362 ms1.468 ms1.000.0310.73 KB1.00
DefaultHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun6.375 ms0.3801 ms0.3904 ms6.319 ms5.923 ms7.178 ms1.070.079.67 KB1.00
DefaultHandshakeIPv4AsyncJob-OFJRMC/main/corerun5.961 ms0.1159 ms0.1084 ms5.973 ms5.755 ms6.219 ms1.000.029.69 KB1.00
DefaultMutualHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun11.515 ms0.4361 ms0.5022 ms11.386 ms10.935 ms12.586 ms1.020.0514.56 KB0.84
DefaultMutualHandshakeIPv4AsyncJob-OFJRMC/main/corerun11.323 ms0.2131 ms0.1994 ms11.356 ms11.020 ms11.657 ms1.000.0217.37 KB1.00

Comment on lines +162 to +171
byte[]? hash = null;

if (sslAuthenticationOptions.CertificateContext?.TargetCertificate is X509Certificate2 cert)
{
// This is equivalent to cert.GetCertHash(HashAlgorithmName.Sha256), but this
// way the code does not allocate a new byte[] with raw cert contents.every time
hash = SHA256.HashData(cert.RawDataMemory.Span);
}

var key = new SslContextCacheKey(protocols, hash);

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@bartonjs is the X509Certificate.GetCertHash(...) behavior by design? looks like something that could potentially help in multiple places if we can change the implementation to use the internaly cached RawData.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Not Jeremy, but...) I don't think so. We can probably make some improvements here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since it's using certPal.RawData instead of this.RawData, I think it's trying to avoid the allocation; but clearly some PALs are caching that and others are not, because we started off with a weak PAL contract and haven't ever cleaned it up.

In this case, it looks like just switching from certPal.RawData to this.RawDataMemory.Span will mean we know exactly what caching is involved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With #103828 merged I am not sure this is needed anymore.

@rzikm
rzikm requested a review from wfurtJune 21, 2024 12:37
private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols protocols)
{
if (protocols.HasFlag(SslProtocols.Tls12) || protocols.HasFlag(SslProtocols.Tls13))
if ((protocols & (SslProtocols.Tls12 | SslProtocols.Tls13)) != SslProtocols.None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The change is fine, but did you see this allocating?

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I collected a trace via dotnet trace and saw an entry for this. The signature is apparently

publicboolHasFlag(Enumflag);

so I assume it is boxing the argument. The trace was in Debug configuration, I am not sure if it happens in Release as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not sure if it happens in Release as well.

The JIT special-cases HasFlag. It shouldn't be allocating in optimized code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The trace was in Debug configuration

You generally don't want to do any perf analysis based on debug. Optimizations are disabled, both at the JIT level and also the C# level (e.g. async methods get emitted as classes rather than structs), so it provides a skewed view of the world.

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I got unsure and rechecked the trace, it indeed comes from a different place. But I don't see it in the most recent traces for some reason.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@rzikm@vcsjones@stephentoub@bartonjs@karelz
, '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

Reduce some allocations in SslStream handshake. - #103814

Merged
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc
Jun 24, 2024
Merged

Reduce some allocations in SslStream handshake.#103814
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc

Conversation

@rzikm

@rzikmrzikm commented Jun 21, 2024

Copy link
Copy Markdown
Member

This removes some unnecessary allocations on TLS handshakes, mostly on Linux.


BenchmarkDotNet v0.13.13-nightly.20240311.145, Ubuntu 22.04.4 LTS (Jammy Jellyfish)
Intel Core i9-10900K CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK 9.0.100-preview.5.24307.3
[Host] : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
Job-QJUXKM : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-OFJRMC : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
DefaultHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.089 ms0.0267 ms0.0297 ms1.083 ms1.048 ms1.146 ms1.030.035.81 KB0.98
DefaultHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.058 ms0.0147 ms0.0137 ms1.059 ms1.034 ms1.079 ms1.000.025.96 KB1.00
DefaultMutualHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.432 ms0.0217 ms0.0181 ms1.432 ms1.395 ms1.459 ms1.010.028.06 KB0.75
DefaultMutualHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.424 ms0.0262 ms0.0269 ms1.430 ms1.362 ms1.468 ms1.000.0310.73 KB1.00
DefaultHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun6.375 ms0.3801 ms0.3904 ms6.319 ms5.923 ms7.178 ms1.070.079.67 KB1.00
DefaultHandshakeIPv4AsyncJob-OFJRMC/main/corerun5.961 ms0.1159 ms0.1084 ms5.973 ms5.755 ms6.219 ms1.000.029.69 KB1.00
DefaultMutualHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun11.515 ms0.4361 ms0.5022 ms11.386 ms10.935 ms12.586 ms1.020.0514.56 KB0.84
DefaultMutualHandshakeIPv4AsyncJob-OFJRMC/main/corerun11.323 ms0.2131 ms0.1994 ms11.356 ms11.020 ms11.657 ms1.000.0217.37 KB1.00

Comment on lines +162 to +171
byte[]? hash = null;

if (sslAuthenticationOptions.CertificateContext?.TargetCertificate is X509Certificate2 cert)
{
// This is equivalent to cert.GetCertHash(HashAlgorithmName.Sha256), but this
// way the code does not allocate a new byte[] with raw cert contents.every time
hash = SHA256.HashData(cert.RawDataMemory.Span);
}

var key = new SslContextCacheKey(protocols, hash);

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@bartonjs is the X509Certificate.GetCertHash(...) behavior by design? looks like something that could potentially help in multiple places if we can change the implementation to use the internaly cached RawData.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Not Jeremy, but...) I don't think so. We can probably make some improvements here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since it's using certPal.RawData instead of this.RawData, I think it's trying to avoid the allocation; but clearly some PALs are caching that and others are not, because we started off with a weak PAL contract and haven't ever cleaned it up.

In this case, it looks like just switching from certPal.RawData to this.RawDataMemory.Span will mean we know exactly what caching is involved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With #103828 merged I am not sure this is needed anymore.

@rzikm
rzikm requested a review from wfurtJune 21, 2024 12:37
private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols protocols)
{
if (protocols.HasFlag(SslProtocols.Tls12) || protocols.HasFlag(SslProtocols.Tls13))
if ((protocols & (SslProtocols.Tls12 | SslProtocols.Tls13)) != SslProtocols.None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The change is fine, but did you see this allocating?

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I collected a trace via dotnet trace and saw an entry for this. The signature is apparently

publicboolHasFlag(Enumflag);

so I assume it is boxing the argument. The trace was in Debug configuration, I am not sure if it happens in Release as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not sure if it happens in Release as well.

The JIT special-cases HasFlag. It shouldn't be allocating in optimized code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The trace was in Debug configuration

You generally don't want to do any perf analysis based on debug. Optimizations are disabled, both at the JIT level and also the C# level (e.g. async methods get emitted as classes rather than structs), so it provides a skewed view of the world.

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I got unsure and rechecked the trace, it indeed comes from a different place. But I don't see it in the most recent traces for some reason.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@rzikm@vcsjones@stephentoub@bartonjs@karelz
, '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

Reduce some allocations in SslStream handshake. - #103814

Merged
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc
Jun 24, 2024
Merged

Reduce some allocations in SslStream handshake.#103814
rzikm merged 2 commits into
dotnet:mainfrom
rzikm:mutualAuth-reduce-gc

Conversation

@rzikm

@rzikmrzikm commented Jun 21, 2024

Copy link
Copy Markdown
Member

This removes some unnecessary allocations on TLS handshakes, mostly on Linux.


BenchmarkDotNet v0.13.13-nightly.20240311.145, Ubuntu 22.04.4 LTS (Jammy Jellyfish)
Intel Core i9-10900K CPU 3.70GHz, 1 CPU, 20 logical and 10 physical cores
.NET SDK 9.0.100-preview.5.24307.3
[Host] : .NET 9.0.0 (9.0.24.30607), X64 RyuJIT AVX2
Job-QJUXKM : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
Job-OFJRMC : .NET 9.0.0 (42.42.42.42424), X64 RyuJIT AVX2
PowerPlanMode=00000000-0000-0000-0000-000000000000 IterationTime=250ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainMeanErrorStdDevMedianMinMaxRatioRatioSDAllocatedAlloc Ratio
DefaultHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.089 ms0.0267 ms0.0297 ms1.083 ms1.048 ms1.146 ms1.030.035.81 KB0.98
DefaultHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.058 ms0.0147 ms0.0137 ms1.059 ms1.034 ms1.079 ms1.000.025.96 KB1.00
DefaultMutualHandshakeContextIPv4AsyncJob-QJUXKM/9.0.0/corerun1.432 ms0.0217 ms0.0181 ms1.432 ms1.395 ms1.459 ms1.010.028.06 KB0.75
DefaultMutualHandshakeContextIPv4AsyncJob-OFJRMC/main/corerun1.424 ms0.0262 ms0.0269 ms1.430 ms1.362 ms1.468 ms1.000.0310.73 KB1.00
DefaultHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun6.375 ms0.3801 ms0.3904 ms6.319 ms5.923 ms7.178 ms1.070.079.67 KB1.00
DefaultHandshakeIPv4AsyncJob-OFJRMC/main/corerun5.961 ms0.1159 ms0.1084 ms5.973 ms5.755 ms6.219 ms1.000.029.69 KB1.00
DefaultMutualHandshakeIPv4AsyncJob-QJUXKM/9.0.0/corerun11.515 ms0.4361 ms0.5022 ms11.386 ms10.935 ms12.586 ms1.020.0514.56 KB0.84
DefaultMutualHandshakeIPv4AsyncJob-OFJRMC/main/corerun11.323 ms0.2131 ms0.1994 ms11.356 ms11.020 ms11.657 ms1.000.0217.37 KB1.00

Comment on lines +162 to +171
byte[]? hash = null;

if (sslAuthenticationOptions.CertificateContext?.TargetCertificate is X509Certificate2 cert)
{
// This is equivalent to cert.GetCertHash(HashAlgorithmName.Sha256), but this
// way the code does not allocate a new byte[] with raw cert contents.every time
hash = SHA256.HashData(cert.RawDataMemory.Span);
}

var key = new SslContextCacheKey(protocols, hash);

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

@bartonjs is the X509Certificate.GetCertHash(...) behavior by design? looks like something that could potentially help in multiple places if we can change the implementation to use the internaly cached RawData.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Not Jeremy, but...) I don't think so. We can probably make some improvements here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Since it's using certPal.RawData instead of this.RawData, I think it's trying to avoid the allocation; but clearly some PALs are caching that and others are not, because we started off with a weak PAL contract and haven't ever cleaned it up.

In this case, it looks like just switching from certPal.RawData to this.RawDataMemory.Span will mean we know exactly what caching is involved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

With #103828 merged I am not sure this is needed anymore.

@rzikm
rzikm requested a review from wfurtJune 21, 2024 12:37
private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols protocols)
{
if (protocols.HasFlag(SslProtocols.Tls12) || protocols.HasFlag(SslProtocols.Tls13))
if ((protocols & (SslProtocols.Tls12 | SslProtocols.Tls13)) != SslProtocols.None)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The change is fine, but did you see this allocating?

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I collected a trace via dotnet trace and saw an entry for this. The signature is apparently

publicboolHasFlag(Enumflag);

so I assume it is boxing the argument. The trace was in Debug configuration, I am not sure if it happens in Release as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not sure if it happens in Release as well.

The JIT special-cases HasFlag. It shouldn't be allocating in optimized code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The trace was in Debug configuration

You generally don't want to do any perf analysis based on debug. Optimizations are disabled, both at the JIT level and also the C# level (e.g. async methods get emitted as classes rather than structs), so it provides a skewed view of the world.

@rzikmrzikmJun 21, 2024

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I got unsure and rechecked the trace, it indeed comes from a different place. But I don't see it in the most recent traces for some reason.

@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jul 25, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@rzikm@vcsjones@stephentoub@bartonjs@karelz