Rework ProbabilisticMap character checks in SearchValues - #101001

Merged
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2
May 16, 2024
Merged

Rework ProbabilisticMap character checks in SearchValues#101001
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2

Conversation

@MihaZupan

Copy link
Copy Markdown
Member

Contributes to #100315 (comment)

The probabilistic map uses an O(i * m) fallback for -Except methods (scan all the values for each character in the input).
We use the same O(m) helper (scan the values) to confirm potential matches in the vectorized helper.

This PR adds support for the probmap to use an O(1) contains check for -Except methods and match confirmations.
The check is effectively a perfect hash check entries[value % entries.Length] == value, but implemented using a variant of FastMod. These checks can be inlined into the vectorized methods while not consuming too much memory.
We use this only when the probmap is computed as part of SearchValues as finding an optimal modulus is relatively expensive (see ProbabilisticMapState.FindModulus - there are probably smarter ways to go about it). When the probabilistic map is created for single-use IndexOfAny operations, we still use the same O(m) checks as before.

This PR also replaces the Latin1CharSearchValues implementation with BitmapCharSearchValues, which can use a bitmap of arbitrary size (not limited to [0, 255]). We use this when we guess that it'll be faster than the ProbabilisticMap (e.g. there are a lot of values in the set, or the set is dense and the probmap isn't vectorized).
I haven't changed the condition when we use ProbabilisticWithAsciiCharSearchValues as there are plausible cases where the ASCII fast path may still be useful even if the probabilistic path could be a bitmap instead.

Improvements for early matches (cheaper confirmation step)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privateconststringText="űaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";[Benchmark]// This one will do the confirmation inside the vectorized pathpublicintIndexOfAny()=>Text.AsSpan().IndexOfAny(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnymain11.236 ns0.0375 ns1.00
IndexOfAnypr7.768 ns0.0412 ns0.69
Improvements for IndexOfAnyExcept (O(1) instead of O(m) character checks)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privatestaticreadonlystring_textExcept=newstring('K',1000);[Benchmark]publicintIndexOfAnyExcept()=>_textExcept.AsSpan().IndexOfAnyExcept(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnyExceptmain2,832.7 ns10.65 ns1.00
IndexOfAnyExceptpr675.2 ns2.39 ns0.24

Speedup factor is going to depend on how many values are in the set and how early in the values each input character matched with the previous implementation.
With the new implementation, the throughput is less dependent on the haystack.

The bitmap vs probmap hash O(1) checks
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_bitmap=SearchValues.Create(newstring('\u0080',256)+'\u0082');privatestaticreadonlySearchValues<char>s_probmap=SearchValues.Create(newstring('\u0080',100)+'\uF000');privatestaticreadonlystringTextExcept=newstring('\u0080',1000);[Benchmark]publicintBitmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_bitmap);[Benchmark]publicintProbmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_probmap);}
MethodMeanError
Bitmap754.2 ns14.42 ns
Probmap868.3 ns0.11 ns

This happens to bring all SearchValues<char> implementations to an O(i) worst-case (from O(i * m)).

@MihaZupanMihaZupan added this to the 9.0.0 milestone Apr 13, 2024
@MihaZupanMihaZupan self-assigned this Apr 13, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@danmoseley

Copy link
Copy Markdown
Contributor

Are all these well covered in the perf repo?

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

Are all these well covered in the perf repo?

Yes, mainly by the ßäöüÄÖÜ values.
The BitmapCharSearchValues would be covered by platforms without explicit ISA support.

Results
MethodToolchainValuesMeanErrorRatio
ContainsmainßäöüÄÖÜ1.832 ns0.0125 ns1.00
ContainsprßäöüÄÖÜ1.734 ns0.0058 ns0.95
ContainsAnymainßäöüÄÖÜ21.025 ns1.5866 ns1.01
ContainsAnyprßäöüÄÖÜ15.148 ns0.1088 ns0.73
IndexOfAnymainßäöüÄÖÜ21.404 ns2.7379 ns1.03
IndexOfAnyprßäöüÄÖÜ15.867 ns0.5050 ns0.76
LastIndexOfAnymainßäöüÄÖÜ124.294 ns0.1215 ns1.00
LastIndexOfAnyprßäöüÄÖÜ91.647 ns0.1313 ns0.74
LastIndexOfAnyExceptmainßäöüÄÖÜ270.352 ns0.0708 ns1.00
LastIndexOfAnyExceptprßäöüÄÖÜ91.775 ns0.0788 ns0.34
IndexOfAnyExceptmainßäöüÄÖÜ273.921 ns0.7002 ns1.00
IndexOfAnyExceptprßäöüÄÖÜ76.792 ns0.3625 ns0.28

@EgorBo

Copy link
Copy Markdown
Member

Improvements on arm64: dotnet/perf-autofiling-issues#34817

Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
* Rework ProbabilisticMap character checks in SearchValues
* Reduce footprint of ProbMap SearchValues
* Update misleading comment
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 23, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MihaZupan@danmoseley@EgorBo@stephentoub
, '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

Rework ProbabilisticMap character checks in SearchValues - #101001

Merged
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2
May 16, 2024
Merged

Rework ProbabilisticMap character checks in SearchValues#101001
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2

Conversation

@MihaZupan

Copy link
Copy Markdown
Member

Contributes to #100315 (comment)

The probabilistic map uses an O(i * m) fallback for -Except methods (scan all the values for each character in the input).
We use the same O(m) helper (scan the values) to confirm potential matches in the vectorized helper.

This PR adds support for the probmap to use an O(1) contains check for -Except methods and match confirmations.
The check is effectively a perfect hash check entries[value % entries.Length] == value, but implemented using a variant of FastMod. These checks can be inlined into the vectorized methods while not consuming too much memory.
We use this only when the probmap is computed as part of SearchValues as finding an optimal modulus is relatively expensive (see ProbabilisticMapState.FindModulus - there are probably smarter ways to go about it). When the probabilistic map is created for single-use IndexOfAny operations, we still use the same O(m) checks as before.

This PR also replaces the Latin1CharSearchValues implementation with BitmapCharSearchValues, which can use a bitmap of arbitrary size (not limited to [0, 255]). We use this when we guess that it'll be faster than the ProbabilisticMap (e.g. there are a lot of values in the set, or the set is dense and the probmap isn't vectorized).
I haven't changed the condition when we use ProbabilisticWithAsciiCharSearchValues as there are plausible cases where the ASCII fast path may still be useful even if the probabilistic path could be a bitmap instead.

Improvements for early matches (cheaper confirmation step)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privateconststringText="űaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";[Benchmark]// This one will do the confirmation inside the vectorized pathpublicintIndexOfAny()=>Text.AsSpan().IndexOfAny(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnymain11.236 ns0.0375 ns1.00
IndexOfAnypr7.768 ns0.0412 ns0.69
Improvements for IndexOfAnyExcept (O(1) instead of O(m) character checks)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privatestaticreadonlystring_textExcept=newstring('K',1000);[Benchmark]publicintIndexOfAnyExcept()=>_textExcept.AsSpan().IndexOfAnyExcept(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnyExceptmain2,832.7 ns10.65 ns1.00
IndexOfAnyExceptpr675.2 ns2.39 ns0.24

Speedup factor is going to depend on how many values are in the set and how early in the values each input character matched with the previous implementation.
With the new implementation, the throughput is less dependent on the haystack.

The bitmap vs probmap hash O(1) checks
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_bitmap=SearchValues.Create(newstring('\u0080',256)+'\u0082');privatestaticreadonlySearchValues<char>s_probmap=SearchValues.Create(newstring('\u0080',100)+'\uF000');privatestaticreadonlystringTextExcept=newstring('\u0080',1000);[Benchmark]publicintBitmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_bitmap);[Benchmark]publicintProbmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_probmap);}
MethodMeanError
Bitmap754.2 ns14.42 ns
Probmap868.3 ns0.11 ns

This happens to bring all SearchValues<char> implementations to an O(i) worst-case (from O(i * m)).

@MihaZupanMihaZupan added this to the 9.0.0 milestone Apr 13, 2024
@MihaZupanMihaZupan self-assigned this Apr 13, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@danmoseley

Copy link
Copy Markdown
Contributor

Are all these well covered in the perf repo?

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

Are all these well covered in the perf repo?

Yes, mainly by the ßäöüÄÖÜ values.
The BitmapCharSearchValues would be covered by platforms without explicit ISA support.

Results
MethodToolchainValuesMeanErrorRatio
ContainsmainßäöüÄÖÜ1.832 ns0.0125 ns1.00
ContainsprßäöüÄÖÜ1.734 ns0.0058 ns0.95
ContainsAnymainßäöüÄÖÜ21.025 ns1.5866 ns1.01
ContainsAnyprßäöüÄÖÜ15.148 ns0.1088 ns0.73
IndexOfAnymainßäöüÄÖÜ21.404 ns2.7379 ns1.03
IndexOfAnyprßäöüÄÖÜ15.867 ns0.5050 ns0.76
LastIndexOfAnymainßäöüÄÖÜ124.294 ns0.1215 ns1.00
LastIndexOfAnyprßäöüÄÖÜ91.647 ns0.1313 ns0.74
LastIndexOfAnyExceptmainßäöüÄÖÜ270.352 ns0.0708 ns1.00
LastIndexOfAnyExceptprßäöüÄÖÜ91.775 ns0.0788 ns0.34
IndexOfAnyExceptmainßäöüÄÖÜ273.921 ns0.7002 ns1.00
IndexOfAnyExceptprßäöüÄÖÜ76.792 ns0.3625 ns0.28

@EgorBo

Copy link
Copy Markdown
Member

Improvements on arm64: dotnet/perf-autofiling-issues#34817

Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
* Rework ProbabilisticMap character checks in SearchValues
* Reduce footprint of ProbMap SearchValues
* Update misleading comment
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 23, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MihaZupan@danmoseley@EgorBo@stephentoub
, '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

Rework ProbabilisticMap character checks in SearchValues - #101001

Merged
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2
May 16, 2024
Merged

Rework ProbabilisticMap character checks in SearchValues#101001
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2

Conversation

@MihaZupan

Copy link
Copy Markdown
Member

Contributes to #100315 (comment)

The probabilistic map uses an O(i * m) fallback for -Except methods (scan all the values for each character in the input).
We use the same O(m) helper (scan the values) to confirm potential matches in the vectorized helper.

This PR adds support for the probmap to use an O(1) contains check for -Except methods and match confirmations.
The check is effectively a perfect hash check entries[value % entries.Length] == value, but implemented using a variant of FastMod. These checks can be inlined into the vectorized methods while not consuming too much memory.
We use this only when the probmap is computed as part of SearchValues as finding an optimal modulus is relatively expensive (see ProbabilisticMapState.FindModulus - there are probably smarter ways to go about it). When the probabilistic map is created for single-use IndexOfAny operations, we still use the same O(m) checks as before.

This PR also replaces the Latin1CharSearchValues implementation with BitmapCharSearchValues, which can use a bitmap of arbitrary size (not limited to [0, 255]). We use this when we guess that it'll be faster than the ProbabilisticMap (e.g. there are a lot of values in the set, or the set is dense and the probmap isn't vectorized).
I haven't changed the condition when we use ProbabilisticWithAsciiCharSearchValues as there are plausible cases where the ASCII fast path may still be useful even if the probabilistic path could be a bitmap instead.

Improvements for early matches (cheaper confirmation step)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privateconststringText="űaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";[Benchmark]// This one will do the confirmation inside the vectorized pathpublicintIndexOfAny()=>Text.AsSpan().IndexOfAny(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnymain11.236 ns0.0375 ns1.00
IndexOfAnypr7.768 ns0.0412 ns0.69
Improvements for IndexOfAnyExcept (O(1) instead of O(m) character checks)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privatestaticreadonlystring_textExcept=newstring('K',1000);[Benchmark]publicintIndexOfAnyExcept()=>_textExcept.AsSpan().IndexOfAnyExcept(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnyExceptmain2,832.7 ns10.65 ns1.00
IndexOfAnyExceptpr675.2 ns2.39 ns0.24

Speedup factor is going to depend on how many values are in the set and how early in the values each input character matched with the previous implementation.
With the new implementation, the throughput is less dependent on the haystack.

The bitmap vs probmap hash O(1) checks
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_bitmap=SearchValues.Create(newstring('\u0080',256)+'\u0082');privatestaticreadonlySearchValues<char>s_probmap=SearchValues.Create(newstring('\u0080',100)+'\uF000');privatestaticreadonlystringTextExcept=newstring('\u0080',1000);[Benchmark]publicintBitmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_bitmap);[Benchmark]publicintProbmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_probmap);}
MethodMeanError
Bitmap754.2 ns14.42 ns
Probmap868.3 ns0.11 ns

This happens to bring all SearchValues<char> implementations to an O(i) worst-case (from O(i * m)).

@MihaZupanMihaZupan added this to the 9.0.0 milestone Apr 13, 2024
@MihaZupanMihaZupan self-assigned this Apr 13, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@danmoseley

Copy link
Copy Markdown
Contributor

Are all these well covered in the perf repo?

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

Are all these well covered in the perf repo?

Yes, mainly by the ßäöüÄÖÜ values.
The BitmapCharSearchValues would be covered by platforms without explicit ISA support.

Results
MethodToolchainValuesMeanErrorRatio
ContainsmainßäöüÄÖÜ1.832 ns0.0125 ns1.00
ContainsprßäöüÄÖÜ1.734 ns0.0058 ns0.95
ContainsAnymainßäöüÄÖÜ21.025 ns1.5866 ns1.01
ContainsAnyprßäöüÄÖÜ15.148 ns0.1088 ns0.73
IndexOfAnymainßäöüÄÖÜ21.404 ns2.7379 ns1.03
IndexOfAnyprßäöüÄÖÜ15.867 ns0.5050 ns0.76
LastIndexOfAnymainßäöüÄÖÜ124.294 ns0.1215 ns1.00
LastIndexOfAnyprßäöüÄÖÜ91.647 ns0.1313 ns0.74
LastIndexOfAnyExceptmainßäöüÄÖÜ270.352 ns0.0708 ns1.00
LastIndexOfAnyExceptprßäöüÄÖÜ91.775 ns0.0788 ns0.34
IndexOfAnyExceptmainßäöüÄÖÜ273.921 ns0.7002 ns1.00
IndexOfAnyExceptprßäöüÄÖÜ76.792 ns0.3625 ns0.28

@EgorBo

Copy link
Copy Markdown
Member

Improvements on arm64: dotnet/perf-autofiling-issues#34817

Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
* Rework ProbabilisticMap character checks in SearchValues
* Reduce footprint of ProbMap SearchValues
* Update misleading comment
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 23, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MihaZupan@danmoseley@EgorBo@stephentoub
, '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

Rework ProbabilisticMap character checks in SearchValues - #101001

Merged
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2
May 16, 2024
Merged

Rework ProbabilisticMap character checks in SearchValues#101001
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2

Conversation

@MihaZupan

Copy link
Copy Markdown
Member

Contributes to #100315 (comment)

The probabilistic map uses an O(i * m) fallback for -Except methods (scan all the values for each character in the input).
We use the same O(m) helper (scan the values) to confirm potential matches in the vectorized helper.

This PR adds support for the probmap to use an O(1) contains check for -Except methods and match confirmations.
The check is effectively a perfect hash check entries[value % entries.Length] == value, but implemented using a variant of FastMod. These checks can be inlined into the vectorized methods while not consuming too much memory.
We use this only when the probmap is computed as part of SearchValues as finding an optimal modulus is relatively expensive (see ProbabilisticMapState.FindModulus - there are probably smarter ways to go about it). When the probabilistic map is created for single-use IndexOfAny operations, we still use the same O(m) checks as before.

This PR also replaces the Latin1CharSearchValues implementation with BitmapCharSearchValues, which can use a bitmap of arbitrary size (not limited to [0, 255]). We use this when we guess that it'll be faster than the ProbabilisticMap (e.g. there are a lot of values in the set, or the set is dense and the probmap isn't vectorized).
I haven't changed the condition when we use ProbabilisticWithAsciiCharSearchValues as there are plausible cases where the ASCII fast path may still be useful even if the probabilistic path could be a bitmap instead.

Improvements for early matches (cheaper confirmation step)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privateconststringText="űaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";[Benchmark]// This one will do the confirmation inside the vectorized pathpublicintIndexOfAny()=>Text.AsSpan().IndexOfAny(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnymain11.236 ns0.0375 ns1.00
IndexOfAnypr7.768 ns0.0412 ns0.69
Improvements for IndexOfAnyExcept (O(1) instead of O(m) character checks)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privatestaticreadonlystring_textExcept=newstring('K',1000);[Benchmark]publicintIndexOfAnyExcept()=>_textExcept.AsSpan().IndexOfAnyExcept(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnyExceptmain2,832.7 ns10.65 ns1.00
IndexOfAnyExceptpr675.2 ns2.39 ns0.24

Speedup factor is going to depend on how many values are in the set and how early in the values each input character matched with the previous implementation.
With the new implementation, the throughput is less dependent on the haystack.

The bitmap vs probmap hash O(1) checks
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_bitmap=SearchValues.Create(newstring('\u0080',256)+'\u0082');privatestaticreadonlySearchValues<char>s_probmap=SearchValues.Create(newstring('\u0080',100)+'\uF000');privatestaticreadonlystringTextExcept=newstring('\u0080',1000);[Benchmark]publicintBitmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_bitmap);[Benchmark]publicintProbmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_probmap);}
MethodMeanError
Bitmap754.2 ns14.42 ns
Probmap868.3 ns0.11 ns

This happens to bring all SearchValues<char> implementations to an O(i) worst-case (from O(i * m)).

@MihaZupanMihaZupan added this to the 9.0.0 milestone Apr 13, 2024
@MihaZupanMihaZupan self-assigned this Apr 13, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@danmoseley

Copy link
Copy Markdown
Contributor

Are all these well covered in the perf repo?

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

Are all these well covered in the perf repo?

Yes, mainly by the ßäöüÄÖÜ values.
The BitmapCharSearchValues would be covered by platforms without explicit ISA support.

Results
MethodToolchainValuesMeanErrorRatio
ContainsmainßäöüÄÖÜ1.832 ns0.0125 ns1.00
ContainsprßäöüÄÖÜ1.734 ns0.0058 ns0.95
ContainsAnymainßäöüÄÖÜ21.025 ns1.5866 ns1.01
ContainsAnyprßäöüÄÖÜ15.148 ns0.1088 ns0.73
IndexOfAnymainßäöüÄÖÜ21.404 ns2.7379 ns1.03
IndexOfAnyprßäöüÄÖÜ15.867 ns0.5050 ns0.76
LastIndexOfAnymainßäöüÄÖÜ124.294 ns0.1215 ns1.00
LastIndexOfAnyprßäöüÄÖÜ91.647 ns0.1313 ns0.74
LastIndexOfAnyExceptmainßäöüÄÖÜ270.352 ns0.0708 ns1.00
LastIndexOfAnyExceptprßäöüÄÖÜ91.775 ns0.0788 ns0.34
IndexOfAnyExceptmainßäöüÄÖÜ273.921 ns0.7002 ns1.00
IndexOfAnyExceptprßäöüÄÖÜ76.792 ns0.3625 ns0.28

@EgorBo

Copy link
Copy Markdown
Member

Improvements on arm64: dotnet/perf-autofiling-issues#34817

Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
* Rework ProbabilisticMap character checks in SearchValues
* Reduce footprint of ProbMap SearchValues
* Update misleading comment
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 23, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MihaZupan@danmoseley@EgorBo@stephentoub
, '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

Rework ProbabilisticMap character checks in SearchValues - #101001

Merged
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2
May 16, 2024
Merged

Rework ProbabilisticMap character checks in SearchValues#101001
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2

Conversation

@MihaZupan

Copy link
Copy Markdown
Member

Contributes to #100315 (comment)

The probabilistic map uses an O(i * m) fallback for -Except methods (scan all the values for each character in the input).
We use the same O(m) helper (scan the values) to confirm potential matches in the vectorized helper.

This PR adds support for the probmap to use an O(1) contains check for -Except methods and match confirmations.
The check is effectively a perfect hash check entries[value % entries.Length] == value, but implemented using a variant of FastMod. These checks can be inlined into the vectorized methods while not consuming too much memory.
We use this only when the probmap is computed as part of SearchValues as finding an optimal modulus is relatively expensive (see ProbabilisticMapState.FindModulus - there are probably smarter ways to go about it). When the probabilistic map is created for single-use IndexOfAny operations, we still use the same O(m) checks as before.

This PR also replaces the Latin1CharSearchValues implementation with BitmapCharSearchValues, which can use a bitmap of arbitrary size (not limited to [0, 255]). We use this when we guess that it'll be faster than the ProbabilisticMap (e.g. there are a lot of values in the set, or the set is dense and the probmap isn't vectorized).
I haven't changed the condition when we use ProbabilisticWithAsciiCharSearchValues as there are plausible cases where the ASCII fast path may still be useful even if the probabilistic path could be a bitmap instead.

Improvements for early matches (cheaper confirmation step)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privateconststringText="űaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";[Benchmark]// This one will do the confirmation inside the vectorized pathpublicintIndexOfAny()=>Text.AsSpan().IndexOfAny(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnymain11.236 ns0.0375 ns1.00
IndexOfAnypr7.768 ns0.0412 ns0.69
Improvements for IndexOfAnyExcept (O(1) instead of O(m) character checks)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privatestaticreadonlystring_textExcept=newstring('K',1000);[Benchmark]publicintIndexOfAnyExcept()=>_textExcept.AsSpan().IndexOfAnyExcept(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnyExceptmain2,832.7 ns10.65 ns1.00
IndexOfAnyExceptpr675.2 ns2.39 ns0.24

Speedup factor is going to depend on how many values are in the set and how early in the values each input character matched with the previous implementation.
With the new implementation, the throughput is less dependent on the haystack.

The bitmap vs probmap hash O(1) checks
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_bitmap=SearchValues.Create(newstring('\u0080',256)+'\u0082');privatestaticreadonlySearchValues<char>s_probmap=SearchValues.Create(newstring('\u0080',100)+'\uF000');privatestaticreadonlystringTextExcept=newstring('\u0080',1000);[Benchmark]publicintBitmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_bitmap);[Benchmark]publicintProbmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_probmap);}
MethodMeanError
Bitmap754.2 ns14.42 ns
Probmap868.3 ns0.11 ns

This happens to bring all SearchValues<char> implementations to an O(i) worst-case (from O(i * m)).

@MihaZupanMihaZupan added this to the 9.0.0 milestone Apr 13, 2024
@MihaZupanMihaZupan self-assigned this Apr 13, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@danmoseley

Copy link
Copy Markdown
Contributor

Are all these well covered in the perf repo?

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

Are all these well covered in the perf repo?

Yes, mainly by the ßäöüÄÖÜ values.
The BitmapCharSearchValues would be covered by platforms without explicit ISA support.

Results
MethodToolchainValuesMeanErrorRatio
ContainsmainßäöüÄÖÜ1.832 ns0.0125 ns1.00
ContainsprßäöüÄÖÜ1.734 ns0.0058 ns0.95
ContainsAnymainßäöüÄÖÜ21.025 ns1.5866 ns1.01
ContainsAnyprßäöüÄÖÜ15.148 ns0.1088 ns0.73
IndexOfAnymainßäöüÄÖÜ21.404 ns2.7379 ns1.03
IndexOfAnyprßäöüÄÖÜ15.867 ns0.5050 ns0.76
LastIndexOfAnymainßäöüÄÖÜ124.294 ns0.1215 ns1.00
LastIndexOfAnyprßäöüÄÖÜ91.647 ns0.1313 ns0.74
LastIndexOfAnyExceptmainßäöüÄÖÜ270.352 ns0.0708 ns1.00
LastIndexOfAnyExceptprßäöüÄÖÜ91.775 ns0.0788 ns0.34
IndexOfAnyExceptmainßäöüÄÖÜ273.921 ns0.7002 ns1.00
IndexOfAnyExceptprßäöüÄÖÜ76.792 ns0.3625 ns0.28

@EgorBo

Copy link
Copy Markdown
Member

Improvements on arm64: dotnet/perf-autofiling-issues#34817

Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
* Rework ProbabilisticMap character checks in SearchValues
* Reduce footprint of ProbMap SearchValues
* Update misleading comment
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 23, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MihaZupan@danmoseley@EgorBo@stephentoub
, '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

Rework ProbabilisticMap character checks in SearchValues - #101001

Merged
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2
May 16, 2024
Merged

Rework ProbabilisticMap character checks in SearchValues#101001
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2

Conversation

@MihaZupan

Copy link
Copy Markdown
Member

Contributes to #100315 (comment)

The probabilistic map uses an O(i * m) fallback for -Except methods (scan all the values for each character in the input).
We use the same O(m) helper (scan the values) to confirm potential matches in the vectorized helper.

This PR adds support for the probmap to use an O(1) contains check for -Except methods and match confirmations.
The check is effectively a perfect hash check entries[value % entries.Length] == value, but implemented using a variant of FastMod. These checks can be inlined into the vectorized methods while not consuming too much memory.
We use this only when the probmap is computed as part of SearchValues as finding an optimal modulus is relatively expensive (see ProbabilisticMapState.FindModulus - there are probably smarter ways to go about it). When the probabilistic map is created for single-use IndexOfAny operations, we still use the same O(m) checks as before.

This PR also replaces the Latin1CharSearchValues implementation with BitmapCharSearchValues, which can use a bitmap of arbitrary size (not limited to [0, 255]). We use this when we guess that it'll be faster than the ProbabilisticMap (e.g. there are a lot of values in the set, or the set is dense and the probmap isn't vectorized).
I haven't changed the condition when we use ProbabilisticWithAsciiCharSearchValues as there are plausible cases where the ASCII fast path may still be useful even if the probabilistic path could be a bitmap instead.

Improvements for early matches (cheaper confirmation step)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privateconststringText="űaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";[Benchmark]// This one will do the confirmation inside the vectorized pathpublicintIndexOfAny()=>Text.AsSpan().IndexOfAny(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnymain11.236 ns0.0375 ns1.00
IndexOfAnypr7.768 ns0.0412 ns0.69
Improvements for IndexOfAnyExcept (O(1) instead of O(m) character checks)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privatestaticreadonlystring_textExcept=newstring('K',1000);[Benchmark]publicintIndexOfAnyExcept()=>_textExcept.AsSpan().IndexOfAnyExcept(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnyExceptmain2,832.7 ns10.65 ns1.00
IndexOfAnyExceptpr675.2 ns2.39 ns0.24

Speedup factor is going to depend on how many values are in the set and how early in the values each input character matched with the previous implementation.
With the new implementation, the throughput is less dependent on the haystack.

The bitmap vs probmap hash O(1) checks
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_bitmap=SearchValues.Create(newstring('\u0080',256)+'\u0082');privatestaticreadonlySearchValues<char>s_probmap=SearchValues.Create(newstring('\u0080',100)+'\uF000');privatestaticreadonlystringTextExcept=newstring('\u0080',1000);[Benchmark]publicintBitmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_bitmap);[Benchmark]publicintProbmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_probmap);}
MethodMeanError
Bitmap754.2 ns14.42 ns
Probmap868.3 ns0.11 ns

This happens to bring all SearchValues<char> implementations to an O(i) worst-case (from O(i * m)).

@MihaZupanMihaZupan added this to the 9.0.0 milestone Apr 13, 2024
@MihaZupanMihaZupan self-assigned this Apr 13, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@danmoseley

Copy link
Copy Markdown
Contributor

Are all these well covered in the perf repo?

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

Are all these well covered in the perf repo?

Yes, mainly by the ßäöüÄÖÜ values.
The BitmapCharSearchValues would be covered by platforms without explicit ISA support.

Results
MethodToolchainValuesMeanErrorRatio
ContainsmainßäöüÄÖÜ1.832 ns0.0125 ns1.00
ContainsprßäöüÄÖÜ1.734 ns0.0058 ns0.95
ContainsAnymainßäöüÄÖÜ21.025 ns1.5866 ns1.01
ContainsAnyprßäöüÄÖÜ15.148 ns0.1088 ns0.73
IndexOfAnymainßäöüÄÖÜ21.404 ns2.7379 ns1.03
IndexOfAnyprßäöüÄÖÜ15.867 ns0.5050 ns0.76
LastIndexOfAnymainßäöüÄÖÜ124.294 ns0.1215 ns1.00
LastIndexOfAnyprßäöüÄÖÜ91.647 ns0.1313 ns0.74
LastIndexOfAnyExceptmainßäöüÄÖÜ270.352 ns0.0708 ns1.00
LastIndexOfAnyExceptprßäöüÄÖÜ91.775 ns0.0788 ns0.34
IndexOfAnyExceptmainßäöüÄÖÜ273.921 ns0.7002 ns1.00
IndexOfAnyExceptprßäöüÄÖÜ76.792 ns0.3625 ns0.28

@EgorBo

Copy link
Copy Markdown
Member

Improvements on arm64: dotnet/perf-autofiling-issues#34817

Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
* Rework ProbabilisticMap character checks in SearchValues
* Reduce footprint of ProbMap SearchValues
* Update misleading comment
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 23, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MihaZupan@danmoseley@EgorBo@stephentoub
, '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

Rework ProbabilisticMap character checks in SearchValues - #101001

Merged
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2
May 16, 2024
Merged

Rework ProbabilisticMap character checks in SearchValues#101001
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2

Conversation

@MihaZupan

Copy link
Copy Markdown
Member

Contributes to #100315 (comment)

The probabilistic map uses an O(i * m) fallback for -Except methods (scan all the values for each character in the input).
We use the same O(m) helper (scan the values) to confirm potential matches in the vectorized helper.

This PR adds support for the probmap to use an O(1) contains check for -Except methods and match confirmations.
The check is effectively a perfect hash check entries[value % entries.Length] == value, but implemented using a variant of FastMod. These checks can be inlined into the vectorized methods while not consuming too much memory.
We use this only when the probmap is computed as part of SearchValues as finding an optimal modulus is relatively expensive (see ProbabilisticMapState.FindModulus - there are probably smarter ways to go about it). When the probabilistic map is created for single-use IndexOfAny operations, we still use the same O(m) checks as before.

This PR also replaces the Latin1CharSearchValues implementation with BitmapCharSearchValues, which can use a bitmap of arbitrary size (not limited to [0, 255]). We use this when we guess that it'll be faster than the ProbabilisticMap (e.g. there are a lot of values in the set, or the set is dense and the probmap isn't vectorized).
I haven't changed the condition when we use ProbabilisticWithAsciiCharSearchValues as there are plausible cases where the ASCII fast path may still be useful even if the probabilistic path could be a bitmap instead.

Improvements for early matches (cheaper confirmation step)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privateconststringText="űaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";[Benchmark]// This one will do the confirmation inside the vectorized pathpublicintIndexOfAny()=>Text.AsSpan().IndexOfAny(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnymain11.236 ns0.0375 ns1.00
IndexOfAnypr7.768 ns0.0412 ns0.69
Improvements for IndexOfAnyExcept (O(1) instead of O(m) character checks)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privatestaticreadonlystring_textExcept=newstring('K',1000);[Benchmark]publicintIndexOfAnyExcept()=>_textExcept.AsSpan().IndexOfAnyExcept(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnyExceptmain2,832.7 ns10.65 ns1.00
IndexOfAnyExceptpr675.2 ns2.39 ns0.24

Speedup factor is going to depend on how many values are in the set and how early in the values each input character matched with the previous implementation.
With the new implementation, the throughput is less dependent on the haystack.

The bitmap vs probmap hash O(1) checks
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_bitmap=SearchValues.Create(newstring('\u0080',256)+'\u0082');privatestaticreadonlySearchValues<char>s_probmap=SearchValues.Create(newstring('\u0080',100)+'\uF000');privatestaticreadonlystringTextExcept=newstring('\u0080',1000);[Benchmark]publicintBitmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_bitmap);[Benchmark]publicintProbmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_probmap);}
MethodMeanError
Bitmap754.2 ns14.42 ns
Probmap868.3 ns0.11 ns

This happens to bring all SearchValues<char> implementations to an O(i) worst-case (from O(i * m)).

@MihaZupanMihaZupan added this to the 9.0.0 milestone Apr 13, 2024
@MihaZupanMihaZupan self-assigned this Apr 13, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@danmoseley

Copy link
Copy Markdown
Contributor

Are all these well covered in the perf repo?

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

Are all these well covered in the perf repo?

Yes, mainly by the ßäöüÄÖÜ values.
The BitmapCharSearchValues would be covered by platforms without explicit ISA support.

Results
MethodToolchainValuesMeanErrorRatio
ContainsmainßäöüÄÖÜ1.832 ns0.0125 ns1.00
ContainsprßäöüÄÖÜ1.734 ns0.0058 ns0.95
ContainsAnymainßäöüÄÖÜ21.025 ns1.5866 ns1.01
ContainsAnyprßäöüÄÖÜ15.148 ns0.1088 ns0.73
IndexOfAnymainßäöüÄÖÜ21.404 ns2.7379 ns1.03
IndexOfAnyprßäöüÄÖÜ15.867 ns0.5050 ns0.76
LastIndexOfAnymainßäöüÄÖÜ124.294 ns0.1215 ns1.00
LastIndexOfAnyprßäöüÄÖÜ91.647 ns0.1313 ns0.74
LastIndexOfAnyExceptmainßäöüÄÖÜ270.352 ns0.0708 ns1.00
LastIndexOfAnyExceptprßäöüÄÖÜ91.775 ns0.0788 ns0.34
IndexOfAnyExceptmainßäöüÄÖÜ273.921 ns0.7002 ns1.00
IndexOfAnyExceptprßäöüÄÖÜ76.792 ns0.3625 ns0.28

@EgorBo

Copy link
Copy Markdown
Member

Improvements on arm64: dotnet/perf-autofiling-issues#34817

Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
* Rework ProbabilisticMap character checks in SearchValues
* Reduce footprint of ProbMap SearchValues
* Update misleading comment
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 23, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MihaZupan@danmoseley@EgorBo@stephentoub
, '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

Rework ProbabilisticMap character checks in SearchValues - #101001

Merged
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2
May 16, 2024
Merged

Rework ProbabilisticMap character checks in SearchValues#101001
MihaZupan merged 3 commits into
dotnet:mainfrom
MihaZupan:searchvalues-probMap-fastContains2

Conversation

@MihaZupan

Copy link
Copy Markdown
Member

Contributes to #100315 (comment)

The probabilistic map uses an O(i * m) fallback for -Except methods (scan all the values for each character in the input).
We use the same O(m) helper (scan the values) to confirm potential matches in the vectorized helper.

This PR adds support for the probmap to use an O(1) contains check for -Except methods and match confirmations.
The check is effectively a perfect hash check entries[value % entries.Length] == value, but implemented using a variant of FastMod. These checks can be inlined into the vectorized methods while not consuming too much memory.
We use this only when the probmap is computed as part of SearchValues as finding an optimal modulus is relatively expensive (see ProbabilisticMapState.FindModulus - there are probably smarter ways to go about it). When the probabilistic map is created for single-use IndexOfAny operations, we still use the same O(m) checks as before.

This PR also replaces the Latin1CharSearchValues implementation with BitmapCharSearchValues, which can use a bitmap of arbitrary size (not limited to [0, 255]). We use this when we guess that it'll be faster than the ProbabilisticMap (e.g. there are a lot of values in the set, or the set is dense and the probmap isn't vectorized).
I haven't changed the condition when we use ProbabilisticWithAsciiCharSearchValues as there are plausible cases where the ASCII fast path may still be useful even if the probabilistic path could be a bitmap instead.

Improvements for early matches (cheaper confirmation step)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privateconststringText="űaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";[Benchmark]// This one will do the confirmation inside the vectorized pathpublicintIndexOfAny()=>Text.AsSpan().IndexOfAny(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnymain11.236 ns0.0375 ns1.00
IndexOfAnypr7.768 ns0.0412 ns0.69
Improvements for IndexOfAnyExcept (O(1) instead of O(m) character checks)
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_charsWithNonAscii=SearchValues.Create("-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzK");privatestaticreadonlystring_textExcept=newstring('K',1000);[Benchmark]publicintIndexOfAnyExcept()=>_textExcept.AsSpan().IndexOfAnyExcept(s_charsWithNonAscii);}
MethodToolchainMeanErrorRatio
IndexOfAnyExceptmain2,832.7 ns10.65 ns1.00
IndexOfAnyExceptpr675.2 ns2.39 ns0.24

Speedup factor is going to depend on how many values are in the set and how early in the values each input character matched with the previous implementation.
With the new implementation, the throughput is less dependent on the haystack.

The bitmap vs probmap hash O(1) checks
publicclassIndexOfAnyMixedAsciiNonAscii{privatestaticreadonlySearchValues<char>s_bitmap=SearchValues.Create(newstring('\u0080',256)+'\u0082');privatestaticreadonlySearchValues<char>s_probmap=SearchValues.Create(newstring('\u0080',100)+'\uF000');privatestaticreadonlystringTextExcept=newstring('\u0080',1000);[Benchmark]publicintBitmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_bitmap);[Benchmark]publicintProbmap()=>TextExcept.AsSpan().IndexOfAnyExcept(s_probmap);}
MethodMeanError
Bitmap754.2 ns14.42 ns
Probmap868.3 ns0.11 ns

This happens to bring all SearchValues<char> implementations to an O(i) worst-case (from O(i * m)).

@MihaZupanMihaZupan added this to the 9.0.0 milestone Apr 13, 2024
@MihaZupanMihaZupan self-assigned this Apr 13, 2024
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

@danmoseley

Copy link
Copy Markdown
Contributor

Are all these well covered in the perf repo?

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

/azp run runtime-libraries-coreclr outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@MihaZupan

Copy link
Copy Markdown
MemberAuthor

Are all these well covered in the perf repo?

Yes, mainly by the ßäöüÄÖÜ values.
The BitmapCharSearchValues would be covered by platforms without explicit ISA support.

Results
MethodToolchainValuesMeanErrorRatio
ContainsmainßäöüÄÖÜ1.832 ns0.0125 ns1.00
ContainsprßäöüÄÖÜ1.734 ns0.0058 ns0.95
ContainsAnymainßäöüÄÖÜ21.025 ns1.5866 ns1.01
ContainsAnyprßäöüÄÖÜ15.148 ns0.1088 ns0.73
IndexOfAnymainßäöüÄÖÜ21.404 ns2.7379 ns1.03
IndexOfAnyprßäöüÄÖÜ15.867 ns0.5050 ns0.76
LastIndexOfAnymainßäöüÄÖÜ124.294 ns0.1215 ns1.00
LastIndexOfAnyprßäöüÄÖÜ91.647 ns0.1313 ns0.74
LastIndexOfAnyExceptmainßäöüÄÖÜ270.352 ns0.0708 ns1.00
LastIndexOfAnyExceptprßäöüÄÖÜ91.775 ns0.0788 ns0.34
IndexOfAnyExceptmainßäöüÄÖÜ273.921 ns0.7002 ns1.00
IndexOfAnyExceptprßäöüÄÖÜ76.792 ns0.3625 ns0.28

@EgorBo

Copy link
Copy Markdown
Member

Improvements on arm64: dotnet/perf-autofiling-issues#34817

Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
* Rework ProbabilisticMap character checks in SearchValues
* Reduce footprint of ProbMap SearchValues
* Update misleading comment
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jun 23, 2024
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@MihaZupan@danmoseley@EgorBo@stephentoub