Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections - #94667

Merged
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation
Nov 13, 2023
Merged

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections#94667
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation

Conversation

@andrewjsaid

@andrewjsaidandrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
Contributor

See #93986 for full context

Fix#93974

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Collections labels Nov 13, 2023
@ghost

Copy link
Copy Markdown

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

Issue Details

See #93986 for full context

Author:andrewjsaid
Assignees:-
Labels:

area-System.Collections, community-contribution

Milestone:-

@eiriktsarpaliseiriktsarpalis left a comment

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.

To get a feel of potential perf regressions, would it be possible to share benchmarks comparing this to main in the impacted cases?

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis That would require a PR in the performance repo. Is it possible for somebody from the MS team to do that as I am pretty busy. If not I'll try find some time.

@stephentoubstephentoub left a comment

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.

Thanks. Do we have an understanding of what uses this might regress and by how much? We should make the change, regardless, but I'd like to understand the ramifications and what we should be looking at following-up on.

@andrewjsaid
andrewjsaidforce-pushed the frozen-collections-ordinal-ignore-case-remove-optimisation branch from c44f0a1 to c45a592CompareNovember 13, 2023 15:00
@andrewjsaid

andrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
ContributorAuthor

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

@stephentoub

Copy link
Copy Markdown
Member

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

Ok, thanks. As noted, we don't need to block on it. @eiriktsarpalis, if you have some time to get some quick numbers just to aid in our understanding of the impact and whether there's an important follow-up here, that'd be helpful.

@eiriktsarpalis

Copy link
Copy Markdown
Member

I tweaked the Perf_SubstringFrozenDictionary benchmark to use case insensitive comparison and here's what I got:

MethodJobToolchainCountMeanErrorStdDevMedianMinMaxRatioMannWhitney(3%)RatioSDGen0Gen1Gen2AllocatedAlloc Ratio
ToFrozenDictionaryJob-LOTKLJmain101,389.48 ns31.906 ns36.743 ns1,382.71 ns1,327.53 ns1,473.59 ns1.00Base0.000.1660--1720 B1.00
ToFrozenDictionaryJob-QETJTAPR101,393.58 ns27.624 ns31.812 ns1,399.03 ns1,323.29 ns1,453.86 ns1.00Same0.040.1708--1720 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain1054.00 ns1.288 ns1.322 ns54.13 ns51.35 ns55.60 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1073.24 ns1.078 ns1.008 ns73.38 ns70.06 ns74.24 ns1.35Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain1008,446.66 ns158.800 ns163.076 ns8,471.45 ns8,134.23 ns8,717.36 ns1.00Base0.001.1780--12112 B1.00
ToFrozenDictionaryJob-QETJTAPR1008,771.04 ns237.326 ns273.305 ns8,836.49 ns8,160.74 ns9,217.86 ns1.04Same0.031.2019--12112 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100563.85 ns10.168 ns9.014 ns567.79 ns547.12 ns576.15 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1001,006.73 ns15.021 ns14.050 ns1,006.87 ns985.24 ns1,036.20 ns1.79Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain100074,531.61 ns1,469.417 ns1,508.983 ns74,819.41 ns71,279.16 ns77,269.59 ns1.00Base0.009.27031.1962-94864 B1.00
ToFrozenDictionaryJob-QETJTAPR100072,473.19 ns1,866.260 ns2,149.188 ns73,203.68 ns68,539.46 ns75,121.58 ns0.98Same0.039.22621.1905-94864 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100048,575.95 ns935.546 ns918.831 ns48,985.45 ns46,654.11 ns50,021.83 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR100050,132.24 ns976.166 ns958.725 ns50,439.63 ns47,947.67 ns51,144.53 ns1.03Same0.03----NA
ToFrozenDictionaryJob-LOTKLJmain100001,367,777.25 ns31,636.638 ns32,488.498 ns1,378,004.69 ns1,300,021.88 ns1,428,109.38 ns1.00Base0.00148.4375148.4375148.4375926138 B1.00
ToFrozenDictionaryJob-QETJTAPR100001,361,651.79 ns26,136.206 ns25,669.249 ns1,371,019.27 ns1,298,396.35 ns1,389,535.42 ns1.00Same0.03145.8333145.8333145.8333926134 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain10000429,049.49 ns7,951.179 ns7,048.514 ns430,354.73 ns412,395.95 ns438,879.39 ns1.00Base0.00---1 B1.00
TryGetValue_True_FrozenDictionaryJob-QETJTAPR10000433,232.35 ns7,438.534 ns6,958.009 ns435,446.22 ns419,605.92 ns442,993.75 ns1.01Same0.03---11 B11.00
ToDictionaryJob-LOTKLJmain10140.89 ns2.840 ns2.656 ns142.01 ns136.03 ns144.31 ns1.00Base0.000.0433--440 B1.00
ToDictionaryJob-QETJTAPR10141.67 ns2.772 ns3.081 ns142.03 ns135.25 ns146.24 ns1.01Same0.040.0435--440 B1.00
ToImmutableDictionaryJob-LOTKLJmain10915.37 ns21.383 ns24.625 ns916.86 ns868.44 ns952.28 ns1.00Base0.000.0712--736 B1.00
ToImmutableDictionaryJob-QETJTAPR101,064.95 ns20.525 ns21.961 ns1,068.78 ns1,016.33 ns1,093.58 ns1.16Slower0.030.0703--736 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10108.24 ns2.142 ns2.103 ns109.02 ns104.05 ns110.36 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR10108.94 ns2.030 ns1.899 ns109.56 ns104.94 ns111.03 ns1.01Same0.03----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10173.50 ns3.458 ns3.552 ns173.71 ns166.58 ns179.82 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10178.48 ns3.450 ns3.543 ns179.64 ns170.79 ns183.23 ns1.03Same0.03----NA
ToDictionaryJob-LOTKLJmain1001,039.01 ns21.634 ns24.913 ns1,042.85 ns984.15 ns1,069.44 ns1.00Base0.000.3080--3128 B1.00
ToDictionaryJob-QETJTAPR1001,001.41 ns15.964 ns14.152 ns1,006.79 ns967.14 ns1,018.20 ns0.96Faster0.020.3090--3128 B1.00
ToImmutableDictionaryJob-LOTKLJmain10013,437.35 ns262.918 ns258.220 ns13,528.59 ns12,911.66 ns13,739.11 ns1.00Base0.000.6020--6496 B1.00
ToImmutableDictionaryJob-QETJTAPR10013,385.87 ns263.227 ns281.650 ns13,508.10 ns12,705.08 ns13,655.47 ns1.00Same0.020.6111--6496 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain1001,270.08 ns24.849 ns24.405 ns1,274.04 ns1,228.82 ns1,306.02 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR1001,355.12 ns25.952 ns27.768 ns1,364.22 ns1,293.32 ns1,391.48 ns1.07Slower0.04----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain1002,051.36 ns40.157 ns44.634 ns2,051.86 ns1,952.25 ns2,141.73 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR1002,024.26 ns39.026 ns41.758 ns2,030.78 ns1,918.59 ns2,087.70 ns0.99Same0.04----NA
ToDictionaryJob-LOTKLJmain100010,196.84 ns251.988 ns290.190 ns10,254.18 ns9,696.03 ns10,705.83 ns1.00Base0.003.04480.2469-31016 B1.00
ToDictionaryJob-QETJTAPR10009,639.19 ns187.533 ns192.583 ns9,678.42 ns9,206.36 ns9,917.35 ns0.94Faster0.033.05450.2741-31016 B1.00
ToImmutableDictionaryJob-LOTKLJmain1000237,679.59 ns3,661.106 ns3,057.189 ns238,570.74 ns229,820.64 ns240,334.00 ns1.00Base0.005.68180.9470-64102 B1.00
ToImmutableDictionaryJob-QETJTAPR1000230,173.07 ns4,311.465 ns3,822.002 ns231,834.56 ns219,823.01 ns233,708.06 ns0.97Same0.026.34060.9058-64102 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain100015,714.41 ns299.767 ns294.411 ns15,796.10 ns15,092.75 ns16,233.26 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR100016,141.57 ns315.820 ns295.418 ns16,228.09 ns15,554.74 ns16,578.18 ns1.03Same0.02----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain100059,513.28 ns1,073.929 ns1,004.554 ns59,719.73 ns57,310.95 ns60,994.35 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR100059,640.00 ns1,154.325 ns1,079.756 ns60,059.92 ns57,502.75 ns60,990.41 ns1.00Same0.03---2 BNA
ToDictionaryJob-LOTKLJmain10000244,971.90 ns4,813.058 ns5,349.701 ns245,296.34 ns235,273.75 ns253,046.79 ns1.00Base0.0076.785776.785776.7857283073 B1.00
ToDictionaryJob-QETJTAPR10000246,629.63 ns7,293.248 ns8,106.426 ns243,601.12 ns233,309.42 ns263,366.79 ns1.01Same0.0376.492576.492576.4925283068 B1.00
ToImmutableDictionaryJob-LOTKLJmain100003,143,437.89 ns60,920.847 ns59,832.417 ns3,164,813.12 ns3,007,650.00 ns3,203,625.00 ns1.00Base0.0062.500025.0000-640105 B1.00
ToImmutableDictionaryJob-QETJTAPR100003,190,842.99 ns57,976.310 ns62,034.002 ns3,207,857.50 ns3,062,612.50 ns3,308,832.50 ns1.01Same0.0362.500025.0000-640105 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10000199,541.34 ns4,524.300 ns5,028.748 ns200,181.25 ns191,146.48 ns211,170.08 ns1.00Base0.00---1 B1.00
TryGetValue_True_DictionaryJob-QETJTAPR10000195,256.67 ns3,751.698 ns3,684.669 ns196,337.70 ns188,147.78 ns200,153.24 ns0.98Same0.04---5 B5.00
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10000884,652.17 ns15,963.048 ns14,150.826 ns891,639.79 ns856,336.67 ns898,688.75 ns1.00Base0.00---3 B1.00
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10000906,593.35 ns17,063.661 ns14,248.930 ns910,996.53 ns875,974.31 ns928,415.62 ns1.03Same0.03---3 B1.00

In certain cases performance becomes up to 2x slower, but this impacts cases where lookup is currently incorrect.

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid there seem to be a few failing unit tests from KeyAnalyzer.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis there's 2 parts to the reduced performance.

As the bug is causing us to use case sensitive comparison it's artificially faster than any correct version of the code thus it's not a fair comparison.

The second part is the cost of removing this optimization which boils down to case sensitivity of a a partial substring of up to 8 chars. To measure that change we can benchmark with #93986 which has a much larger diff but keeps the optimization.

@eiriktsarpaliseiriktsarpalis left a comment

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.

Thanks

@eiriktsarpaliseiriktsarpalis added this to the 9.0.0 milestone Nov 13, 2023
@eiriktsarpalis

Copy link
Copy Markdown
Member

Thank you for the help @andrewjsaid!

@eiriktsarpalis

Copy link
Copy Markdown
Member

/backport to release/8.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0: https://github.com/dotnet/runtime/actions/runs/6856200961

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis backporting to release/8.0 failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Add failing tests
Applying: Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
Applying: When hashing the entire string, case sensitivity of hash and equals should be the same
Applying: Address code review comments
Applying: Only ignore case insensitivity if entire string is ASCII non-letters
error: sha1 information is lacking or useless (src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj).
error: could not build fake ancestor
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0005 Only ignore case insensitivity if entire string is ASCII non-letters
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis an error occurred while backporting to release/8.0, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

Pleased to be of help!

eiriktsarpalis pushed a commit to eiriktsarpalis/runtime that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (dotnet#94667)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixesdotnet#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
eiriktsarpalis added a commit that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (#94667) (#94685)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixes#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
Co-authored-by: Andrew J Said <andrewjsaid@gmail.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 14, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FrozenDictionary is incorrectly case sensitive in some cases

3 participants

@andrewjsaid@stephentoub@eiriktsarpalis
, '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

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections - #94667

Merged
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation
Nov 13, 2023
Merged

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections#94667
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation

Conversation

@andrewjsaid

@andrewjsaidandrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
Contributor

See #93986 for full context

Fix#93974

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Collections labels Nov 13, 2023
@ghost

Copy link
Copy Markdown

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

Issue Details

See #93986 for full context

Author:andrewjsaid
Assignees:-
Labels:

area-System.Collections, community-contribution

Milestone:-

@eiriktsarpaliseiriktsarpalis left a comment

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.

To get a feel of potential perf regressions, would it be possible to share benchmarks comparing this to main in the impacted cases?

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis That would require a PR in the performance repo. Is it possible for somebody from the MS team to do that as I am pretty busy. If not I'll try find some time.

@stephentoubstephentoub left a comment

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.

Thanks. Do we have an understanding of what uses this might regress and by how much? We should make the change, regardless, but I'd like to understand the ramifications and what we should be looking at following-up on.

@andrewjsaid
andrewjsaidforce-pushed the frozen-collections-ordinal-ignore-case-remove-optimisation branch from c44f0a1 to c45a592CompareNovember 13, 2023 15:00
@andrewjsaid

andrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
ContributorAuthor

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

@stephentoub

Copy link
Copy Markdown
Member

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

Ok, thanks. As noted, we don't need to block on it. @eiriktsarpalis, if you have some time to get some quick numbers just to aid in our understanding of the impact and whether there's an important follow-up here, that'd be helpful.

@eiriktsarpalis

Copy link
Copy Markdown
Member

I tweaked the Perf_SubstringFrozenDictionary benchmark to use case insensitive comparison and here's what I got:

MethodJobToolchainCountMeanErrorStdDevMedianMinMaxRatioMannWhitney(3%)RatioSDGen0Gen1Gen2AllocatedAlloc Ratio
ToFrozenDictionaryJob-LOTKLJmain101,389.48 ns31.906 ns36.743 ns1,382.71 ns1,327.53 ns1,473.59 ns1.00Base0.000.1660--1720 B1.00
ToFrozenDictionaryJob-QETJTAPR101,393.58 ns27.624 ns31.812 ns1,399.03 ns1,323.29 ns1,453.86 ns1.00Same0.040.1708--1720 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain1054.00 ns1.288 ns1.322 ns54.13 ns51.35 ns55.60 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1073.24 ns1.078 ns1.008 ns73.38 ns70.06 ns74.24 ns1.35Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain1008,446.66 ns158.800 ns163.076 ns8,471.45 ns8,134.23 ns8,717.36 ns1.00Base0.001.1780--12112 B1.00
ToFrozenDictionaryJob-QETJTAPR1008,771.04 ns237.326 ns273.305 ns8,836.49 ns8,160.74 ns9,217.86 ns1.04Same0.031.2019--12112 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100563.85 ns10.168 ns9.014 ns567.79 ns547.12 ns576.15 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1001,006.73 ns15.021 ns14.050 ns1,006.87 ns985.24 ns1,036.20 ns1.79Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain100074,531.61 ns1,469.417 ns1,508.983 ns74,819.41 ns71,279.16 ns77,269.59 ns1.00Base0.009.27031.1962-94864 B1.00
ToFrozenDictionaryJob-QETJTAPR100072,473.19 ns1,866.260 ns2,149.188 ns73,203.68 ns68,539.46 ns75,121.58 ns0.98Same0.039.22621.1905-94864 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100048,575.95 ns935.546 ns918.831 ns48,985.45 ns46,654.11 ns50,021.83 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR100050,132.24 ns976.166 ns958.725 ns50,439.63 ns47,947.67 ns51,144.53 ns1.03Same0.03----NA
ToFrozenDictionaryJob-LOTKLJmain100001,367,777.25 ns31,636.638 ns32,488.498 ns1,378,004.69 ns1,300,021.88 ns1,428,109.38 ns1.00Base0.00148.4375148.4375148.4375926138 B1.00
ToFrozenDictionaryJob-QETJTAPR100001,361,651.79 ns26,136.206 ns25,669.249 ns1,371,019.27 ns1,298,396.35 ns1,389,535.42 ns1.00Same0.03145.8333145.8333145.8333926134 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain10000429,049.49 ns7,951.179 ns7,048.514 ns430,354.73 ns412,395.95 ns438,879.39 ns1.00Base0.00---1 B1.00
TryGetValue_True_FrozenDictionaryJob-QETJTAPR10000433,232.35 ns7,438.534 ns6,958.009 ns435,446.22 ns419,605.92 ns442,993.75 ns1.01Same0.03---11 B11.00
ToDictionaryJob-LOTKLJmain10140.89 ns2.840 ns2.656 ns142.01 ns136.03 ns144.31 ns1.00Base0.000.0433--440 B1.00
ToDictionaryJob-QETJTAPR10141.67 ns2.772 ns3.081 ns142.03 ns135.25 ns146.24 ns1.01Same0.040.0435--440 B1.00
ToImmutableDictionaryJob-LOTKLJmain10915.37 ns21.383 ns24.625 ns916.86 ns868.44 ns952.28 ns1.00Base0.000.0712--736 B1.00
ToImmutableDictionaryJob-QETJTAPR101,064.95 ns20.525 ns21.961 ns1,068.78 ns1,016.33 ns1,093.58 ns1.16Slower0.030.0703--736 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10108.24 ns2.142 ns2.103 ns109.02 ns104.05 ns110.36 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR10108.94 ns2.030 ns1.899 ns109.56 ns104.94 ns111.03 ns1.01Same0.03----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10173.50 ns3.458 ns3.552 ns173.71 ns166.58 ns179.82 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10178.48 ns3.450 ns3.543 ns179.64 ns170.79 ns183.23 ns1.03Same0.03----NA
ToDictionaryJob-LOTKLJmain1001,039.01 ns21.634 ns24.913 ns1,042.85 ns984.15 ns1,069.44 ns1.00Base0.000.3080--3128 B1.00
ToDictionaryJob-QETJTAPR1001,001.41 ns15.964 ns14.152 ns1,006.79 ns967.14 ns1,018.20 ns0.96Faster0.020.3090--3128 B1.00
ToImmutableDictionaryJob-LOTKLJmain10013,437.35 ns262.918 ns258.220 ns13,528.59 ns12,911.66 ns13,739.11 ns1.00Base0.000.6020--6496 B1.00
ToImmutableDictionaryJob-QETJTAPR10013,385.87 ns263.227 ns281.650 ns13,508.10 ns12,705.08 ns13,655.47 ns1.00Same0.020.6111--6496 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain1001,270.08 ns24.849 ns24.405 ns1,274.04 ns1,228.82 ns1,306.02 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR1001,355.12 ns25.952 ns27.768 ns1,364.22 ns1,293.32 ns1,391.48 ns1.07Slower0.04----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain1002,051.36 ns40.157 ns44.634 ns2,051.86 ns1,952.25 ns2,141.73 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR1002,024.26 ns39.026 ns41.758 ns2,030.78 ns1,918.59 ns2,087.70 ns0.99Same0.04----NA
ToDictionaryJob-LOTKLJmain100010,196.84 ns251.988 ns290.190 ns10,254.18 ns9,696.03 ns10,705.83 ns1.00Base0.003.04480.2469-31016 B1.00
ToDictionaryJob-QETJTAPR10009,639.19 ns187.533 ns192.583 ns9,678.42 ns9,206.36 ns9,917.35 ns0.94Faster0.033.05450.2741-31016 B1.00
ToImmutableDictionaryJob-LOTKLJmain1000237,679.59 ns3,661.106 ns3,057.189 ns238,570.74 ns229,820.64 ns240,334.00 ns1.00Base0.005.68180.9470-64102 B1.00
ToImmutableDictionaryJob-QETJTAPR1000230,173.07 ns4,311.465 ns3,822.002 ns231,834.56 ns219,823.01 ns233,708.06 ns0.97Same0.026.34060.9058-64102 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain100015,714.41 ns299.767 ns294.411 ns15,796.10 ns15,092.75 ns16,233.26 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR100016,141.57 ns315.820 ns295.418 ns16,228.09 ns15,554.74 ns16,578.18 ns1.03Same0.02----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain100059,513.28 ns1,073.929 ns1,004.554 ns59,719.73 ns57,310.95 ns60,994.35 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR100059,640.00 ns1,154.325 ns1,079.756 ns60,059.92 ns57,502.75 ns60,990.41 ns1.00Same0.03---2 BNA
ToDictionaryJob-LOTKLJmain10000244,971.90 ns4,813.058 ns5,349.701 ns245,296.34 ns235,273.75 ns253,046.79 ns1.00Base0.0076.785776.785776.7857283073 B1.00
ToDictionaryJob-QETJTAPR10000246,629.63 ns7,293.248 ns8,106.426 ns243,601.12 ns233,309.42 ns263,366.79 ns1.01Same0.0376.492576.492576.4925283068 B1.00
ToImmutableDictionaryJob-LOTKLJmain100003,143,437.89 ns60,920.847 ns59,832.417 ns3,164,813.12 ns3,007,650.00 ns3,203,625.00 ns1.00Base0.0062.500025.0000-640105 B1.00
ToImmutableDictionaryJob-QETJTAPR100003,190,842.99 ns57,976.310 ns62,034.002 ns3,207,857.50 ns3,062,612.50 ns3,308,832.50 ns1.01Same0.0362.500025.0000-640105 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10000199,541.34 ns4,524.300 ns5,028.748 ns200,181.25 ns191,146.48 ns211,170.08 ns1.00Base0.00---1 B1.00
TryGetValue_True_DictionaryJob-QETJTAPR10000195,256.67 ns3,751.698 ns3,684.669 ns196,337.70 ns188,147.78 ns200,153.24 ns0.98Same0.04---5 B5.00
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10000884,652.17 ns15,963.048 ns14,150.826 ns891,639.79 ns856,336.67 ns898,688.75 ns1.00Base0.00---3 B1.00
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10000906,593.35 ns17,063.661 ns14,248.930 ns910,996.53 ns875,974.31 ns928,415.62 ns1.03Same0.03---3 B1.00

In certain cases performance becomes up to 2x slower, but this impacts cases where lookup is currently incorrect.

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid there seem to be a few failing unit tests from KeyAnalyzer.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis there's 2 parts to the reduced performance.

As the bug is causing us to use case sensitive comparison it's artificially faster than any correct version of the code thus it's not a fair comparison.

The second part is the cost of removing this optimization which boils down to case sensitivity of a a partial substring of up to 8 chars. To measure that change we can benchmark with #93986 which has a much larger diff but keeps the optimization.

@eiriktsarpaliseiriktsarpalis left a comment

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.

Thanks

@eiriktsarpaliseiriktsarpalis added this to the 9.0.0 milestone Nov 13, 2023
@eiriktsarpalis

Copy link
Copy Markdown
Member

Thank you for the help @andrewjsaid!

@eiriktsarpalis

Copy link
Copy Markdown
Member

/backport to release/8.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0: https://github.com/dotnet/runtime/actions/runs/6856200961

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis backporting to release/8.0 failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Add failing tests
Applying: Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
Applying: When hashing the entire string, case sensitivity of hash and equals should be the same
Applying: Address code review comments
Applying: Only ignore case insensitivity if entire string is ASCII non-letters
error: sha1 information is lacking or useless (src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj).
error: could not build fake ancestor
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0005 Only ignore case insensitivity if entire string is ASCII non-letters
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis an error occurred while backporting to release/8.0, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

Pleased to be of help!

eiriktsarpalis pushed a commit to eiriktsarpalis/runtime that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (dotnet#94667)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixesdotnet#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
eiriktsarpalis added a commit that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (#94667) (#94685)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixes#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
Co-authored-by: Andrew J Said <andrewjsaid@gmail.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 14, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FrozenDictionary is incorrectly case sensitive in some cases

3 participants

@andrewjsaid@stephentoub@eiriktsarpalis
, '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

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections - #94667

Merged
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation
Nov 13, 2023
Merged

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections#94667
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation

Conversation

@andrewjsaid

@andrewjsaidandrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
Contributor

See #93986 for full context

Fix#93974

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Collections labels Nov 13, 2023
@ghost

Copy link
Copy Markdown

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

Issue Details

See #93986 for full context

Author:andrewjsaid
Assignees:-
Labels:

area-System.Collections, community-contribution

Milestone:-

@eiriktsarpaliseiriktsarpalis left a comment

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.

To get a feel of potential perf regressions, would it be possible to share benchmarks comparing this to main in the impacted cases?

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis That would require a PR in the performance repo. Is it possible for somebody from the MS team to do that as I am pretty busy. If not I'll try find some time.

@stephentoubstephentoub left a comment

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.

Thanks. Do we have an understanding of what uses this might regress and by how much? We should make the change, regardless, but I'd like to understand the ramifications and what we should be looking at following-up on.

@andrewjsaid
andrewjsaidforce-pushed the frozen-collections-ordinal-ignore-case-remove-optimisation branch from c44f0a1 to c45a592CompareNovember 13, 2023 15:00
@andrewjsaid

andrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
ContributorAuthor

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

@stephentoub

Copy link
Copy Markdown
Member

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

Ok, thanks. As noted, we don't need to block on it. @eiriktsarpalis, if you have some time to get some quick numbers just to aid in our understanding of the impact and whether there's an important follow-up here, that'd be helpful.

@eiriktsarpalis

Copy link
Copy Markdown
Member

I tweaked the Perf_SubstringFrozenDictionary benchmark to use case insensitive comparison and here's what I got:

MethodJobToolchainCountMeanErrorStdDevMedianMinMaxRatioMannWhitney(3%)RatioSDGen0Gen1Gen2AllocatedAlloc Ratio
ToFrozenDictionaryJob-LOTKLJmain101,389.48 ns31.906 ns36.743 ns1,382.71 ns1,327.53 ns1,473.59 ns1.00Base0.000.1660--1720 B1.00
ToFrozenDictionaryJob-QETJTAPR101,393.58 ns27.624 ns31.812 ns1,399.03 ns1,323.29 ns1,453.86 ns1.00Same0.040.1708--1720 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain1054.00 ns1.288 ns1.322 ns54.13 ns51.35 ns55.60 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1073.24 ns1.078 ns1.008 ns73.38 ns70.06 ns74.24 ns1.35Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain1008,446.66 ns158.800 ns163.076 ns8,471.45 ns8,134.23 ns8,717.36 ns1.00Base0.001.1780--12112 B1.00
ToFrozenDictionaryJob-QETJTAPR1008,771.04 ns237.326 ns273.305 ns8,836.49 ns8,160.74 ns9,217.86 ns1.04Same0.031.2019--12112 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100563.85 ns10.168 ns9.014 ns567.79 ns547.12 ns576.15 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1001,006.73 ns15.021 ns14.050 ns1,006.87 ns985.24 ns1,036.20 ns1.79Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain100074,531.61 ns1,469.417 ns1,508.983 ns74,819.41 ns71,279.16 ns77,269.59 ns1.00Base0.009.27031.1962-94864 B1.00
ToFrozenDictionaryJob-QETJTAPR100072,473.19 ns1,866.260 ns2,149.188 ns73,203.68 ns68,539.46 ns75,121.58 ns0.98Same0.039.22621.1905-94864 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100048,575.95 ns935.546 ns918.831 ns48,985.45 ns46,654.11 ns50,021.83 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR100050,132.24 ns976.166 ns958.725 ns50,439.63 ns47,947.67 ns51,144.53 ns1.03Same0.03----NA
ToFrozenDictionaryJob-LOTKLJmain100001,367,777.25 ns31,636.638 ns32,488.498 ns1,378,004.69 ns1,300,021.88 ns1,428,109.38 ns1.00Base0.00148.4375148.4375148.4375926138 B1.00
ToFrozenDictionaryJob-QETJTAPR100001,361,651.79 ns26,136.206 ns25,669.249 ns1,371,019.27 ns1,298,396.35 ns1,389,535.42 ns1.00Same0.03145.8333145.8333145.8333926134 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain10000429,049.49 ns7,951.179 ns7,048.514 ns430,354.73 ns412,395.95 ns438,879.39 ns1.00Base0.00---1 B1.00
TryGetValue_True_FrozenDictionaryJob-QETJTAPR10000433,232.35 ns7,438.534 ns6,958.009 ns435,446.22 ns419,605.92 ns442,993.75 ns1.01Same0.03---11 B11.00
ToDictionaryJob-LOTKLJmain10140.89 ns2.840 ns2.656 ns142.01 ns136.03 ns144.31 ns1.00Base0.000.0433--440 B1.00
ToDictionaryJob-QETJTAPR10141.67 ns2.772 ns3.081 ns142.03 ns135.25 ns146.24 ns1.01Same0.040.0435--440 B1.00
ToImmutableDictionaryJob-LOTKLJmain10915.37 ns21.383 ns24.625 ns916.86 ns868.44 ns952.28 ns1.00Base0.000.0712--736 B1.00
ToImmutableDictionaryJob-QETJTAPR101,064.95 ns20.525 ns21.961 ns1,068.78 ns1,016.33 ns1,093.58 ns1.16Slower0.030.0703--736 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10108.24 ns2.142 ns2.103 ns109.02 ns104.05 ns110.36 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR10108.94 ns2.030 ns1.899 ns109.56 ns104.94 ns111.03 ns1.01Same0.03----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10173.50 ns3.458 ns3.552 ns173.71 ns166.58 ns179.82 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10178.48 ns3.450 ns3.543 ns179.64 ns170.79 ns183.23 ns1.03Same0.03----NA
ToDictionaryJob-LOTKLJmain1001,039.01 ns21.634 ns24.913 ns1,042.85 ns984.15 ns1,069.44 ns1.00Base0.000.3080--3128 B1.00
ToDictionaryJob-QETJTAPR1001,001.41 ns15.964 ns14.152 ns1,006.79 ns967.14 ns1,018.20 ns0.96Faster0.020.3090--3128 B1.00
ToImmutableDictionaryJob-LOTKLJmain10013,437.35 ns262.918 ns258.220 ns13,528.59 ns12,911.66 ns13,739.11 ns1.00Base0.000.6020--6496 B1.00
ToImmutableDictionaryJob-QETJTAPR10013,385.87 ns263.227 ns281.650 ns13,508.10 ns12,705.08 ns13,655.47 ns1.00Same0.020.6111--6496 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain1001,270.08 ns24.849 ns24.405 ns1,274.04 ns1,228.82 ns1,306.02 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR1001,355.12 ns25.952 ns27.768 ns1,364.22 ns1,293.32 ns1,391.48 ns1.07Slower0.04----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain1002,051.36 ns40.157 ns44.634 ns2,051.86 ns1,952.25 ns2,141.73 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR1002,024.26 ns39.026 ns41.758 ns2,030.78 ns1,918.59 ns2,087.70 ns0.99Same0.04----NA
ToDictionaryJob-LOTKLJmain100010,196.84 ns251.988 ns290.190 ns10,254.18 ns9,696.03 ns10,705.83 ns1.00Base0.003.04480.2469-31016 B1.00
ToDictionaryJob-QETJTAPR10009,639.19 ns187.533 ns192.583 ns9,678.42 ns9,206.36 ns9,917.35 ns0.94Faster0.033.05450.2741-31016 B1.00
ToImmutableDictionaryJob-LOTKLJmain1000237,679.59 ns3,661.106 ns3,057.189 ns238,570.74 ns229,820.64 ns240,334.00 ns1.00Base0.005.68180.9470-64102 B1.00
ToImmutableDictionaryJob-QETJTAPR1000230,173.07 ns4,311.465 ns3,822.002 ns231,834.56 ns219,823.01 ns233,708.06 ns0.97Same0.026.34060.9058-64102 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain100015,714.41 ns299.767 ns294.411 ns15,796.10 ns15,092.75 ns16,233.26 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR100016,141.57 ns315.820 ns295.418 ns16,228.09 ns15,554.74 ns16,578.18 ns1.03Same0.02----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain100059,513.28 ns1,073.929 ns1,004.554 ns59,719.73 ns57,310.95 ns60,994.35 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR100059,640.00 ns1,154.325 ns1,079.756 ns60,059.92 ns57,502.75 ns60,990.41 ns1.00Same0.03---2 BNA
ToDictionaryJob-LOTKLJmain10000244,971.90 ns4,813.058 ns5,349.701 ns245,296.34 ns235,273.75 ns253,046.79 ns1.00Base0.0076.785776.785776.7857283073 B1.00
ToDictionaryJob-QETJTAPR10000246,629.63 ns7,293.248 ns8,106.426 ns243,601.12 ns233,309.42 ns263,366.79 ns1.01Same0.0376.492576.492576.4925283068 B1.00
ToImmutableDictionaryJob-LOTKLJmain100003,143,437.89 ns60,920.847 ns59,832.417 ns3,164,813.12 ns3,007,650.00 ns3,203,625.00 ns1.00Base0.0062.500025.0000-640105 B1.00
ToImmutableDictionaryJob-QETJTAPR100003,190,842.99 ns57,976.310 ns62,034.002 ns3,207,857.50 ns3,062,612.50 ns3,308,832.50 ns1.01Same0.0362.500025.0000-640105 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10000199,541.34 ns4,524.300 ns5,028.748 ns200,181.25 ns191,146.48 ns211,170.08 ns1.00Base0.00---1 B1.00
TryGetValue_True_DictionaryJob-QETJTAPR10000195,256.67 ns3,751.698 ns3,684.669 ns196,337.70 ns188,147.78 ns200,153.24 ns0.98Same0.04---5 B5.00
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10000884,652.17 ns15,963.048 ns14,150.826 ns891,639.79 ns856,336.67 ns898,688.75 ns1.00Base0.00---3 B1.00
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10000906,593.35 ns17,063.661 ns14,248.930 ns910,996.53 ns875,974.31 ns928,415.62 ns1.03Same0.03---3 B1.00

In certain cases performance becomes up to 2x slower, but this impacts cases where lookup is currently incorrect.

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid there seem to be a few failing unit tests from KeyAnalyzer.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis there's 2 parts to the reduced performance.

As the bug is causing us to use case sensitive comparison it's artificially faster than any correct version of the code thus it's not a fair comparison.

The second part is the cost of removing this optimization which boils down to case sensitivity of a a partial substring of up to 8 chars. To measure that change we can benchmark with #93986 which has a much larger diff but keeps the optimization.

@eiriktsarpaliseiriktsarpalis left a comment

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.

Thanks

@eiriktsarpaliseiriktsarpalis added this to the 9.0.0 milestone Nov 13, 2023
@eiriktsarpalis

Copy link
Copy Markdown
Member

Thank you for the help @andrewjsaid!

@eiriktsarpalis

Copy link
Copy Markdown
Member

/backport to release/8.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0: https://github.com/dotnet/runtime/actions/runs/6856200961

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis backporting to release/8.0 failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Add failing tests
Applying: Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
Applying: When hashing the entire string, case sensitivity of hash and equals should be the same
Applying: Address code review comments
Applying: Only ignore case insensitivity if entire string is ASCII non-letters
error: sha1 information is lacking or useless (src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj).
error: could not build fake ancestor
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0005 Only ignore case insensitivity if entire string is ASCII non-letters
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis an error occurred while backporting to release/8.0, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

Pleased to be of help!

eiriktsarpalis pushed a commit to eiriktsarpalis/runtime that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (dotnet#94667)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixesdotnet#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
eiriktsarpalis added a commit that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (#94667) (#94685)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixes#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
Co-authored-by: Andrew J Said <andrewjsaid@gmail.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 14, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FrozenDictionary is incorrectly case sensitive in some cases

3 participants

@andrewjsaid@stephentoub@eiriktsarpalis
, '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

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections - #94667

Merged
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation
Nov 13, 2023
Merged

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections#94667
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation

Conversation

@andrewjsaid

@andrewjsaidandrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
Contributor

See #93986 for full context

Fix#93974

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Collections labels Nov 13, 2023
@ghost

Copy link
Copy Markdown

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

Issue Details

See #93986 for full context

Author:andrewjsaid
Assignees:-
Labels:

area-System.Collections, community-contribution

Milestone:-

@eiriktsarpaliseiriktsarpalis left a comment

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.

To get a feel of potential perf regressions, would it be possible to share benchmarks comparing this to main in the impacted cases?

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis That would require a PR in the performance repo. Is it possible for somebody from the MS team to do that as I am pretty busy. If not I'll try find some time.

@stephentoubstephentoub left a comment

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.

Thanks. Do we have an understanding of what uses this might regress and by how much? We should make the change, regardless, but I'd like to understand the ramifications and what we should be looking at following-up on.

@andrewjsaid
andrewjsaidforce-pushed the frozen-collections-ordinal-ignore-case-remove-optimisation branch from c44f0a1 to c45a592CompareNovember 13, 2023 15:00
@andrewjsaid

andrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
ContributorAuthor

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

@stephentoub

Copy link
Copy Markdown
Member

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

Ok, thanks. As noted, we don't need to block on it. @eiriktsarpalis, if you have some time to get some quick numbers just to aid in our understanding of the impact and whether there's an important follow-up here, that'd be helpful.

@eiriktsarpalis

Copy link
Copy Markdown
Member

I tweaked the Perf_SubstringFrozenDictionary benchmark to use case insensitive comparison and here's what I got:

MethodJobToolchainCountMeanErrorStdDevMedianMinMaxRatioMannWhitney(3%)RatioSDGen0Gen1Gen2AllocatedAlloc Ratio
ToFrozenDictionaryJob-LOTKLJmain101,389.48 ns31.906 ns36.743 ns1,382.71 ns1,327.53 ns1,473.59 ns1.00Base0.000.1660--1720 B1.00
ToFrozenDictionaryJob-QETJTAPR101,393.58 ns27.624 ns31.812 ns1,399.03 ns1,323.29 ns1,453.86 ns1.00Same0.040.1708--1720 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain1054.00 ns1.288 ns1.322 ns54.13 ns51.35 ns55.60 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1073.24 ns1.078 ns1.008 ns73.38 ns70.06 ns74.24 ns1.35Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain1008,446.66 ns158.800 ns163.076 ns8,471.45 ns8,134.23 ns8,717.36 ns1.00Base0.001.1780--12112 B1.00
ToFrozenDictionaryJob-QETJTAPR1008,771.04 ns237.326 ns273.305 ns8,836.49 ns8,160.74 ns9,217.86 ns1.04Same0.031.2019--12112 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100563.85 ns10.168 ns9.014 ns567.79 ns547.12 ns576.15 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1001,006.73 ns15.021 ns14.050 ns1,006.87 ns985.24 ns1,036.20 ns1.79Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain100074,531.61 ns1,469.417 ns1,508.983 ns74,819.41 ns71,279.16 ns77,269.59 ns1.00Base0.009.27031.1962-94864 B1.00
ToFrozenDictionaryJob-QETJTAPR100072,473.19 ns1,866.260 ns2,149.188 ns73,203.68 ns68,539.46 ns75,121.58 ns0.98Same0.039.22621.1905-94864 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100048,575.95 ns935.546 ns918.831 ns48,985.45 ns46,654.11 ns50,021.83 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR100050,132.24 ns976.166 ns958.725 ns50,439.63 ns47,947.67 ns51,144.53 ns1.03Same0.03----NA
ToFrozenDictionaryJob-LOTKLJmain100001,367,777.25 ns31,636.638 ns32,488.498 ns1,378,004.69 ns1,300,021.88 ns1,428,109.38 ns1.00Base0.00148.4375148.4375148.4375926138 B1.00
ToFrozenDictionaryJob-QETJTAPR100001,361,651.79 ns26,136.206 ns25,669.249 ns1,371,019.27 ns1,298,396.35 ns1,389,535.42 ns1.00Same0.03145.8333145.8333145.8333926134 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain10000429,049.49 ns7,951.179 ns7,048.514 ns430,354.73 ns412,395.95 ns438,879.39 ns1.00Base0.00---1 B1.00
TryGetValue_True_FrozenDictionaryJob-QETJTAPR10000433,232.35 ns7,438.534 ns6,958.009 ns435,446.22 ns419,605.92 ns442,993.75 ns1.01Same0.03---11 B11.00
ToDictionaryJob-LOTKLJmain10140.89 ns2.840 ns2.656 ns142.01 ns136.03 ns144.31 ns1.00Base0.000.0433--440 B1.00
ToDictionaryJob-QETJTAPR10141.67 ns2.772 ns3.081 ns142.03 ns135.25 ns146.24 ns1.01Same0.040.0435--440 B1.00
ToImmutableDictionaryJob-LOTKLJmain10915.37 ns21.383 ns24.625 ns916.86 ns868.44 ns952.28 ns1.00Base0.000.0712--736 B1.00
ToImmutableDictionaryJob-QETJTAPR101,064.95 ns20.525 ns21.961 ns1,068.78 ns1,016.33 ns1,093.58 ns1.16Slower0.030.0703--736 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10108.24 ns2.142 ns2.103 ns109.02 ns104.05 ns110.36 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR10108.94 ns2.030 ns1.899 ns109.56 ns104.94 ns111.03 ns1.01Same0.03----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10173.50 ns3.458 ns3.552 ns173.71 ns166.58 ns179.82 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10178.48 ns3.450 ns3.543 ns179.64 ns170.79 ns183.23 ns1.03Same0.03----NA
ToDictionaryJob-LOTKLJmain1001,039.01 ns21.634 ns24.913 ns1,042.85 ns984.15 ns1,069.44 ns1.00Base0.000.3080--3128 B1.00
ToDictionaryJob-QETJTAPR1001,001.41 ns15.964 ns14.152 ns1,006.79 ns967.14 ns1,018.20 ns0.96Faster0.020.3090--3128 B1.00
ToImmutableDictionaryJob-LOTKLJmain10013,437.35 ns262.918 ns258.220 ns13,528.59 ns12,911.66 ns13,739.11 ns1.00Base0.000.6020--6496 B1.00
ToImmutableDictionaryJob-QETJTAPR10013,385.87 ns263.227 ns281.650 ns13,508.10 ns12,705.08 ns13,655.47 ns1.00Same0.020.6111--6496 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain1001,270.08 ns24.849 ns24.405 ns1,274.04 ns1,228.82 ns1,306.02 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR1001,355.12 ns25.952 ns27.768 ns1,364.22 ns1,293.32 ns1,391.48 ns1.07Slower0.04----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain1002,051.36 ns40.157 ns44.634 ns2,051.86 ns1,952.25 ns2,141.73 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR1002,024.26 ns39.026 ns41.758 ns2,030.78 ns1,918.59 ns2,087.70 ns0.99Same0.04----NA
ToDictionaryJob-LOTKLJmain100010,196.84 ns251.988 ns290.190 ns10,254.18 ns9,696.03 ns10,705.83 ns1.00Base0.003.04480.2469-31016 B1.00
ToDictionaryJob-QETJTAPR10009,639.19 ns187.533 ns192.583 ns9,678.42 ns9,206.36 ns9,917.35 ns0.94Faster0.033.05450.2741-31016 B1.00
ToImmutableDictionaryJob-LOTKLJmain1000237,679.59 ns3,661.106 ns3,057.189 ns238,570.74 ns229,820.64 ns240,334.00 ns1.00Base0.005.68180.9470-64102 B1.00
ToImmutableDictionaryJob-QETJTAPR1000230,173.07 ns4,311.465 ns3,822.002 ns231,834.56 ns219,823.01 ns233,708.06 ns0.97Same0.026.34060.9058-64102 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain100015,714.41 ns299.767 ns294.411 ns15,796.10 ns15,092.75 ns16,233.26 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR100016,141.57 ns315.820 ns295.418 ns16,228.09 ns15,554.74 ns16,578.18 ns1.03Same0.02----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain100059,513.28 ns1,073.929 ns1,004.554 ns59,719.73 ns57,310.95 ns60,994.35 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR100059,640.00 ns1,154.325 ns1,079.756 ns60,059.92 ns57,502.75 ns60,990.41 ns1.00Same0.03---2 BNA
ToDictionaryJob-LOTKLJmain10000244,971.90 ns4,813.058 ns5,349.701 ns245,296.34 ns235,273.75 ns253,046.79 ns1.00Base0.0076.785776.785776.7857283073 B1.00
ToDictionaryJob-QETJTAPR10000246,629.63 ns7,293.248 ns8,106.426 ns243,601.12 ns233,309.42 ns263,366.79 ns1.01Same0.0376.492576.492576.4925283068 B1.00
ToImmutableDictionaryJob-LOTKLJmain100003,143,437.89 ns60,920.847 ns59,832.417 ns3,164,813.12 ns3,007,650.00 ns3,203,625.00 ns1.00Base0.0062.500025.0000-640105 B1.00
ToImmutableDictionaryJob-QETJTAPR100003,190,842.99 ns57,976.310 ns62,034.002 ns3,207,857.50 ns3,062,612.50 ns3,308,832.50 ns1.01Same0.0362.500025.0000-640105 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10000199,541.34 ns4,524.300 ns5,028.748 ns200,181.25 ns191,146.48 ns211,170.08 ns1.00Base0.00---1 B1.00
TryGetValue_True_DictionaryJob-QETJTAPR10000195,256.67 ns3,751.698 ns3,684.669 ns196,337.70 ns188,147.78 ns200,153.24 ns0.98Same0.04---5 B5.00
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10000884,652.17 ns15,963.048 ns14,150.826 ns891,639.79 ns856,336.67 ns898,688.75 ns1.00Base0.00---3 B1.00
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10000906,593.35 ns17,063.661 ns14,248.930 ns910,996.53 ns875,974.31 ns928,415.62 ns1.03Same0.03---3 B1.00

In certain cases performance becomes up to 2x slower, but this impacts cases where lookup is currently incorrect.

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid there seem to be a few failing unit tests from KeyAnalyzer.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis there's 2 parts to the reduced performance.

As the bug is causing us to use case sensitive comparison it's artificially faster than any correct version of the code thus it's not a fair comparison.

The second part is the cost of removing this optimization which boils down to case sensitivity of a a partial substring of up to 8 chars. To measure that change we can benchmark with #93986 which has a much larger diff but keeps the optimization.

@eiriktsarpaliseiriktsarpalis left a comment

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.

Thanks

@eiriktsarpaliseiriktsarpalis added this to the 9.0.0 milestone Nov 13, 2023
@eiriktsarpalis

Copy link
Copy Markdown
Member

Thank you for the help @andrewjsaid!

@eiriktsarpalis

Copy link
Copy Markdown
Member

/backport to release/8.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0: https://github.com/dotnet/runtime/actions/runs/6856200961

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis backporting to release/8.0 failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Add failing tests
Applying: Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
Applying: When hashing the entire string, case sensitivity of hash and equals should be the same
Applying: Address code review comments
Applying: Only ignore case insensitivity if entire string is ASCII non-letters
error: sha1 information is lacking or useless (src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj).
error: could not build fake ancestor
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0005 Only ignore case insensitivity if entire string is ASCII non-letters
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis an error occurred while backporting to release/8.0, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

Pleased to be of help!

eiriktsarpalis pushed a commit to eiriktsarpalis/runtime that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (dotnet#94667)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixesdotnet#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
eiriktsarpalis added a commit that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (#94667) (#94685)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixes#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
Co-authored-by: Andrew J Said <andrewjsaid@gmail.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 14, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FrozenDictionary is incorrectly case sensitive in some cases

3 participants

@andrewjsaid@stephentoub@eiriktsarpalis
, '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

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections - #94667

Merged
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation
Nov 13, 2023
Merged

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections#94667
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation

Conversation

@andrewjsaid

@andrewjsaidandrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
Contributor

See #93986 for full context

Fix#93974

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Collections labels Nov 13, 2023
@ghost

Copy link
Copy Markdown

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

Issue Details

See #93986 for full context

Author:andrewjsaid
Assignees:-
Labels:

area-System.Collections, community-contribution

Milestone:-

@eiriktsarpaliseiriktsarpalis left a comment

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.

To get a feel of potential perf regressions, would it be possible to share benchmarks comparing this to main in the impacted cases?

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis That would require a PR in the performance repo. Is it possible for somebody from the MS team to do that as I am pretty busy. If not I'll try find some time.

@stephentoubstephentoub left a comment

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.

Thanks. Do we have an understanding of what uses this might regress and by how much? We should make the change, regardless, but I'd like to understand the ramifications and what we should be looking at following-up on.

@andrewjsaid
andrewjsaidforce-pushed the frozen-collections-ordinal-ignore-case-remove-optimisation branch from c44f0a1 to c45a592CompareNovember 13, 2023 15:00
@andrewjsaid

andrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
ContributorAuthor

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

@stephentoub

Copy link
Copy Markdown
Member

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

Ok, thanks. As noted, we don't need to block on it. @eiriktsarpalis, if you have some time to get some quick numbers just to aid in our understanding of the impact and whether there's an important follow-up here, that'd be helpful.

@eiriktsarpalis

Copy link
Copy Markdown
Member

I tweaked the Perf_SubstringFrozenDictionary benchmark to use case insensitive comparison and here's what I got:

MethodJobToolchainCountMeanErrorStdDevMedianMinMaxRatioMannWhitney(3%)RatioSDGen0Gen1Gen2AllocatedAlloc Ratio
ToFrozenDictionaryJob-LOTKLJmain101,389.48 ns31.906 ns36.743 ns1,382.71 ns1,327.53 ns1,473.59 ns1.00Base0.000.1660--1720 B1.00
ToFrozenDictionaryJob-QETJTAPR101,393.58 ns27.624 ns31.812 ns1,399.03 ns1,323.29 ns1,453.86 ns1.00Same0.040.1708--1720 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain1054.00 ns1.288 ns1.322 ns54.13 ns51.35 ns55.60 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1073.24 ns1.078 ns1.008 ns73.38 ns70.06 ns74.24 ns1.35Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain1008,446.66 ns158.800 ns163.076 ns8,471.45 ns8,134.23 ns8,717.36 ns1.00Base0.001.1780--12112 B1.00
ToFrozenDictionaryJob-QETJTAPR1008,771.04 ns237.326 ns273.305 ns8,836.49 ns8,160.74 ns9,217.86 ns1.04Same0.031.2019--12112 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100563.85 ns10.168 ns9.014 ns567.79 ns547.12 ns576.15 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1001,006.73 ns15.021 ns14.050 ns1,006.87 ns985.24 ns1,036.20 ns1.79Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain100074,531.61 ns1,469.417 ns1,508.983 ns74,819.41 ns71,279.16 ns77,269.59 ns1.00Base0.009.27031.1962-94864 B1.00
ToFrozenDictionaryJob-QETJTAPR100072,473.19 ns1,866.260 ns2,149.188 ns73,203.68 ns68,539.46 ns75,121.58 ns0.98Same0.039.22621.1905-94864 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100048,575.95 ns935.546 ns918.831 ns48,985.45 ns46,654.11 ns50,021.83 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR100050,132.24 ns976.166 ns958.725 ns50,439.63 ns47,947.67 ns51,144.53 ns1.03Same0.03----NA
ToFrozenDictionaryJob-LOTKLJmain100001,367,777.25 ns31,636.638 ns32,488.498 ns1,378,004.69 ns1,300,021.88 ns1,428,109.38 ns1.00Base0.00148.4375148.4375148.4375926138 B1.00
ToFrozenDictionaryJob-QETJTAPR100001,361,651.79 ns26,136.206 ns25,669.249 ns1,371,019.27 ns1,298,396.35 ns1,389,535.42 ns1.00Same0.03145.8333145.8333145.8333926134 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain10000429,049.49 ns7,951.179 ns7,048.514 ns430,354.73 ns412,395.95 ns438,879.39 ns1.00Base0.00---1 B1.00
TryGetValue_True_FrozenDictionaryJob-QETJTAPR10000433,232.35 ns7,438.534 ns6,958.009 ns435,446.22 ns419,605.92 ns442,993.75 ns1.01Same0.03---11 B11.00
ToDictionaryJob-LOTKLJmain10140.89 ns2.840 ns2.656 ns142.01 ns136.03 ns144.31 ns1.00Base0.000.0433--440 B1.00
ToDictionaryJob-QETJTAPR10141.67 ns2.772 ns3.081 ns142.03 ns135.25 ns146.24 ns1.01Same0.040.0435--440 B1.00
ToImmutableDictionaryJob-LOTKLJmain10915.37 ns21.383 ns24.625 ns916.86 ns868.44 ns952.28 ns1.00Base0.000.0712--736 B1.00
ToImmutableDictionaryJob-QETJTAPR101,064.95 ns20.525 ns21.961 ns1,068.78 ns1,016.33 ns1,093.58 ns1.16Slower0.030.0703--736 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10108.24 ns2.142 ns2.103 ns109.02 ns104.05 ns110.36 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR10108.94 ns2.030 ns1.899 ns109.56 ns104.94 ns111.03 ns1.01Same0.03----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10173.50 ns3.458 ns3.552 ns173.71 ns166.58 ns179.82 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10178.48 ns3.450 ns3.543 ns179.64 ns170.79 ns183.23 ns1.03Same0.03----NA
ToDictionaryJob-LOTKLJmain1001,039.01 ns21.634 ns24.913 ns1,042.85 ns984.15 ns1,069.44 ns1.00Base0.000.3080--3128 B1.00
ToDictionaryJob-QETJTAPR1001,001.41 ns15.964 ns14.152 ns1,006.79 ns967.14 ns1,018.20 ns0.96Faster0.020.3090--3128 B1.00
ToImmutableDictionaryJob-LOTKLJmain10013,437.35 ns262.918 ns258.220 ns13,528.59 ns12,911.66 ns13,739.11 ns1.00Base0.000.6020--6496 B1.00
ToImmutableDictionaryJob-QETJTAPR10013,385.87 ns263.227 ns281.650 ns13,508.10 ns12,705.08 ns13,655.47 ns1.00Same0.020.6111--6496 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain1001,270.08 ns24.849 ns24.405 ns1,274.04 ns1,228.82 ns1,306.02 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR1001,355.12 ns25.952 ns27.768 ns1,364.22 ns1,293.32 ns1,391.48 ns1.07Slower0.04----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain1002,051.36 ns40.157 ns44.634 ns2,051.86 ns1,952.25 ns2,141.73 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR1002,024.26 ns39.026 ns41.758 ns2,030.78 ns1,918.59 ns2,087.70 ns0.99Same0.04----NA
ToDictionaryJob-LOTKLJmain100010,196.84 ns251.988 ns290.190 ns10,254.18 ns9,696.03 ns10,705.83 ns1.00Base0.003.04480.2469-31016 B1.00
ToDictionaryJob-QETJTAPR10009,639.19 ns187.533 ns192.583 ns9,678.42 ns9,206.36 ns9,917.35 ns0.94Faster0.033.05450.2741-31016 B1.00
ToImmutableDictionaryJob-LOTKLJmain1000237,679.59 ns3,661.106 ns3,057.189 ns238,570.74 ns229,820.64 ns240,334.00 ns1.00Base0.005.68180.9470-64102 B1.00
ToImmutableDictionaryJob-QETJTAPR1000230,173.07 ns4,311.465 ns3,822.002 ns231,834.56 ns219,823.01 ns233,708.06 ns0.97Same0.026.34060.9058-64102 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain100015,714.41 ns299.767 ns294.411 ns15,796.10 ns15,092.75 ns16,233.26 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR100016,141.57 ns315.820 ns295.418 ns16,228.09 ns15,554.74 ns16,578.18 ns1.03Same0.02----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain100059,513.28 ns1,073.929 ns1,004.554 ns59,719.73 ns57,310.95 ns60,994.35 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR100059,640.00 ns1,154.325 ns1,079.756 ns60,059.92 ns57,502.75 ns60,990.41 ns1.00Same0.03---2 BNA
ToDictionaryJob-LOTKLJmain10000244,971.90 ns4,813.058 ns5,349.701 ns245,296.34 ns235,273.75 ns253,046.79 ns1.00Base0.0076.785776.785776.7857283073 B1.00
ToDictionaryJob-QETJTAPR10000246,629.63 ns7,293.248 ns8,106.426 ns243,601.12 ns233,309.42 ns263,366.79 ns1.01Same0.0376.492576.492576.4925283068 B1.00
ToImmutableDictionaryJob-LOTKLJmain100003,143,437.89 ns60,920.847 ns59,832.417 ns3,164,813.12 ns3,007,650.00 ns3,203,625.00 ns1.00Base0.0062.500025.0000-640105 B1.00
ToImmutableDictionaryJob-QETJTAPR100003,190,842.99 ns57,976.310 ns62,034.002 ns3,207,857.50 ns3,062,612.50 ns3,308,832.50 ns1.01Same0.0362.500025.0000-640105 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10000199,541.34 ns4,524.300 ns5,028.748 ns200,181.25 ns191,146.48 ns211,170.08 ns1.00Base0.00---1 B1.00
TryGetValue_True_DictionaryJob-QETJTAPR10000195,256.67 ns3,751.698 ns3,684.669 ns196,337.70 ns188,147.78 ns200,153.24 ns0.98Same0.04---5 B5.00
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10000884,652.17 ns15,963.048 ns14,150.826 ns891,639.79 ns856,336.67 ns898,688.75 ns1.00Base0.00---3 B1.00
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10000906,593.35 ns17,063.661 ns14,248.930 ns910,996.53 ns875,974.31 ns928,415.62 ns1.03Same0.03---3 B1.00

In certain cases performance becomes up to 2x slower, but this impacts cases where lookup is currently incorrect.

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid there seem to be a few failing unit tests from KeyAnalyzer.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis there's 2 parts to the reduced performance.

As the bug is causing us to use case sensitive comparison it's artificially faster than any correct version of the code thus it's not a fair comparison.

The second part is the cost of removing this optimization which boils down to case sensitivity of a a partial substring of up to 8 chars. To measure that change we can benchmark with #93986 which has a much larger diff but keeps the optimization.

@eiriktsarpaliseiriktsarpalis left a comment

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.

Thanks

@eiriktsarpaliseiriktsarpalis added this to the 9.0.0 milestone Nov 13, 2023
@eiriktsarpalis

Copy link
Copy Markdown
Member

Thank you for the help @andrewjsaid!

@eiriktsarpalis

Copy link
Copy Markdown
Member

/backport to release/8.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0: https://github.com/dotnet/runtime/actions/runs/6856200961

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis backporting to release/8.0 failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Add failing tests
Applying: Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
Applying: When hashing the entire string, case sensitivity of hash and equals should be the same
Applying: Address code review comments
Applying: Only ignore case insensitivity if entire string is ASCII non-letters
error: sha1 information is lacking or useless (src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj).
error: could not build fake ancestor
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0005 Only ignore case insensitivity if entire string is ASCII non-letters
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis an error occurred while backporting to release/8.0, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

Pleased to be of help!

eiriktsarpalis pushed a commit to eiriktsarpalis/runtime that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (dotnet#94667)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixesdotnet#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
eiriktsarpalis added a commit that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (#94667) (#94685)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixes#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
Co-authored-by: Andrew J Said <andrewjsaid@gmail.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 14, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FrozenDictionary is incorrectly case sensitive in some cases

3 participants

@andrewjsaid@stephentoub@eiriktsarpalis
, '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

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections - #94667

Merged
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation
Nov 13, 2023
Merged

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections#94667
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation

Conversation

@andrewjsaid

@andrewjsaidandrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
Contributor

See #93986 for full context

Fix#93974

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Collections labels Nov 13, 2023
@ghost

Copy link
Copy Markdown

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

Issue Details

See #93986 for full context

Author:andrewjsaid
Assignees:-
Labels:

area-System.Collections, community-contribution

Milestone:-

@eiriktsarpaliseiriktsarpalis left a comment

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.

To get a feel of potential perf regressions, would it be possible to share benchmarks comparing this to main in the impacted cases?

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis That would require a PR in the performance repo. Is it possible for somebody from the MS team to do that as I am pretty busy. If not I'll try find some time.

@stephentoubstephentoub left a comment

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.

Thanks. Do we have an understanding of what uses this might regress and by how much? We should make the change, regardless, but I'd like to understand the ramifications and what we should be looking at following-up on.

@andrewjsaid
andrewjsaidforce-pushed the frozen-collections-ordinal-ignore-case-remove-optimisation branch from c44f0a1 to c45a592CompareNovember 13, 2023 15:00
@andrewjsaid

andrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
ContributorAuthor

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

@stephentoub

Copy link
Copy Markdown
Member

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

Ok, thanks. As noted, we don't need to block on it. @eiriktsarpalis, if you have some time to get some quick numbers just to aid in our understanding of the impact and whether there's an important follow-up here, that'd be helpful.

@eiriktsarpalis

Copy link
Copy Markdown
Member

I tweaked the Perf_SubstringFrozenDictionary benchmark to use case insensitive comparison and here's what I got:

MethodJobToolchainCountMeanErrorStdDevMedianMinMaxRatioMannWhitney(3%)RatioSDGen0Gen1Gen2AllocatedAlloc Ratio
ToFrozenDictionaryJob-LOTKLJmain101,389.48 ns31.906 ns36.743 ns1,382.71 ns1,327.53 ns1,473.59 ns1.00Base0.000.1660--1720 B1.00
ToFrozenDictionaryJob-QETJTAPR101,393.58 ns27.624 ns31.812 ns1,399.03 ns1,323.29 ns1,453.86 ns1.00Same0.040.1708--1720 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain1054.00 ns1.288 ns1.322 ns54.13 ns51.35 ns55.60 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1073.24 ns1.078 ns1.008 ns73.38 ns70.06 ns74.24 ns1.35Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain1008,446.66 ns158.800 ns163.076 ns8,471.45 ns8,134.23 ns8,717.36 ns1.00Base0.001.1780--12112 B1.00
ToFrozenDictionaryJob-QETJTAPR1008,771.04 ns237.326 ns273.305 ns8,836.49 ns8,160.74 ns9,217.86 ns1.04Same0.031.2019--12112 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100563.85 ns10.168 ns9.014 ns567.79 ns547.12 ns576.15 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1001,006.73 ns15.021 ns14.050 ns1,006.87 ns985.24 ns1,036.20 ns1.79Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain100074,531.61 ns1,469.417 ns1,508.983 ns74,819.41 ns71,279.16 ns77,269.59 ns1.00Base0.009.27031.1962-94864 B1.00
ToFrozenDictionaryJob-QETJTAPR100072,473.19 ns1,866.260 ns2,149.188 ns73,203.68 ns68,539.46 ns75,121.58 ns0.98Same0.039.22621.1905-94864 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100048,575.95 ns935.546 ns918.831 ns48,985.45 ns46,654.11 ns50,021.83 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR100050,132.24 ns976.166 ns958.725 ns50,439.63 ns47,947.67 ns51,144.53 ns1.03Same0.03----NA
ToFrozenDictionaryJob-LOTKLJmain100001,367,777.25 ns31,636.638 ns32,488.498 ns1,378,004.69 ns1,300,021.88 ns1,428,109.38 ns1.00Base0.00148.4375148.4375148.4375926138 B1.00
ToFrozenDictionaryJob-QETJTAPR100001,361,651.79 ns26,136.206 ns25,669.249 ns1,371,019.27 ns1,298,396.35 ns1,389,535.42 ns1.00Same0.03145.8333145.8333145.8333926134 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain10000429,049.49 ns7,951.179 ns7,048.514 ns430,354.73 ns412,395.95 ns438,879.39 ns1.00Base0.00---1 B1.00
TryGetValue_True_FrozenDictionaryJob-QETJTAPR10000433,232.35 ns7,438.534 ns6,958.009 ns435,446.22 ns419,605.92 ns442,993.75 ns1.01Same0.03---11 B11.00
ToDictionaryJob-LOTKLJmain10140.89 ns2.840 ns2.656 ns142.01 ns136.03 ns144.31 ns1.00Base0.000.0433--440 B1.00
ToDictionaryJob-QETJTAPR10141.67 ns2.772 ns3.081 ns142.03 ns135.25 ns146.24 ns1.01Same0.040.0435--440 B1.00
ToImmutableDictionaryJob-LOTKLJmain10915.37 ns21.383 ns24.625 ns916.86 ns868.44 ns952.28 ns1.00Base0.000.0712--736 B1.00
ToImmutableDictionaryJob-QETJTAPR101,064.95 ns20.525 ns21.961 ns1,068.78 ns1,016.33 ns1,093.58 ns1.16Slower0.030.0703--736 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10108.24 ns2.142 ns2.103 ns109.02 ns104.05 ns110.36 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR10108.94 ns2.030 ns1.899 ns109.56 ns104.94 ns111.03 ns1.01Same0.03----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10173.50 ns3.458 ns3.552 ns173.71 ns166.58 ns179.82 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10178.48 ns3.450 ns3.543 ns179.64 ns170.79 ns183.23 ns1.03Same0.03----NA
ToDictionaryJob-LOTKLJmain1001,039.01 ns21.634 ns24.913 ns1,042.85 ns984.15 ns1,069.44 ns1.00Base0.000.3080--3128 B1.00
ToDictionaryJob-QETJTAPR1001,001.41 ns15.964 ns14.152 ns1,006.79 ns967.14 ns1,018.20 ns0.96Faster0.020.3090--3128 B1.00
ToImmutableDictionaryJob-LOTKLJmain10013,437.35 ns262.918 ns258.220 ns13,528.59 ns12,911.66 ns13,739.11 ns1.00Base0.000.6020--6496 B1.00
ToImmutableDictionaryJob-QETJTAPR10013,385.87 ns263.227 ns281.650 ns13,508.10 ns12,705.08 ns13,655.47 ns1.00Same0.020.6111--6496 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain1001,270.08 ns24.849 ns24.405 ns1,274.04 ns1,228.82 ns1,306.02 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR1001,355.12 ns25.952 ns27.768 ns1,364.22 ns1,293.32 ns1,391.48 ns1.07Slower0.04----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain1002,051.36 ns40.157 ns44.634 ns2,051.86 ns1,952.25 ns2,141.73 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR1002,024.26 ns39.026 ns41.758 ns2,030.78 ns1,918.59 ns2,087.70 ns0.99Same0.04----NA
ToDictionaryJob-LOTKLJmain100010,196.84 ns251.988 ns290.190 ns10,254.18 ns9,696.03 ns10,705.83 ns1.00Base0.003.04480.2469-31016 B1.00
ToDictionaryJob-QETJTAPR10009,639.19 ns187.533 ns192.583 ns9,678.42 ns9,206.36 ns9,917.35 ns0.94Faster0.033.05450.2741-31016 B1.00
ToImmutableDictionaryJob-LOTKLJmain1000237,679.59 ns3,661.106 ns3,057.189 ns238,570.74 ns229,820.64 ns240,334.00 ns1.00Base0.005.68180.9470-64102 B1.00
ToImmutableDictionaryJob-QETJTAPR1000230,173.07 ns4,311.465 ns3,822.002 ns231,834.56 ns219,823.01 ns233,708.06 ns0.97Same0.026.34060.9058-64102 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain100015,714.41 ns299.767 ns294.411 ns15,796.10 ns15,092.75 ns16,233.26 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR100016,141.57 ns315.820 ns295.418 ns16,228.09 ns15,554.74 ns16,578.18 ns1.03Same0.02----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain100059,513.28 ns1,073.929 ns1,004.554 ns59,719.73 ns57,310.95 ns60,994.35 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR100059,640.00 ns1,154.325 ns1,079.756 ns60,059.92 ns57,502.75 ns60,990.41 ns1.00Same0.03---2 BNA
ToDictionaryJob-LOTKLJmain10000244,971.90 ns4,813.058 ns5,349.701 ns245,296.34 ns235,273.75 ns253,046.79 ns1.00Base0.0076.785776.785776.7857283073 B1.00
ToDictionaryJob-QETJTAPR10000246,629.63 ns7,293.248 ns8,106.426 ns243,601.12 ns233,309.42 ns263,366.79 ns1.01Same0.0376.492576.492576.4925283068 B1.00
ToImmutableDictionaryJob-LOTKLJmain100003,143,437.89 ns60,920.847 ns59,832.417 ns3,164,813.12 ns3,007,650.00 ns3,203,625.00 ns1.00Base0.0062.500025.0000-640105 B1.00
ToImmutableDictionaryJob-QETJTAPR100003,190,842.99 ns57,976.310 ns62,034.002 ns3,207,857.50 ns3,062,612.50 ns3,308,832.50 ns1.01Same0.0362.500025.0000-640105 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10000199,541.34 ns4,524.300 ns5,028.748 ns200,181.25 ns191,146.48 ns211,170.08 ns1.00Base0.00---1 B1.00
TryGetValue_True_DictionaryJob-QETJTAPR10000195,256.67 ns3,751.698 ns3,684.669 ns196,337.70 ns188,147.78 ns200,153.24 ns0.98Same0.04---5 B5.00
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10000884,652.17 ns15,963.048 ns14,150.826 ns891,639.79 ns856,336.67 ns898,688.75 ns1.00Base0.00---3 B1.00
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10000906,593.35 ns17,063.661 ns14,248.930 ns910,996.53 ns875,974.31 ns928,415.62 ns1.03Same0.03---3 B1.00

In certain cases performance becomes up to 2x slower, but this impacts cases where lookup is currently incorrect.

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid there seem to be a few failing unit tests from KeyAnalyzer.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis there's 2 parts to the reduced performance.

As the bug is causing us to use case sensitive comparison it's artificially faster than any correct version of the code thus it's not a fair comparison.

The second part is the cost of removing this optimization which boils down to case sensitivity of a a partial substring of up to 8 chars. To measure that change we can benchmark with #93986 which has a much larger diff but keeps the optimization.

@eiriktsarpaliseiriktsarpalis left a comment

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.

Thanks

@eiriktsarpaliseiriktsarpalis added this to the 9.0.0 milestone Nov 13, 2023
@eiriktsarpalis

Copy link
Copy Markdown
Member

Thank you for the help @andrewjsaid!

@eiriktsarpalis

Copy link
Copy Markdown
Member

/backport to release/8.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0: https://github.com/dotnet/runtime/actions/runs/6856200961

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis backporting to release/8.0 failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Add failing tests
Applying: Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
Applying: When hashing the entire string, case sensitivity of hash and equals should be the same
Applying: Address code review comments
Applying: Only ignore case insensitivity if entire string is ASCII non-letters
error: sha1 information is lacking or useless (src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj).
error: could not build fake ancestor
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0005 Only ignore case insensitivity if entire string is ASCII non-letters
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis an error occurred while backporting to release/8.0, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

Pleased to be of help!

eiriktsarpalis pushed a commit to eiriktsarpalis/runtime that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (dotnet#94667)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixesdotnet#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
eiriktsarpalis added a commit that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (#94667) (#94685)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixes#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
Co-authored-by: Andrew J Said <andrewjsaid@gmail.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 14, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FrozenDictionary is incorrectly case sensitive in some cases

3 participants

@andrewjsaid@stephentoub@eiriktsarpalis
, '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

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections - #94667

Merged
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation
Nov 13, 2023
Merged

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections#94667
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation

Conversation

@andrewjsaid

@andrewjsaidandrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
Contributor

See #93986 for full context

Fix#93974

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Collections labels Nov 13, 2023
@ghost

Copy link
Copy Markdown

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

Issue Details

See #93986 for full context

Author:andrewjsaid
Assignees:-
Labels:

area-System.Collections, community-contribution

Milestone:-

@eiriktsarpaliseiriktsarpalis left a comment

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.

To get a feel of potential perf regressions, would it be possible to share benchmarks comparing this to main in the impacted cases?

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis That would require a PR in the performance repo. Is it possible for somebody from the MS team to do that as I am pretty busy. If not I'll try find some time.

@stephentoubstephentoub left a comment

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.

Thanks. Do we have an understanding of what uses this might regress and by how much? We should make the change, regardless, but I'd like to understand the ramifications and what we should be looking at following-up on.

@andrewjsaid
andrewjsaidforce-pushed the frozen-collections-ordinal-ignore-case-remove-optimisation branch from c44f0a1 to c45a592CompareNovember 13, 2023 15:00
@andrewjsaid

andrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
ContributorAuthor

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

@stephentoub

Copy link
Copy Markdown
Member

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

Ok, thanks. As noted, we don't need to block on it. @eiriktsarpalis, if you have some time to get some quick numbers just to aid in our understanding of the impact and whether there's an important follow-up here, that'd be helpful.

@eiriktsarpalis

Copy link
Copy Markdown
Member

I tweaked the Perf_SubstringFrozenDictionary benchmark to use case insensitive comparison and here's what I got:

MethodJobToolchainCountMeanErrorStdDevMedianMinMaxRatioMannWhitney(3%)RatioSDGen0Gen1Gen2AllocatedAlloc Ratio
ToFrozenDictionaryJob-LOTKLJmain101,389.48 ns31.906 ns36.743 ns1,382.71 ns1,327.53 ns1,473.59 ns1.00Base0.000.1660--1720 B1.00
ToFrozenDictionaryJob-QETJTAPR101,393.58 ns27.624 ns31.812 ns1,399.03 ns1,323.29 ns1,453.86 ns1.00Same0.040.1708--1720 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain1054.00 ns1.288 ns1.322 ns54.13 ns51.35 ns55.60 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1073.24 ns1.078 ns1.008 ns73.38 ns70.06 ns74.24 ns1.35Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain1008,446.66 ns158.800 ns163.076 ns8,471.45 ns8,134.23 ns8,717.36 ns1.00Base0.001.1780--12112 B1.00
ToFrozenDictionaryJob-QETJTAPR1008,771.04 ns237.326 ns273.305 ns8,836.49 ns8,160.74 ns9,217.86 ns1.04Same0.031.2019--12112 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100563.85 ns10.168 ns9.014 ns567.79 ns547.12 ns576.15 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1001,006.73 ns15.021 ns14.050 ns1,006.87 ns985.24 ns1,036.20 ns1.79Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain100074,531.61 ns1,469.417 ns1,508.983 ns74,819.41 ns71,279.16 ns77,269.59 ns1.00Base0.009.27031.1962-94864 B1.00
ToFrozenDictionaryJob-QETJTAPR100072,473.19 ns1,866.260 ns2,149.188 ns73,203.68 ns68,539.46 ns75,121.58 ns0.98Same0.039.22621.1905-94864 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100048,575.95 ns935.546 ns918.831 ns48,985.45 ns46,654.11 ns50,021.83 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR100050,132.24 ns976.166 ns958.725 ns50,439.63 ns47,947.67 ns51,144.53 ns1.03Same0.03----NA
ToFrozenDictionaryJob-LOTKLJmain100001,367,777.25 ns31,636.638 ns32,488.498 ns1,378,004.69 ns1,300,021.88 ns1,428,109.38 ns1.00Base0.00148.4375148.4375148.4375926138 B1.00
ToFrozenDictionaryJob-QETJTAPR100001,361,651.79 ns26,136.206 ns25,669.249 ns1,371,019.27 ns1,298,396.35 ns1,389,535.42 ns1.00Same0.03145.8333145.8333145.8333926134 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain10000429,049.49 ns7,951.179 ns7,048.514 ns430,354.73 ns412,395.95 ns438,879.39 ns1.00Base0.00---1 B1.00
TryGetValue_True_FrozenDictionaryJob-QETJTAPR10000433,232.35 ns7,438.534 ns6,958.009 ns435,446.22 ns419,605.92 ns442,993.75 ns1.01Same0.03---11 B11.00
ToDictionaryJob-LOTKLJmain10140.89 ns2.840 ns2.656 ns142.01 ns136.03 ns144.31 ns1.00Base0.000.0433--440 B1.00
ToDictionaryJob-QETJTAPR10141.67 ns2.772 ns3.081 ns142.03 ns135.25 ns146.24 ns1.01Same0.040.0435--440 B1.00
ToImmutableDictionaryJob-LOTKLJmain10915.37 ns21.383 ns24.625 ns916.86 ns868.44 ns952.28 ns1.00Base0.000.0712--736 B1.00
ToImmutableDictionaryJob-QETJTAPR101,064.95 ns20.525 ns21.961 ns1,068.78 ns1,016.33 ns1,093.58 ns1.16Slower0.030.0703--736 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10108.24 ns2.142 ns2.103 ns109.02 ns104.05 ns110.36 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR10108.94 ns2.030 ns1.899 ns109.56 ns104.94 ns111.03 ns1.01Same0.03----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10173.50 ns3.458 ns3.552 ns173.71 ns166.58 ns179.82 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10178.48 ns3.450 ns3.543 ns179.64 ns170.79 ns183.23 ns1.03Same0.03----NA
ToDictionaryJob-LOTKLJmain1001,039.01 ns21.634 ns24.913 ns1,042.85 ns984.15 ns1,069.44 ns1.00Base0.000.3080--3128 B1.00
ToDictionaryJob-QETJTAPR1001,001.41 ns15.964 ns14.152 ns1,006.79 ns967.14 ns1,018.20 ns0.96Faster0.020.3090--3128 B1.00
ToImmutableDictionaryJob-LOTKLJmain10013,437.35 ns262.918 ns258.220 ns13,528.59 ns12,911.66 ns13,739.11 ns1.00Base0.000.6020--6496 B1.00
ToImmutableDictionaryJob-QETJTAPR10013,385.87 ns263.227 ns281.650 ns13,508.10 ns12,705.08 ns13,655.47 ns1.00Same0.020.6111--6496 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain1001,270.08 ns24.849 ns24.405 ns1,274.04 ns1,228.82 ns1,306.02 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR1001,355.12 ns25.952 ns27.768 ns1,364.22 ns1,293.32 ns1,391.48 ns1.07Slower0.04----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain1002,051.36 ns40.157 ns44.634 ns2,051.86 ns1,952.25 ns2,141.73 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR1002,024.26 ns39.026 ns41.758 ns2,030.78 ns1,918.59 ns2,087.70 ns0.99Same0.04----NA
ToDictionaryJob-LOTKLJmain100010,196.84 ns251.988 ns290.190 ns10,254.18 ns9,696.03 ns10,705.83 ns1.00Base0.003.04480.2469-31016 B1.00
ToDictionaryJob-QETJTAPR10009,639.19 ns187.533 ns192.583 ns9,678.42 ns9,206.36 ns9,917.35 ns0.94Faster0.033.05450.2741-31016 B1.00
ToImmutableDictionaryJob-LOTKLJmain1000237,679.59 ns3,661.106 ns3,057.189 ns238,570.74 ns229,820.64 ns240,334.00 ns1.00Base0.005.68180.9470-64102 B1.00
ToImmutableDictionaryJob-QETJTAPR1000230,173.07 ns4,311.465 ns3,822.002 ns231,834.56 ns219,823.01 ns233,708.06 ns0.97Same0.026.34060.9058-64102 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain100015,714.41 ns299.767 ns294.411 ns15,796.10 ns15,092.75 ns16,233.26 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR100016,141.57 ns315.820 ns295.418 ns16,228.09 ns15,554.74 ns16,578.18 ns1.03Same0.02----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain100059,513.28 ns1,073.929 ns1,004.554 ns59,719.73 ns57,310.95 ns60,994.35 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR100059,640.00 ns1,154.325 ns1,079.756 ns60,059.92 ns57,502.75 ns60,990.41 ns1.00Same0.03---2 BNA
ToDictionaryJob-LOTKLJmain10000244,971.90 ns4,813.058 ns5,349.701 ns245,296.34 ns235,273.75 ns253,046.79 ns1.00Base0.0076.785776.785776.7857283073 B1.00
ToDictionaryJob-QETJTAPR10000246,629.63 ns7,293.248 ns8,106.426 ns243,601.12 ns233,309.42 ns263,366.79 ns1.01Same0.0376.492576.492576.4925283068 B1.00
ToImmutableDictionaryJob-LOTKLJmain100003,143,437.89 ns60,920.847 ns59,832.417 ns3,164,813.12 ns3,007,650.00 ns3,203,625.00 ns1.00Base0.0062.500025.0000-640105 B1.00
ToImmutableDictionaryJob-QETJTAPR100003,190,842.99 ns57,976.310 ns62,034.002 ns3,207,857.50 ns3,062,612.50 ns3,308,832.50 ns1.01Same0.0362.500025.0000-640105 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10000199,541.34 ns4,524.300 ns5,028.748 ns200,181.25 ns191,146.48 ns211,170.08 ns1.00Base0.00---1 B1.00
TryGetValue_True_DictionaryJob-QETJTAPR10000195,256.67 ns3,751.698 ns3,684.669 ns196,337.70 ns188,147.78 ns200,153.24 ns0.98Same0.04---5 B5.00
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10000884,652.17 ns15,963.048 ns14,150.826 ns891,639.79 ns856,336.67 ns898,688.75 ns1.00Base0.00---3 B1.00
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10000906,593.35 ns17,063.661 ns14,248.930 ns910,996.53 ns875,974.31 ns928,415.62 ns1.03Same0.03---3 B1.00

In certain cases performance becomes up to 2x slower, but this impacts cases where lookup is currently incorrect.

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid there seem to be a few failing unit tests from KeyAnalyzer.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis there's 2 parts to the reduced performance.

As the bug is causing us to use case sensitive comparison it's artificially faster than any correct version of the code thus it's not a fair comparison.

The second part is the cost of removing this optimization which boils down to case sensitivity of a a partial substring of up to 8 chars. To measure that change we can benchmark with #93986 which has a much larger diff but keeps the optimization.

@eiriktsarpaliseiriktsarpalis left a comment

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.

Thanks

@eiriktsarpaliseiriktsarpalis added this to the 9.0.0 milestone Nov 13, 2023
@eiriktsarpalis

Copy link
Copy Markdown
Member

Thank you for the help @andrewjsaid!

@eiriktsarpalis

Copy link
Copy Markdown
Member

/backport to release/8.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0: https://github.com/dotnet/runtime/actions/runs/6856200961

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis backporting to release/8.0 failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Add failing tests
Applying: Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
Applying: When hashing the entire string, case sensitivity of hash and equals should be the same
Applying: Address code review comments
Applying: Only ignore case insensitivity if entire string is ASCII non-letters
error: sha1 information is lacking or useless (src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj).
error: could not build fake ancestor
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0005 Only ignore case insensitivity if entire string is ASCII non-letters
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis an error occurred while backporting to release/8.0, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

Pleased to be of help!

eiriktsarpalis pushed a commit to eiriktsarpalis/runtime that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (dotnet#94667)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixesdotnet#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
eiriktsarpalis added a commit that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (#94667) (#94685)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixes#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
Co-authored-by: Andrew J Said <andrewjsaid@gmail.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 14, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FrozenDictionary is incorrectly case sensitive in some cases

3 participants

@andrewjsaid@stephentoub@eiriktsarpalis
, '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

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections - #94667

Merged
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation
Nov 13, 2023
Merged

Disable optimization which sometimes results in incorrect case sensitivity in FrozenCollections#94667
eiriktsarpalis merged 10 commits into
dotnet:mainfrom
andrewjsaid:frozen-collections-ordinal-ignore-case-remove-optimisation

Conversation

@andrewjsaid

@andrewjsaidandrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
Contributor

See #93986 for full context

Fix#93974

@ghostghost added community-contribution Indicates that the PR has been added by a community member area-System.Collections labels Nov 13, 2023
@ghost

Copy link
Copy Markdown

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

Issue Details

See #93986 for full context

Author:andrewjsaid
Assignees:-
Labels:

area-System.Collections, community-contribution

Milestone:-

@eiriktsarpaliseiriktsarpalis left a comment

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.

To get a feel of potential perf regressions, would it be possible to share benchmarks comparing this to main in the impacted cases?

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis That would require a PR in the performance repo. Is it possible for somebody from the MS team to do that as I am pretty busy. If not I'll try find some time.

@stephentoubstephentoub left a comment

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.

Thanks. Do we have an understanding of what uses this might regress and by how much? We should make the change, regardless, but I'd like to understand the ramifications and what we should be looking at following-up on.

@andrewjsaid
andrewjsaidforce-pushed the frozen-collections-ordinal-ignore-case-remove-optimisation branch from c44f0a1 to c45a592CompareNovember 13, 2023 15:00
@andrewjsaid

andrewjsaid commented Nov 13, 2023

Copy link
Copy Markdown
ContributorAuthor

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

@stephentoub

Copy link
Copy Markdown
Member

Do we have an understanding of what uses this might regress

Removing the optimization will be a regression (haven't got a measurement*) only in the case where we are case insensitive, have identified a partial substring to hash, and that substring contains no letters. In all other cases it matches .NET 8 release.

and by how much?

Perf benchmarks in #93986 (comment) show that for case sensitive there is no effect. However that doesn't answer your question as this particular optimization being disabled was never benchmarked by the performance repo. If I find time to add a benchmark I'll add it here but I don't foresee having the time before the weekend.

Ok, thanks. As noted, we don't need to block on it. @eiriktsarpalis, if you have some time to get some quick numbers just to aid in our understanding of the impact and whether there's an important follow-up here, that'd be helpful.

@eiriktsarpalis

Copy link
Copy Markdown
Member

I tweaked the Perf_SubstringFrozenDictionary benchmark to use case insensitive comparison and here's what I got:

MethodJobToolchainCountMeanErrorStdDevMedianMinMaxRatioMannWhitney(3%)RatioSDGen0Gen1Gen2AllocatedAlloc Ratio
ToFrozenDictionaryJob-LOTKLJmain101,389.48 ns31.906 ns36.743 ns1,382.71 ns1,327.53 ns1,473.59 ns1.00Base0.000.1660--1720 B1.00
ToFrozenDictionaryJob-QETJTAPR101,393.58 ns27.624 ns31.812 ns1,399.03 ns1,323.29 ns1,453.86 ns1.00Same0.040.1708--1720 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain1054.00 ns1.288 ns1.322 ns54.13 ns51.35 ns55.60 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1073.24 ns1.078 ns1.008 ns73.38 ns70.06 ns74.24 ns1.35Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain1008,446.66 ns158.800 ns163.076 ns8,471.45 ns8,134.23 ns8,717.36 ns1.00Base0.001.1780--12112 B1.00
ToFrozenDictionaryJob-QETJTAPR1008,771.04 ns237.326 ns273.305 ns8,836.49 ns8,160.74 ns9,217.86 ns1.04Same0.031.2019--12112 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100563.85 ns10.168 ns9.014 ns567.79 ns547.12 ns576.15 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR1001,006.73 ns15.021 ns14.050 ns1,006.87 ns985.24 ns1,036.20 ns1.79Slower0.04----NA
ToFrozenDictionaryJob-LOTKLJmain100074,531.61 ns1,469.417 ns1,508.983 ns74,819.41 ns71,279.16 ns77,269.59 ns1.00Base0.009.27031.1962-94864 B1.00
ToFrozenDictionaryJob-QETJTAPR100072,473.19 ns1,866.260 ns2,149.188 ns73,203.68 ns68,539.46 ns75,121.58 ns0.98Same0.039.22621.1905-94864 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain100048,575.95 ns935.546 ns918.831 ns48,985.45 ns46,654.11 ns50,021.83 ns1.00Base0.00----NA
TryGetValue_True_FrozenDictionaryJob-QETJTAPR100050,132.24 ns976.166 ns958.725 ns50,439.63 ns47,947.67 ns51,144.53 ns1.03Same0.03----NA
ToFrozenDictionaryJob-LOTKLJmain100001,367,777.25 ns31,636.638 ns32,488.498 ns1,378,004.69 ns1,300,021.88 ns1,428,109.38 ns1.00Base0.00148.4375148.4375148.4375926138 B1.00
ToFrozenDictionaryJob-QETJTAPR100001,361,651.79 ns26,136.206 ns25,669.249 ns1,371,019.27 ns1,298,396.35 ns1,389,535.42 ns1.00Same0.03145.8333145.8333145.8333926134 B1.00
TryGetValue_True_FrozenDictionaryJob-LOTKLJmain10000429,049.49 ns7,951.179 ns7,048.514 ns430,354.73 ns412,395.95 ns438,879.39 ns1.00Base0.00---1 B1.00
TryGetValue_True_FrozenDictionaryJob-QETJTAPR10000433,232.35 ns7,438.534 ns6,958.009 ns435,446.22 ns419,605.92 ns442,993.75 ns1.01Same0.03---11 B11.00
ToDictionaryJob-LOTKLJmain10140.89 ns2.840 ns2.656 ns142.01 ns136.03 ns144.31 ns1.00Base0.000.0433--440 B1.00
ToDictionaryJob-QETJTAPR10141.67 ns2.772 ns3.081 ns142.03 ns135.25 ns146.24 ns1.01Same0.040.0435--440 B1.00
ToImmutableDictionaryJob-LOTKLJmain10915.37 ns21.383 ns24.625 ns916.86 ns868.44 ns952.28 ns1.00Base0.000.0712--736 B1.00
ToImmutableDictionaryJob-QETJTAPR101,064.95 ns20.525 ns21.961 ns1,068.78 ns1,016.33 ns1,093.58 ns1.16Slower0.030.0703--736 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10108.24 ns2.142 ns2.103 ns109.02 ns104.05 ns110.36 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR10108.94 ns2.030 ns1.899 ns109.56 ns104.94 ns111.03 ns1.01Same0.03----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10173.50 ns3.458 ns3.552 ns173.71 ns166.58 ns179.82 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10178.48 ns3.450 ns3.543 ns179.64 ns170.79 ns183.23 ns1.03Same0.03----NA
ToDictionaryJob-LOTKLJmain1001,039.01 ns21.634 ns24.913 ns1,042.85 ns984.15 ns1,069.44 ns1.00Base0.000.3080--3128 B1.00
ToDictionaryJob-QETJTAPR1001,001.41 ns15.964 ns14.152 ns1,006.79 ns967.14 ns1,018.20 ns0.96Faster0.020.3090--3128 B1.00
ToImmutableDictionaryJob-LOTKLJmain10013,437.35 ns262.918 ns258.220 ns13,528.59 ns12,911.66 ns13,739.11 ns1.00Base0.000.6020--6496 B1.00
ToImmutableDictionaryJob-QETJTAPR10013,385.87 ns263.227 ns281.650 ns13,508.10 ns12,705.08 ns13,655.47 ns1.00Same0.020.6111--6496 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain1001,270.08 ns24.849 ns24.405 ns1,274.04 ns1,228.82 ns1,306.02 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR1001,355.12 ns25.952 ns27.768 ns1,364.22 ns1,293.32 ns1,391.48 ns1.07Slower0.04----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain1002,051.36 ns40.157 ns44.634 ns2,051.86 ns1,952.25 ns2,141.73 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR1002,024.26 ns39.026 ns41.758 ns2,030.78 ns1,918.59 ns2,087.70 ns0.99Same0.04----NA
ToDictionaryJob-LOTKLJmain100010,196.84 ns251.988 ns290.190 ns10,254.18 ns9,696.03 ns10,705.83 ns1.00Base0.003.04480.2469-31016 B1.00
ToDictionaryJob-QETJTAPR10009,639.19 ns187.533 ns192.583 ns9,678.42 ns9,206.36 ns9,917.35 ns0.94Faster0.033.05450.2741-31016 B1.00
ToImmutableDictionaryJob-LOTKLJmain1000237,679.59 ns3,661.106 ns3,057.189 ns238,570.74 ns229,820.64 ns240,334.00 ns1.00Base0.005.68180.9470-64102 B1.00
ToImmutableDictionaryJob-QETJTAPR1000230,173.07 ns4,311.465 ns3,822.002 ns231,834.56 ns219,823.01 ns233,708.06 ns0.97Same0.026.34060.9058-64102 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain100015,714.41 ns299.767 ns294.411 ns15,796.10 ns15,092.75 ns16,233.26 ns1.00Base0.00----NA
TryGetValue_True_DictionaryJob-QETJTAPR100016,141.57 ns315.820 ns295.418 ns16,228.09 ns15,554.74 ns16,578.18 ns1.03Same0.02----NA
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain100059,513.28 ns1,073.929 ns1,004.554 ns59,719.73 ns57,310.95 ns60,994.35 ns1.00Base0.00----NA
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR100059,640.00 ns1,154.325 ns1,079.756 ns60,059.92 ns57,502.75 ns60,990.41 ns1.00Same0.03---2 BNA
ToDictionaryJob-LOTKLJmain10000244,971.90 ns4,813.058 ns5,349.701 ns245,296.34 ns235,273.75 ns253,046.79 ns1.00Base0.0076.785776.785776.7857283073 B1.00
ToDictionaryJob-QETJTAPR10000246,629.63 ns7,293.248 ns8,106.426 ns243,601.12 ns233,309.42 ns263,366.79 ns1.01Same0.0376.492576.492576.4925283068 B1.00
ToImmutableDictionaryJob-LOTKLJmain100003,143,437.89 ns60,920.847 ns59,832.417 ns3,164,813.12 ns3,007,650.00 ns3,203,625.00 ns1.00Base0.0062.500025.0000-640105 B1.00
ToImmutableDictionaryJob-QETJTAPR100003,190,842.99 ns57,976.310 ns62,034.002 ns3,207,857.50 ns3,062,612.50 ns3,308,832.50 ns1.01Same0.0362.500025.0000-640105 B1.00
TryGetValue_True_DictionaryJob-LOTKLJmain10000199,541.34 ns4,524.300 ns5,028.748 ns200,181.25 ns191,146.48 ns211,170.08 ns1.00Base0.00---1 B1.00
TryGetValue_True_DictionaryJob-QETJTAPR10000195,256.67 ns3,751.698 ns3,684.669 ns196,337.70 ns188,147.78 ns200,153.24 ns0.98Same0.04---5 B5.00
TryGetValue_True_ImmutableDictionaryJob-LOTKLJmain10000884,652.17 ns15,963.048 ns14,150.826 ns891,639.79 ns856,336.67 ns898,688.75 ns1.00Base0.00---3 B1.00
TryGetValue_True_ImmutableDictionaryJob-QETJTAPR10000906,593.35 ns17,063.661 ns14,248.930 ns910,996.53 ns875,974.31 ns928,415.62 ns1.03Same0.03---3 B1.00

In certain cases performance becomes up to 2x slower, but this impacts cases where lookup is currently incorrect.

@eiriktsarpalis

Copy link
Copy Markdown
Member

@andrewjsaid there seem to be a few failing unit tests from KeyAnalyzer.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

@eiriktsarpalis there's 2 parts to the reduced performance.

As the bug is causing us to use case sensitive comparison it's artificially faster than any correct version of the code thus it's not a fair comparison.

The second part is the cost of removing this optimization which boils down to case sensitivity of a a partial substring of up to 8 chars. To measure that change we can benchmark with #93986 which has a much larger diff but keeps the optimization.

@eiriktsarpaliseiriktsarpalis left a comment

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.

Thanks

@eiriktsarpaliseiriktsarpalis added this to the 9.0.0 milestone Nov 13, 2023
@eiriktsarpalis

Copy link
Copy Markdown
Member

Thank you for the help @andrewjsaid!

@eiriktsarpalis

Copy link
Copy Markdown
Member

/backport to release/8.0

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/8.0: https://github.com/dotnet/runtime/actions/runs/6856200961

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis backporting to release/8.0 failed, the patch most likely resulted in conflicts:

$ git am --3way --ignore-whitespace --keep-non-patch changes.patch
Applying: Add failing tests
Applying: Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
Applying: When hashing the entire string, case sensitivity of hash and equals should be the same
Applying: Address code review comments
Applying: Only ignore case insensitivity if entire string is ASCII non-letters
error: sha1 information is lacking or useless (src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj).
error: could not build fake ancestor
hint: Use 'git am --show-current-patch=diff' to see the failed patch
Patch failed at 0005 Only ignore case insensitivity if entire string is ASCII non-letters
When you have resolved this problem, run "git am --continue".
If you prefer to skip this patch, run "git am --skip" instead.
To restore the original branch and stop patching, run "git am --abort".
Error: The process '/usr/bin/git' failed with exit code 128

Please backport manually!

@github-actions

Copy link
Copy Markdown
Contributor

@eiriktsarpalis an error occurred while backporting to release/8.0, please check the run log for details!

Error: git am failed, most likely due to a merge conflict.

@andrewjsaid

Copy link
Copy Markdown
ContributorAuthor

Pleased to be of help!

eiriktsarpalis pushed a commit to eiriktsarpalis/runtime that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (dotnet#94667)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixesdotnet#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
eiriktsarpalis added a commit that referenced this pull request Nov 14, 2023
…ivity in FrozenCollections (#94667) (#94685)
* Add failing tests
* Fix incorrect case sensitivity in FrozenDictionary and FrozenSet for some cases
fixes#93974
* When hashing the entire string, case sensitivity of hash and equals should be the same
* Address code review comments
* Only ignore case insensitivity if entire string is ASCII non-letters
* Code review comments
* Undo some new lines
* Fixed tests - incorrect leftover from previous PR
Co-authored-by: Andrew J Said <andrewjsaid@gmail.com>
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Dec 14, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-System.Collectionscommunity-contributionIndicates that the PR has been added by a community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FrozenDictionary is incorrectly case sensitive in some cases

3 participants

@andrewjsaid@stephentoub@eiriktsarpalis