Add StringReaderReadLineTests - #2083

Merged
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests
Dec 6, 2021
Merged

Add StringReaderReadLineTests#2083
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests

Conversation

@nietras

Copy link
Copy Markdown
Contributor

Benchmark for dotnet/runtime#60463

cc: @danmoseley@adamsitnik

Example run comparing main m to PR pr.

BenchmarkDotNet=v0.13.1.1611-nightly, OS=Windows 10.0.19043.1266 (21H1/May2021Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK=6.0.100-rc.2.21505.57
[Host] : .NET 6.0.0 (6.0.21.48005), X64 RyuJIT
Job-BIEWJM : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
Job-IFXICU : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable,-bl:benchmarkdotnet.binlog IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainLineLengthRangeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Allocated
ReadLineJob-BIEWJMruntime-m[ 0, 0]7.208 ns0.0556 ns0.0434 ns7.194 ns7.165 ns7.288 ns1.000.00--
ReadLineJob-IFXICUruntime-pr[ 0, 0]10.112 ns0.1781 ns0.1666 ns10.105 ns9.875 ns10.438 ns1.410.02--
ReadLineJob-BIEWJMruntime-m[ 1, 8]17.751 ns0.2287 ns0.2028 ns17.686 ns17.552 ns18.192 ns1.000.000.002033 B
ReadLineJob-IFXICUruntime-pr[ 1, 8]16.998 ns0.1011 ns0.0946 ns16.992 ns16.872 ns17.212 ns0.960.010.002033 B
ReadLineJob-BIEWJMruntime-m[ 9, 32]26.336 ns0.1205 ns0.1127 ns26.273 ns26.200 ns26.510 ns1.000.000.003965 B
ReadLineJob-IFXICUruntime-pr[ 9, 32]19.896 ns0.0740 ns0.0618 ns19.879 ns19.801 ns20.046 ns0.760.000.003865 B
ReadLineJob-BIEWJMruntime-m[ 33, 128]62.594 ns0.2100 ns0.1861 ns62.584 ns62.222 ns62.928 ns1.000.000.0108185 B
ReadLineJob-IFXICUruntime-pr[ 33, 128]31.021 ns0.0902 ns0.0800 ns31.015 ns30.912 ns31.200 ns0.500.000.0110185 B
ReadLineJob-BIEWJMruntime-m[ 129,1024]319.244 ns0.6595 ns0.6169 ns319.302 ns318.088 ns320.391 ns1.000.000.06971,181 B
ReadLineJob-IFXICUruntime-pr[ 129,1024]98.174 ns0.2733 ns0.2282 ns98.123 ns97.705 ns98.567 ns0.310.000.07051,181 B

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
new (){ Min = 33, Max = 128 },
new (){ Min = 129, Max = 1024 },
};
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd expect it'd be fairly common to find real world data that alternates between longer line lengths and empty lines, with paragraphs separated by blank lines.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I can easily add a test case for [0, 1024] but that is not exactly what you are asking, would that be better or would you rather have this refactored to support the kind of data you suggest?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I should note that the test cases exactly match real world data and use case for me, namely csv content where lines are never empty and often have same range of lengths.

@danmoseleydanmoseleyOct 17, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do you mean something like

 sb.Append(newLine); // existing
if (random.Next() % 5 == 0)
sb.Append(newLine);

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

one way to add this would be to formalize a EmptyLineRatio e.g. 0.20 as part of Range (that should then be renamed). Not sure there is much value in it overall, though. It's pretty straightforward, very short lines see regressions. Others, large gains. If you mix the two, the large gains will prevail since they dominate completely. E.g. A 1024 char line takes much longer to "find" than 1 char line. If that 1 char line only occurs 20% of the time. It's pretty irrelevant. :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Or it other words -200ns compared to +3ns every 5 lines is not really interesting :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Makes sense to me. @stephentoub ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was primarily pointing out that it's not clear to me how these ranges were arrived at, e.g. why do we think a min of 33 and a max of 128 is interesting to encode in a perf test? What is the randomness within these ranges buying us? Is this trying to simulate typical inputs in some file format (csv, xml, json, whatever) that we believe is used with StringReader frequently and such file formats typically have such line lengths?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The line lengths are pretty random, the main reason is to expose the expected regressions, which it has. We can change this to whatever ranges you would like incl fixed e.g. [20, 20]. My use case was primarily long lines ~1000 chars in csv files. It's pretty rare files have fixed line lengths in my use cases but it does happen. Almost always lines are > 80 chars.

So yes the randomness is to better simulate real world inputs. I don't pretend to be able to imagine every use case here. They are bound to be very different. E.g. xml, json exhibits ">" like patterns. csv more line length ranges like this PR adds. Markdown or txt with prose the empty line vs longer paragraphs. But all are somewhat covered by the random ranges.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated

@adamsitnikadamsitnik 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.

@nietras thank you for you contribution! I think that we can simplify the benchmark design, PTAL at my comment.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
…ests.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik comments on pitfalls all true, another reason I would like a MaxInvocationCount feature in BDN 😉, I very much preferred being able to compare per line benchmarks, instead of an accumulated time for parsing entire text. We could still get that by simply checking if ReadLine returns null and then create new StringReader. This would also amortize the cost, while preserving getting per line results. However, I have accepted you suggestions directly. Thanks! :)

So like below, but then initialize reader in GlobalSetup too.

[Benchmark]publicvoidReadLine(){if(reader.ReadLine()==null)reader=new(_text);}

@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

@adamsitnik

Copy link
Copy Markdown
Member

should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

I like this idea! @nietras would you mind sending a separate PR?

The sooner I merge this PR the sooner we get data in our Reporting System, I am going to merge this PR now.

@adamsitnikadamsitnik 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.

LGTM, again thank you @nietras !

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nietras@adamsitnik@stephentoub@danmoseley
, '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

Add StringReaderReadLineTests - #2083

Merged
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests
Dec 6, 2021
Merged

Add StringReaderReadLineTests#2083
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests

Conversation

@nietras

Copy link
Copy Markdown
Contributor

Benchmark for dotnet/runtime#60463

cc: @danmoseley@adamsitnik

Example run comparing main m to PR pr.

BenchmarkDotNet=v0.13.1.1611-nightly, OS=Windows 10.0.19043.1266 (21H1/May2021Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK=6.0.100-rc.2.21505.57
[Host] : .NET 6.0.0 (6.0.21.48005), X64 RyuJIT
Job-BIEWJM : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
Job-IFXICU : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable,-bl:benchmarkdotnet.binlog IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainLineLengthRangeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Allocated
ReadLineJob-BIEWJMruntime-m[ 0, 0]7.208 ns0.0556 ns0.0434 ns7.194 ns7.165 ns7.288 ns1.000.00--
ReadLineJob-IFXICUruntime-pr[ 0, 0]10.112 ns0.1781 ns0.1666 ns10.105 ns9.875 ns10.438 ns1.410.02--
ReadLineJob-BIEWJMruntime-m[ 1, 8]17.751 ns0.2287 ns0.2028 ns17.686 ns17.552 ns18.192 ns1.000.000.002033 B
ReadLineJob-IFXICUruntime-pr[ 1, 8]16.998 ns0.1011 ns0.0946 ns16.992 ns16.872 ns17.212 ns0.960.010.002033 B
ReadLineJob-BIEWJMruntime-m[ 9, 32]26.336 ns0.1205 ns0.1127 ns26.273 ns26.200 ns26.510 ns1.000.000.003965 B
ReadLineJob-IFXICUruntime-pr[ 9, 32]19.896 ns0.0740 ns0.0618 ns19.879 ns19.801 ns20.046 ns0.760.000.003865 B
ReadLineJob-BIEWJMruntime-m[ 33, 128]62.594 ns0.2100 ns0.1861 ns62.584 ns62.222 ns62.928 ns1.000.000.0108185 B
ReadLineJob-IFXICUruntime-pr[ 33, 128]31.021 ns0.0902 ns0.0800 ns31.015 ns30.912 ns31.200 ns0.500.000.0110185 B
ReadLineJob-BIEWJMruntime-m[ 129,1024]319.244 ns0.6595 ns0.6169 ns319.302 ns318.088 ns320.391 ns1.000.000.06971,181 B
ReadLineJob-IFXICUruntime-pr[ 129,1024]98.174 ns0.2733 ns0.2282 ns98.123 ns97.705 ns98.567 ns0.310.000.07051,181 B

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
new (){ Min = 33, Max = 128 },
new (){ Min = 129, Max = 1024 },
};
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd expect it'd be fairly common to find real world data that alternates between longer line lengths and empty lines, with paragraphs separated by blank lines.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I can easily add a test case for [0, 1024] but that is not exactly what you are asking, would that be better or would you rather have this refactored to support the kind of data you suggest?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I should note that the test cases exactly match real world data and use case for me, namely csv content where lines are never empty and often have same range of lengths.

@danmoseleydanmoseleyOct 17, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do you mean something like

 sb.Append(newLine); // existing
if (random.Next() % 5 == 0)
sb.Append(newLine);

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

one way to add this would be to formalize a EmptyLineRatio e.g. 0.20 as part of Range (that should then be renamed). Not sure there is much value in it overall, though. It's pretty straightforward, very short lines see regressions. Others, large gains. If you mix the two, the large gains will prevail since they dominate completely. E.g. A 1024 char line takes much longer to "find" than 1 char line. If that 1 char line only occurs 20% of the time. It's pretty irrelevant. :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Or it other words -200ns compared to +3ns every 5 lines is not really interesting :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Makes sense to me. @stephentoub ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was primarily pointing out that it's not clear to me how these ranges were arrived at, e.g. why do we think a min of 33 and a max of 128 is interesting to encode in a perf test? What is the randomness within these ranges buying us? Is this trying to simulate typical inputs in some file format (csv, xml, json, whatever) that we believe is used with StringReader frequently and such file formats typically have such line lengths?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The line lengths are pretty random, the main reason is to expose the expected regressions, which it has. We can change this to whatever ranges you would like incl fixed e.g. [20, 20]. My use case was primarily long lines ~1000 chars in csv files. It's pretty rare files have fixed line lengths in my use cases but it does happen. Almost always lines are > 80 chars.

So yes the randomness is to better simulate real world inputs. I don't pretend to be able to imagine every use case here. They are bound to be very different. E.g. xml, json exhibits ">" like patterns. csv more line length ranges like this PR adds. Markdown or txt with prose the empty line vs longer paragraphs. But all are somewhat covered by the random ranges.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated

@adamsitnikadamsitnik 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.

@nietras thank you for you contribution! I think that we can simplify the benchmark design, PTAL at my comment.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
…ests.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik comments on pitfalls all true, another reason I would like a MaxInvocationCount feature in BDN 😉, I very much preferred being able to compare per line benchmarks, instead of an accumulated time for parsing entire text. We could still get that by simply checking if ReadLine returns null and then create new StringReader. This would also amortize the cost, while preserving getting per line results. However, I have accepted you suggestions directly. Thanks! :)

So like below, but then initialize reader in GlobalSetup too.

[Benchmark]publicvoidReadLine(){if(reader.ReadLine()==null)reader=new(_text);}

@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

@adamsitnik

Copy link
Copy Markdown
Member

should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

I like this idea! @nietras would you mind sending a separate PR?

The sooner I merge this PR the sooner we get data in our Reporting System, I am going to merge this PR now.

@adamsitnikadamsitnik 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.

LGTM, again thank you @nietras !

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nietras@adamsitnik@stephentoub@danmoseley
, '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

Add StringReaderReadLineTests - #2083

Merged
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests
Dec 6, 2021
Merged

Add StringReaderReadLineTests#2083
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests

Conversation

@nietras

Copy link
Copy Markdown
Contributor

Benchmark for dotnet/runtime#60463

cc: @danmoseley@adamsitnik

Example run comparing main m to PR pr.

BenchmarkDotNet=v0.13.1.1611-nightly, OS=Windows 10.0.19043.1266 (21H1/May2021Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK=6.0.100-rc.2.21505.57
[Host] : .NET 6.0.0 (6.0.21.48005), X64 RyuJIT
Job-BIEWJM : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
Job-IFXICU : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable,-bl:benchmarkdotnet.binlog IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainLineLengthRangeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Allocated
ReadLineJob-BIEWJMruntime-m[ 0, 0]7.208 ns0.0556 ns0.0434 ns7.194 ns7.165 ns7.288 ns1.000.00--
ReadLineJob-IFXICUruntime-pr[ 0, 0]10.112 ns0.1781 ns0.1666 ns10.105 ns9.875 ns10.438 ns1.410.02--
ReadLineJob-BIEWJMruntime-m[ 1, 8]17.751 ns0.2287 ns0.2028 ns17.686 ns17.552 ns18.192 ns1.000.000.002033 B
ReadLineJob-IFXICUruntime-pr[ 1, 8]16.998 ns0.1011 ns0.0946 ns16.992 ns16.872 ns17.212 ns0.960.010.002033 B
ReadLineJob-BIEWJMruntime-m[ 9, 32]26.336 ns0.1205 ns0.1127 ns26.273 ns26.200 ns26.510 ns1.000.000.003965 B
ReadLineJob-IFXICUruntime-pr[ 9, 32]19.896 ns0.0740 ns0.0618 ns19.879 ns19.801 ns20.046 ns0.760.000.003865 B
ReadLineJob-BIEWJMruntime-m[ 33, 128]62.594 ns0.2100 ns0.1861 ns62.584 ns62.222 ns62.928 ns1.000.000.0108185 B
ReadLineJob-IFXICUruntime-pr[ 33, 128]31.021 ns0.0902 ns0.0800 ns31.015 ns30.912 ns31.200 ns0.500.000.0110185 B
ReadLineJob-BIEWJMruntime-m[ 129,1024]319.244 ns0.6595 ns0.6169 ns319.302 ns318.088 ns320.391 ns1.000.000.06971,181 B
ReadLineJob-IFXICUruntime-pr[ 129,1024]98.174 ns0.2733 ns0.2282 ns98.123 ns97.705 ns98.567 ns0.310.000.07051,181 B

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
new (){ Min = 33, Max = 128 },
new (){ Min = 129, Max = 1024 },
};
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd expect it'd be fairly common to find real world data that alternates between longer line lengths and empty lines, with paragraphs separated by blank lines.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I can easily add a test case for [0, 1024] but that is not exactly what you are asking, would that be better or would you rather have this refactored to support the kind of data you suggest?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I should note that the test cases exactly match real world data and use case for me, namely csv content where lines are never empty and often have same range of lengths.

@danmoseleydanmoseleyOct 17, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do you mean something like

 sb.Append(newLine); // existing
if (random.Next() % 5 == 0)
sb.Append(newLine);

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

one way to add this would be to formalize a EmptyLineRatio e.g. 0.20 as part of Range (that should then be renamed). Not sure there is much value in it overall, though. It's pretty straightforward, very short lines see regressions. Others, large gains. If you mix the two, the large gains will prevail since they dominate completely. E.g. A 1024 char line takes much longer to "find" than 1 char line. If that 1 char line only occurs 20% of the time. It's pretty irrelevant. :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Or it other words -200ns compared to +3ns every 5 lines is not really interesting :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Makes sense to me. @stephentoub ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was primarily pointing out that it's not clear to me how these ranges were arrived at, e.g. why do we think a min of 33 and a max of 128 is interesting to encode in a perf test? What is the randomness within these ranges buying us? Is this trying to simulate typical inputs in some file format (csv, xml, json, whatever) that we believe is used with StringReader frequently and such file formats typically have such line lengths?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The line lengths are pretty random, the main reason is to expose the expected regressions, which it has. We can change this to whatever ranges you would like incl fixed e.g. [20, 20]. My use case was primarily long lines ~1000 chars in csv files. It's pretty rare files have fixed line lengths in my use cases but it does happen. Almost always lines are > 80 chars.

So yes the randomness is to better simulate real world inputs. I don't pretend to be able to imagine every use case here. They are bound to be very different. E.g. xml, json exhibits ">" like patterns. csv more line length ranges like this PR adds. Markdown or txt with prose the empty line vs longer paragraphs. But all are somewhat covered by the random ranges.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated

@adamsitnikadamsitnik 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.

@nietras thank you for you contribution! I think that we can simplify the benchmark design, PTAL at my comment.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
…ests.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik comments on pitfalls all true, another reason I would like a MaxInvocationCount feature in BDN 😉, I very much preferred being able to compare per line benchmarks, instead of an accumulated time for parsing entire text. We could still get that by simply checking if ReadLine returns null and then create new StringReader. This would also amortize the cost, while preserving getting per line results. However, I have accepted you suggestions directly. Thanks! :)

So like below, but then initialize reader in GlobalSetup too.

[Benchmark]publicvoidReadLine(){if(reader.ReadLine()==null)reader=new(_text);}

@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

@adamsitnik

Copy link
Copy Markdown
Member

should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

I like this idea! @nietras would you mind sending a separate PR?

The sooner I merge this PR the sooner we get data in our Reporting System, I am going to merge this PR now.

@adamsitnikadamsitnik 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.

LGTM, again thank you @nietras !

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nietras@adamsitnik@stephentoub@danmoseley
, '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

Add StringReaderReadLineTests - #2083

Merged
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests
Dec 6, 2021
Merged

Add StringReaderReadLineTests#2083
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests

Conversation

@nietras

Copy link
Copy Markdown
Contributor

Benchmark for dotnet/runtime#60463

cc: @danmoseley@adamsitnik

Example run comparing main m to PR pr.

BenchmarkDotNet=v0.13.1.1611-nightly, OS=Windows 10.0.19043.1266 (21H1/May2021Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK=6.0.100-rc.2.21505.57
[Host] : .NET 6.0.0 (6.0.21.48005), X64 RyuJIT
Job-BIEWJM : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
Job-IFXICU : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable,-bl:benchmarkdotnet.binlog IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainLineLengthRangeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Allocated
ReadLineJob-BIEWJMruntime-m[ 0, 0]7.208 ns0.0556 ns0.0434 ns7.194 ns7.165 ns7.288 ns1.000.00--
ReadLineJob-IFXICUruntime-pr[ 0, 0]10.112 ns0.1781 ns0.1666 ns10.105 ns9.875 ns10.438 ns1.410.02--
ReadLineJob-BIEWJMruntime-m[ 1, 8]17.751 ns0.2287 ns0.2028 ns17.686 ns17.552 ns18.192 ns1.000.000.002033 B
ReadLineJob-IFXICUruntime-pr[ 1, 8]16.998 ns0.1011 ns0.0946 ns16.992 ns16.872 ns17.212 ns0.960.010.002033 B
ReadLineJob-BIEWJMruntime-m[ 9, 32]26.336 ns0.1205 ns0.1127 ns26.273 ns26.200 ns26.510 ns1.000.000.003965 B
ReadLineJob-IFXICUruntime-pr[ 9, 32]19.896 ns0.0740 ns0.0618 ns19.879 ns19.801 ns20.046 ns0.760.000.003865 B
ReadLineJob-BIEWJMruntime-m[ 33, 128]62.594 ns0.2100 ns0.1861 ns62.584 ns62.222 ns62.928 ns1.000.000.0108185 B
ReadLineJob-IFXICUruntime-pr[ 33, 128]31.021 ns0.0902 ns0.0800 ns31.015 ns30.912 ns31.200 ns0.500.000.0110185 B
ReadLineJob-BIEWJMruntime-m[ 129,1024]319.244 ns0.6595 ns0.6169 ns319.302 ns318.088 ns320.391 ns1.000.000.06971,181 B
ReadLineJob-IFXICUruntime-pr[ 129,1024]98.174 ns0.2733 ns0.2282 ns98.123 ns97.705 ns98.567 ns0.310.000.07051,181 B

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
new (){ Min = 33, Max = 128 },
new (){ Min = 129, Max = 1024 },
};
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd expect it'd be fairly common to find real world data that alternates between longer line lengths and empty lines, with paragraphs separated by blank lines.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I can easily add a test case for [0, 1024] but that is not exactly what you are asking, would that be better or would you rather have this refactored to support the kind of data you suggest?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I should note that the test cases exactly match real world data and use case for me, namely csv content where lines are never empty and often have same range of lengths.

@danmoseleydanmoseleyOct 17, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do you mean something like

 sb.Append(newLine); // existing
if (random.Next() % 5 == 0)
sb.Append(newLine);

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

one way to add this would be to formalize a EmptyLineRatio e.g. 0.20 as part of Range (that should then be renamed). Not sure there is much value in it overall, though. It's pretty straightforward, very short lines see regressions. Others, large gains. If you mix the two, the large gains will prevail since they dominate completely. E.g. A 1024 char line takes much longer to "find" than 1 char line. If that 1 char line only occurs 20% of the time. It's pretty irrelevant. :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Or it other words -200ns compared to +3ns every 5 lines is not really interesting :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Makes sense to me. @stephentoub ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was primarily pointing out that it's not clear to me how these ranges were arrived at, e.g. why do we think a min of 33 and a max of 128 is interesting to encode in a perf test? What is the randomness within these ranges buying us? Is this trying to simulate typical inputs in some file format (csv, xml, json, whatever) that we believe is used with StringReader frequently and such file formats typically have such line lengths?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The line lengths are pretty random, the main reason is to expose the expected regressions, which it has. We can change this to whatever ranges you would like incl fixed e.g. [20, 20]. My use case was primarily long lines ~1000 chars in csv files. It's pretty rare files have fixed line lengths in my use cases but it does happen. Almost always lines are > 80 chars.

So yes the randomness is to better simulate real world inputs. I don't pretend to be able to imagine every use case here. They are bound to be very different. E.g. xml, json exhibits ">" like patterns. csv more line length ranges like this PR adds. Markdown or txt with prose the empty line vs longer paragraphs. But all are somewhat covered by the random ranges.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated

@adamsitnikadamsitnik 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.

@nietras thank you for you contribution! I think that we can simplify the benchmark design, PTAL at my comment.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
…ests.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik comments on pitfalls all true, another reason I would like a MaxInvocationCount feature in BDN 😉, I very much preferred being able to compare per line benchmarks, instead of an accumulated time for parsing entire text. We could still get that by simply checking if ReadLine returns null and then create new StringReader. This would also amortize the cost, while preserving getting per line results. However, I have accepted you suggestions directly. Thanks! :)

So like below, but then initialize reader in GlobalSetup too.

[Benchmark]publicvoidReadLine(){if(reader.ReadLine()==null)reader=new(_text);}

@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

@adamsitnik

Copy link
Copy Markdown
Member

should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

I like this idea! @nietras would you mind sending a separate PR?

The sooner I merge this PR the sooner we get data in our Reporting System, I am going to merge this PR now.

@adamsitnikadamsitnik 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.

LGTM, again thank you @nietras !

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nietras@adamsitnik@stephentoub@danmoseley
, '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

Add StringReaderReadLineTests - #2083

Merged
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests
Dec 6, 2021
Merged

Add StringReaderReadLineTests#2083
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests

Conversation

@nietras

Copy link
Copy Markdown
Contributor

Benchmark for dotnet/runtime#60463

cc: @danmoseley@adamsitnik

Example run comparing main m to PR pr.

BenchmarkDotNet=v0.13.1.1611-nightly, OS=Windows 10.0.19043.1266 (21H1/May2021Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK=6.0.100-rc.2.21505.57
[Host] : .NET 6.0.0 (6.0.21.48005), X64 RyuJIT
Job-BIEWJM : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
Job-IFXICU : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable,-bl:benchmarkdotnet.binlog IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainLineLengthRangeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Allocated
ReadLineJob-BIEWJMruntime-m[ 0, 0]7.208 ns0.0556 ns0.0434 ns7.194 ns7.165 ns7.288 ns1.000.00--
ReadLineJob-IFXICUruntime-pr[ 0, 0]10.112 ns0.1781 ns0.1666 ns10.105 ns9.875 ns10.438 ns1.410.02--
ReadLineJob-BIEWJMruntime-m[ 1, 8]17.751 ns0.2287 ns0.2028 ns17.686 ns17.552 ns18.192 ns1.000.000.002033 B
ReadLineJob-IFXICUruntime-pr[ 1, 8]16.998 ns0.1011 ns0.0946 ns16.992 ns16.872 ns17.212 ns0.960.010.002033 B
ReadLineJob-BIEWJMruntime-m[ 9, 32]26.336 ns0.1205 ns0.1127 ns26.273 ns26.200 ns26.510 ns1.000.000.003965 B
ReadLineJob-IFXICUruntime-pr[ 9, 32]19.896 ns0.0740 ns0.0618 ns19.879 ns19.801 ns20.046 ns0.760.000.003865 B
ReadLineJob-BIEWJMruntime-m[ 33, 128]62.594 ns0.2100 ns0.1861 ns62.584 ns62.222 ns62.928 ns1.000.000.0108185 B
ReadLineJob-IFXICUruntime-pr[ 33, 128]31.021 ns0.0902 ns0.0800 ns31.015 ns30.912 ns31.200 ns0.500.000.0110185 B
ReadLineJob-BIEWJMruntime-m[ 129,1024]319.244 ns0.6595 ns0.6169 ns319.302 ns318.088 ns320.391 ns1.000.000.06971,181 B
ReadLineJob-IFXICUruntime-pr[ 129,1024]98.174 ns0.2733 ns0.2282 ns98.123 ns97.705 ns98.567 ns0.310.000.07051,181 B

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
new (){ Min = 33, Max = 128 },
new (){ Min = 129, Max = 1024 },
};
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd expect it'd be fairly common to find real world data that alternates between longer line lengths and empty lines, with paragraphs separated by blank lines.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I can easily add a test case for [0, 1024] but that is not exactly what you are asking, would that be better or would you rather have this refactored to support the kind of data you suggest?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I should note that the test cases exactly match real world data and use case for me, namely csv content where lines are never empty and often have same range of lengths.

@danmoseleydanmoseleyOct 17, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do you mean something like

 sb.Append(newLine); // existing
if (random.Next() % 5 == 0)
sb.Append(newLine);

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

one way to add this would be to formalize a EmptyLineRatio e.g. 0.20 as part of Range (that should then be renamed). Not sure there is much value in it overall, though. It's pretty straightforward, very short lines see regressions. Others, large gains. If you mix the two, the large gains will prevail since they dominate completely. E.g. A 1024 char line takes much longer to "find" than 1 char line. If that 1 char line only occurs 20% of the time. It's pretty irrelevant. :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Or it other words -200ns compared to +3ns every 5 lines is not really interesting :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Makes sense to me. @stephentoub ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was primarily pointing out that it's not clear to me how these ranges were arrived at, e.g. why do we think a min of 33 and a max of 128 is interesting to encode in a perf test? What is the randomness within these ranges buying us? Is this trying to simulate typical inputs in some file format (csv, xml, json, whatever) that we believe is used with StringReader frequently and such file formats typically have such line lengths?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The line lengths are pretty random, the main reason is to expose the expected regressions, which it has. We can change this to whatever ranges you would like incl fixed e.g. [20, 20]. My use case was primarily long lines ~1000 chars in csv files. It's pretty rare files have fixed line lengths in my use cases but it does happen. Almost always lines are > 80 chars.

So yes the randomness is to better simulate real world inputs. I don't pretend to be able to imagine every use case here. They are bound to be very different. E.g. xml, json exhibits ">" like patterns. csv more line length ranges like this PR adds. Markdown or txt with prose the empty line vs longer paragraphs. But all are somewhat covered by the random ranges.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated

@adamsitnikadamsitnik 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.

@nietras thank you for you contribution! I think that we can simplify the benchmark design, PTAL at my comment.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
…ests.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik comments on pitfalls all true, another reason I would like a MaxInvocationCount feature in BDN 😉, I very much preferred being able to compare per line benchmarks, instead of an accumulated time for parsing entire text. We could still get that by simply checking if ReadLine returns null and then create new StringReader. This would also amortize the cost, while preserving getting per line results. However, I have accepted you suggestions directly. Thanks! :)

So like below, but then initialize reader in GlobalSetup too.

[Benchmark]publicvoidReadLine(){if(reader.ReadLine()==null)reader=new(_text);}

@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

@adamsitnik

Copy link
Copy Markdown
Member

should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

I like this idea! @nietras would you mind sending a separate PR?

The sooner I merge this PR the sooner we get data in our Reporting System, I am going to merge this PR now.

@adamsitnikadamsitnik 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.

LGTM, again thank you @nietras !

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nietras@adamsitnik@stephentoub@danmoseley
, '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

Add StringReaderReadLineTests - #2083

Merged
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests
Dec 6, 2021
Merged

Add StringReaderReadLineTests#2083
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests

Conversation

@nietras

Copy link
Copy Markdown
Contributor

Benchmark for dotnet/runtime#60463

cc: @danmoseley@adamsitnik

Example run comparing main m to PR pr.

BenchmarkDotNet=v0.13.1.1611-nightly, OS=Windows 10.0.19043.1266 (21H1/May2021Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK=6.0.100-rc.2.21505.57
[Host] : .NET 6.0.0 (6.0.21.48005), X64 RyuJIT
Job-BIEWJM : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
Job-IFXICU : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable,-bl:benchmarkdotnet.binlog IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainLineLengthRangeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Allocated
ReadLineJob-BIEWJMruntime-m[ 0, 0]7.208 ns0.0556 ns0.0434 ns7.194 ns7.165 ns7.288 ns1.000.00--
ReadLineJob-IFXICUruntime-pr[ 0, 0]10.112 ns0.1781 ns0.1666 ns10.105 ns9.875 ns10.438 ns1.410.02--
ReadLineJob-BIEWJMruntime-m[ 1, 8]17.751 ns0.2287 ns0.2028 ns17.686 ns17.552 ns18.192 ns1.000.000.002033 B
ReadLineJob-IFXICUruntime-pr[ 1, 8]16.998 ns0.1011 ns0.0946 ns16.992 ns16.872 ns17.212 ns0.960.010.002033 B
ReadLineJob-BIEWJMruntime-m[ 9, 32]26.336 ns0.1205 ns0.1127 ns26.273 ns26.200 ns26.510 ns1.000.000.003965 B
ReadLineJob-IFXICUruntime-pr[ 9, 32]19.896 ns0.0740 ns0.0618 ns19.879 ns19.801 ns20.046 ns0.760.000.003865 B
ReadLineJob-BIEWJMruntime-m[ 33, 128]62.594 ns0.2100 ns0.1861 ns62.584 ns62.222 ns62.928 ns1.000.000.0108185 B
ReadLineJob-IFXICUruntime-pr[ 33, 128]31.021 ns0.0902 ns0.0800 ns31.015 ns30.912 ns31.200 ns0.500.000.0110185 B
ReadLineJob-BIEWJMruntime-m[ 129,1024]319.244 ns0.6595 ns0.6169 ns319.302 ns318.088 ns320.391 ns1.000.000.06971,181 B
ReadLineJob-IFXICUruntime-pr[ 129,1024]98.174 ns0.2733 ns0.2282 ns98.123 ns97.705 ns98.567 ns0.310.000.07051,181 B

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
new (){ Min = 33, Max = 128 },
new (){ Min = 129, Max = 1024 },
};
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd expect it'd be fairly common to find real world data that alternates between longer line lengths and empty lines, with paragraphs separated by blank lines.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I can easily add a test case for [0, 1024] but that is not exactly what you are asking, would that be better or would you rather have this refactored to support the kind of data you suggest?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I should note that the test cases exactly match real world data and use case for me, namely csv content where lines are never empty and often have same range of lengths.

@danmoseleydanmoseleyOct 17, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do you mean something like

 sb.Append(newLine); // existing
if (random.Next() % 5 == 0)
sb.Append(newLine);

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

one way to add this would be to formalize a EmptyLineRatio e.g. 0.20 as part of Range (that should then be renamed). Not sure there is much value in it overall, though. It's pretty straightforward, very short lines see regressions. Others, large gains. If you mix the two, the large gains will prevail since they dominate completely. E.g. A 1024 char line takes much longer to "find" than 1 char line. If that 1 char line only occurs 20% of the time. It's pretty irrelevant. :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Or it other words -200ns compared to +3ns every 5 lines is not really interesting :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Makes sense to me. @stephentoub ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was primarily pointing out that it's not clear to me how these ranges were arrived at, e.g. why do we think a min of 33 and a max of 128 is interesting to encode in a perf test? What is the randomness within these ranges buying us? Is this trying to simulate typical inputs in some file format (csv, xml, json, whatever) that we believe is used with StringReader frequently and such file formats typically have such line lengths?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The line lengths are pretty random, the main reason is to expose the expected regressions, which it has. We can change this to whatever ranges you would like incl fixed e.g. [20, 20]. My use case was primarily long lines ~1000 chars in csv files. It's pretty rare files have fixed line lengths in my use cases but it does happen. Almost always lines are > 80 chars.

So yes the randomness is to better simulate real world inputs. I don't pretend to be able to imagine every use case here. They are bound to be very different. E.g. xml, json exhibits ">" like patterns. csv more line length ranges like this PR adds. Markdown or txt with prose the empty line vs longer paragraphs. But all are somewhat covered by the random ranges.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated

@adamsitnikadamsitnik 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.

@nietras thank you for you contribution! I think that we can simplify the benchmark design, PTAL at my comment.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
…ests.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik comments on pitfalls all true, another reason I would like a MaxInvocationCount feature in BDN 😉, I very much preferred being able to compare per line benchmarks, instead of an accumulated time for parsing entire text. We could still get that by simply checking if ReadLine returns null and then create new StringReader. This would also amortize the cost, while preserving getting per line results. However, I have accepted you suggestions directly. Thanks! :)

So like below, but then initialize reader in GlobalSetup too.

[Benchmark]publicvoidReadLine(){if(reader.ReadLine()==null)reader=new(_text);}

@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

@adamsitnik

Copy link
Copy Markdown
Member

should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

I like this idea! @nietras would you mind sending a separate PR?

The sooner I merge this PR the sooner we get data in our Reporting System, I am going to merge this PR now.

@adamsitnikadamsitnik 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.

LGTM, again thank you @nietras !

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nietras@adamsitnik@stephentoub@danmoseley
, '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

Add StringReaderReadLineTests - #2083

Merged
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests
Dec 6, 2021
Merged

Add StringReaderReadLineTests#2083
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests

Conversation

@nietras

Copy link
Copy Markdown
Contributor

Benchmark for dotnet/runtime#60463

cc: @danmoseley@adamsitnik

Example run comparing main m to PR pr.

BenchmarkDotNet=v0.13.1.1611-nightly, OS=Windows 10.0.19043.1266 (21H1/May2021Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK=6.0.100-rc.2.21505.57
[Host] : .NET 6.0.0 (6.0.21.48005), X64 RyuJIT
Job-BIEWJM : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
Job-IFXICU : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable,-bl:benchmarkdotnet.binlog IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainLineLengthRangeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Allocated
ReadLineJob-BIEWJMruntime-m[ 0, 0]7.208 ns0.0556 ns0.0434 ns7.194 ns7.165 ns7.288 ns1.000.00--
ReadLineJob-IFXICUruntime-pr[ 0, 0]10.112 ns0.1781 ns0.1666 ns10.105 ns9.875 ns10.438 ns1.410.02--
ReadLineJob-BIEWJMruntime-m[ 1, 8]17.751 ns0.2287 ns0.2028 ns17.686 ns17.552 ns18.192 ns1.000.000.002033 B
ReadLineJob-IFXICUruntime-pr[ 1, 8]16.998 ns0.1011 ns0.0946 ns16.992 ns16.872 ns17.212 ns0.960.010.002033 B
ReadLineJob-BIEWJMruntime-m[ 9, 32]26.336 ns0.1205 ns0.1127 ns26.273 ns26.200 ns26.510 ns1.000.000.003965 B
ReadLineJob-IFXICUruntime-pr[ 9, 32]19.896 ns0.0740 ns0.0618 ns19.879 ns19.801 ns20.046 ns0.760.000.003865 B
ReadLineJob-BIEWJMruntime-m[ 33, 128]62.594 ns0.2100 ns0.1861 ns62.584 ns62.222 ns62.928 ns1.000.000.0108185 B
ReadLineJob-IFXICUruntime-pr[ 33, 128]31.021 ns0.0902 ns0.0800 ns31.015 ns30.912 ns31.200 ns0.500.000.0110185 B
ReadLineJob-BIEWJMruntime-m[ 129,1024]319.244 ns0.6595 ns0.6169 ns319.302 ns318.088 ns320.391 ns1.000.000.06971,181 B
ReadLineJob-IFXICUruntime-pr[ 129,1024]98.174 ns0.2733 ns0.2282 ns98.123 ns97.705 ns98.567 ns0.310.000.07051,181 B

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
new (){ Min = 33, Max = 128 },
new (){ Min = 129, Max = 1024 },
};
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd expect it'd be fairly common to find real world data that alternates between longer line lengths and empty lines, with paragraphs separated by blank lines.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I can easily add a test case for [0, 1024] but that is not exactly what you are asking, would that be better or would you rather have this refactored to support the kind of data you suggest?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I should note that the test cases exactly match real world data and use case for me, namely csv content where lines are never empty and often have same range of lengths.

@danmoseleydanmoseleyOct 17, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do you mean something like

 sb.Append(newLine); // existing
if (random.Next() % 5 == 0)
sb.Append(newLine);

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

one way to add this would be to formalize a EmptyLineRatio e.g. 0.20 as part of Range (that should then be renamed). Not sure there is much value in it overall, though. It's pretty straightforward, very short lines see regressions. Others, large gains. If you mix the two, the large gains will prevail since they dominate completely. E.g. A 1024 char line takes much longer to "find" than 1 char line. If that 1 char line only occurs 20% of the time. It's pretty irrelevant. :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Or it other words -200ns compared to +3ns every 5 lines is not really interesting :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Makes sense to me. @stephentoub ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was primarily pointing out that it's not clear to me how these ranges were arrived at, e.g. why do we think a min of 33 and a max of 128 is interesting to encode in a perf test? What is the randomness within these ranges buying us? Is this trying to simulate typical inputs in some file format (csv, xml, json, whatever) that we believe is used with StringReader frequently and such file formats typically have such line lengths?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The line lengths are pretty random, the main reason is to expose the expected regressions, which it has. We can change this to whatever ranges you would like incl fixed e.g. [20, 20]. My use case was primarily long lines ~1000 chars in csv files. It's pretty rare files have fixed line lengths in my use cases but it does happen. Almost always lines are > 80 chars.

So yes the randomness is to better simulate real world inputs. I don't pretend to be able to imagine every use case here. They are bound to be very different. E.g. xml, json exhibits ">" like patterns. csv more line length ranges like this PR adds. Markdown or txt with prose the empty line vs longer paragraphs. But all are somewhat covered by the random ranges.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated

@adamsitnikadamsitnik 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.

@nietras thank you for you contribution! I think that we can simplify the benchmark design, PTAL at my comment.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
…ests.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik comments on pitfalls all true, another reason I would like a MaxInvocationCount feature in BDN 😉, I very much preferred being able to compare per line benchmarks, instead of an accumulated time for parsing entire text. We could still get that by simply checking if ReadLine returns null and then create new StringReader. This would also amortize the cost, while preserving getting per line results. However, I have accepted you suggestions directly. Thanks! :)

So like below, but then initialize reader in GlobalSetup too.

[Benchmark]publicvoidReadLine(){if(reader.ReadLine()==null)reader=new(_text);}

@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

@adamsitnik

Copy link
Copy Markdown
Member

should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

I like this idea! @nietras would you mind sending a separate PR?

The sooner I merge this PR the sooner we get data in our Reporting System, I am going to merge this PR now.

@adamsitnikadamsitnik 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.

LGTM, again thank you @nietras !

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nietras@adamsitnik@stephentoub@danmoseley
, '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

Add StringReaderReadLineTests - #2083

Merged
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests
Dec 6, 2021
Merged

Add StringReaderReadLineTests#2083
adamsitnik merged 12 commits into
dotnet:mainfrom
nietras:stringreader-readline-tests

Conversation

@nietras

Copy link
Copy Markdown
Contributor

Benchmark for dotnet/runtime#60463

cc: @danmoseley@adamsitnik

Example run comparing main m to PR pr.

BenchmarkDotNet=v0.13.1.1611-nightly, OS=Windows 10.0.19043.1266 (21H1/May2021Update)
AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical cores
.NET SDK=6.0.100-rc.2.21505.57
[Host] : .NET 6.0.0 (6.0.21.48005), X64 RyuJIT
Job-BIEWJM : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
Job-IFXICU : .NET 7.0.0 (42.42.42.42424), X64 RyuJIT
PowerPlanMode=00000000-0000-0000-0000-000000000000 Arguments=/p:DebugType=portable,-bl:benchmarkdotnet.binlog IterationTime=250.0000 ms MaxIterationCount=20 MinIterationCount=15 WarmupCount=1 
MethodJobToolchainLineLengthRangeMeanErrorStdDevMedianMinMaxRatioRatioSDGen 0Allocated
ReadLineJob-BIEWJMruntime-m[ 0, 0]7.208 ns0.0556 ns0.0434 ns7.194 ns7.165 ns7.288 ns1.000.00--
ReadLineJob-IFXICUruntime-pr[ 0, 0]10.112 ns0.1781 ns0.1666 ns10.105 ns9.875 ns10.438 ns1.410.02--
ReadLineJob-BIEWJMruntime-m[ 1, 8]17.751 ns0.2287 ns0.2028 ns17.686 ns17.552 ns18.192 ns1.000.000.002033 B
ReadLineJob-IFXICUruntime-pr[ 1, 8]16.998 ns0.1011 ns0.0946 ns16.992 ns16.872 ns17.212 ns0.960.010.002033 B
ReadLineJob-BIEWJMruntime-m[ 9, 32]26.336 ns0.1205 ns0.1127 ns26.273 ns26.200 ns26.510 ns1.000.000.003965 B
ReadLineJob-IFXICUruntime-pr[ 9, 32]19.896 ns0.0740 ns0.0618 ns19.879 ns19.801 ns20.046 ns0.760.000.003865 B
ReadLineJob-BIEWJMruntime-m[ 33, 128]62.594 ns0.2100 ns0.1861 ns62.584 ns62.222 ns62.928 ns1.000.000.0108185 B
ReadLineJob-IFXICUruntime-pr[ 33, 128]31.021 ns0.0902 ns0.0800 ns31.015 ns30.912 ns31.200 ns0.500.000.0110185 B
ReadLineJob-BIEWJMruntime-m[ 129,1024]319.244 ns0.6595 ns0.6169 ns319.302 ns318.088 ns320.391 ns1.000.000.06971,181 B
ReadLineJob-IFXICUruntime-pr[ 129,1024]98.174 ns0.2733 ns0.2282 ns98.123 ns97.705 ns98.567 ns0.310.000.07051,181 B

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
new (){ Min = 33, Max = 128 },
new (){ Min = 129, Max = 1024 },
};
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd expect it'd be fairly common to find real world data that alternates between longer line lengths and empty lines, with paragraphs separated by blank lines.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I can easily add a test case for [0, 1024] but that is not exactly what you are asking, would that be better or would you rather have this refactored to support the kind of data you suggest?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

I should note that the test cases exactly match real world data and use case for me, namely csv content where lines are never empty and often have same range of lengths.

@danmoseleydanmoseleyOct 17, 2021

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do you mean something like

 sb.Append(newLine); // existing
if (random.Next() % 5 == 0)
sb.Append(newLine);

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

one way to add this would be to formalize a EmptyLineRatio e.g. 0.20 as part of Range (that should then be renamed). Not sure there is much value in it overall, though. It's pretty straightforward, very short lines see regressions. Others, large gains. If you mix the two, the large gains will prevail since they dominate completely. E.g. A 1024 char line takes much longer to "find" than 1 char line. If that 1 char line only occurs 20% of the time. It's pretty irrelevant. :)

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

Or it other words -200ns compared to +3ns every 5 lines is not really interesting :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Makes sense to me. @stephentoub ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was primarily pointing out that it's not clear to me how these ranges were arrived at, e.g. why do we think a min of 33 and a max of 128 is interesting to encode in a perf test? What is the randomness within these ranges buying us? Is this trying to simulate typical inputs in some file format (csv, xml, json, whatever) that we believe is used with StringReader frequently and such file formats typically have such line lengths?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

The line lengths are pretty random, the main reason is to expose the expected regressions, which it has. We can change this to whatever ranges you would like incl fixed e.g. [20, 20]. My use case was primarily long lines ~1000 chars in csv files. It's pretty rare files have fixed line lengths in my use cases but it does happen. Almost always lines are > 80 chars.

So yes the randomness is to better simulate real world inputs. I don't pretend to be able to imagine every use case here. They are bound to be very different. E.g. xml, json exhibits ">" like patterns. csv more line length ranges like this PR adds. Markdown or txt with prose the empty line vs longer paragraphs. But all are somewhat covered by the random ranges.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated

@adamsitnikadamsitnik 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.

@nietras thank you for you contribution! I think that we can simplify the benchmark design, PTAL at my comment.

Comment threadsrc/benchmarks/micro/libraries/System.IO/StringReaderReadLineTests.cs Outdated
…ests.cs
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik comments on pitfalls all true, another reason I would like a MaxInvocationCount feature in BDN 😉, I very much preferred being able to compare per line benchmarks, instead of an accumulated time for parsing entire text. We could still get that by simply checking if ReadLine returns null and then create new StringReader. This would also amortize the cost, while preserving getting per line results. However, I have accepted you suggestions directly. Thanks! :)

So like below, but then initialize reader in GlobalSetup too.

[Benchmark]publicvoidReadLine(){if(reader.ReadLine()==null)reader=new(_text);}

@nietras

Copy link
Copy Markdown
ContributorAuthor

@adamsitnik should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

@adamsitnik

Copy link
Copy Markdown
Member

should we use this opportunity to also add test for StreamReader using this as base? Thinking use MemoryStream then.

I like this idea! @nietras would you mind sending a separate PR?

The sooner I merge this PR the sooner we get data in our Reporting System, I am going to merge this PR now.

@adamsitnikadamsitnik 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.

LGTM, again thank you @nietras !

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@nietras@adamsitnik@stephentoub@danmoseley