From 762f9d19c30399546e2698cb0c9eb77ca8fa245a Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 20 Sep 2019 14:34:21 +0200 Subject: [PATCH 1/7] fix a bug --- .../micro/corefx/System.Globalization/Perf.CompareInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/benchmarks/micro/corefx/System.Globalization/Perf.CompareInfo.cs b/src/benchmarks/micro/corefx/System.Globalization/Perf.CompareInfo.cs index 7644125bbd8..a6f55b6ca88 100644 --- a/src/benchmarks/micro/corefx/System.Globalization/Perf.CompareInfo.cs +++ b/src/benchmarks/micro/corefx/System.Globalization/Perf.CompareInfo.cs @@ -16,7 +16,7 @@ private static string GenerateInputString(char source, int count, char replaceCh char[] str = new char[count]; for (int i = 0; i < count; i++) { - str[i] = replaceChar; + str[i] = source; } str[replacePos] = replaceChar; From d8c6c0234d2fde10ebe11b184b89a2bdcb306766 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 20 Sep 2019 16:58:44 +0200 Subject: [PATCH 2/7] implement new benchmarks for culture-specific string operations --- .../Perf.StringCultureSpecific.cs | 94 +++++++++++++++++++ .../System.IO.Compression/CompressedFile.cs | 2 +- 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 src/benchmarks/micro/corefx/System.Globalization/Perf.StringCultureSpecific.cs diff --git a/src/benchmarks/micro/corefx/System.Globalization/Perf.StringCultureSpecific.cs b/src/benchmarks/micro/corefx/System.Globalization/Perf.StringCultureSpecific.cs new file mode 100644 index 00000000000..145ebb9bf10 --- /dev/null +++ b/src/benchmarks/micro/corefx/System.Globalization/Perf.StringCultureSpecific.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using BenchmarkDotNet.Attributes; +using MicroBenchmarks; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; + +namespace System.Globalization.Tests +{ + [BenchmarkCategory(Categories.CoreFX, Categories.CoreCLR)] + public class Perf_StringCultureSpecific + { + private string _value, _same, _diffAtFirstChar, _diffAtLastChar, _firstHalf, _secondHalf; + + public static IEnumerable<(CultureInfo CultureInfo, CompareOptions CompareOptions)> GetCultureOptions() + { + // Ordinal and OrdinalIgnoreCase use single execution path for all cultures, so we test it only for "en-US" + yield return (new CultureInfo("en-US"), CompareOptions.Ordinal); + yield return (new CultureInfo("en-US"), CompareOptions.OrdinalIgnoreCase); + + yield return (new CultureInfo("en-US"), CompareOptions.None); + yield return (new CultureInfo("en-US"), CompareOptions.IgnoreCase); + yield return (new CultureInfo("en-US"), CompareOptions.IgnoreSymbols); + + yield return (CultureInfo.InvariantCulture, CompareOptions.None); + yield return (CultureInfo.InvariantCulture, CompareOptions.IgnoreCase); + + // both en-US and Invariant cultures have an optimized execution path on Unix systems: + // https://github.com/dotnet/coreclr/blob/cd6bc26bdc4ac06fe2165b283eaf9fb5ff5293f4/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs#L35 + + // so we need one more culture to test the "slow path" + // Polish language has a lot of special characters, for example 'ch', 'rz', 'sz', 'cz' use two chars to express one ;) + // it also has a lot of characters with accent so we use it as an example of "complex" language + yield return (new CultureInfo("pl-PL"), CompareOptions.None); + } + + [ParamsSource(nameof(GetCultureOptions))] + public (CultureInfo CultureInfo, CompareOptions CompareOptions) CultureOptions; + + [Params( + 128, // small input that fits into stack-allocated array + 1024 * 128)] // medium size input that fits into an array rented from ArrayPool.Shared without allocation + public int Count { get; set; } + + [GlobalSetup] + public void Setup() + { + // we are using part of Alice's Adventures in Wonderland text as test data + // it contains mostly simply ascii characters, but also some "high" chars that get special treatment and hit the slow path + char[] characters = File.ReadAllText(CompressedFile.GetFilePath("alice29.txt")).ToCharArray().Take(Count).ToArray(); + _value = new string(characters); + _same = new string(characters); + _firstHalf = new string(characters.Take(Count / 2).ToArray()); + _secondHalf = new string(characters.Skip(Count / 2).ToArray()); + char[] copy = characters.ToArray(); + copy[0] = (char)(copy[0] + 1); + _diffAtFirstChar = new string(copy); + copy = characters.ToArray(); + copy[Count - 1] = (char)(copy[Count - 1] + 1); + _diffAtLastChar = new string(copy); + } + + [Benchmark] + public new void GetHashCode() => CultureOptions.CultureInfo.CompareInfo.GetHashCode(_value, CultureOptions.CompareOptions); + + [Benchmark] // the most work to do: the strings have same conent, but don't point to the same memory + public int Compare_Same() => CultureOptions.CultureInfo.CompareInfo.Compare(_value, _same, CultureOptions.CompareOptions); + + [Benchmark] // this should return quickly + public int Compare_DifferentFirstChar() => CultureOptions.CultureInfo.CompareInfo.Compare(_value, _diffAtFirstChar, CultureOptions.CompareOptions); + + [Benchmark] + public bool IsPrefix_FirstHalf() => CultureOptions.CultureInfo.CompareInfo.IsPrefix(_value, _firstHalf, CultureOptions.CompareOptions); + + [Benchmark] // this should return quickly + public bool IsPrefix_DifferentFirstChar() => CultureOptions.CultureInfo.CompareInfo.IsPrefix(_value, _diffAtFirstChar, CultureOptions.CompareOptions); + + [Benchmark] + public bool IsSuffix_SecondHalf() => CultureOptions.CultureInfo.CompareInfo.IsSuffix(_value, _secondHalf, CultureOptions.CompareOptions); + + [Benchmark] // this should return quickly + public bool IsSuffix_DifferentLastChar() => CultureOptions.CultureInfo.CompareInfo.IsSuffix(_value, _diffAtLastChar, CultureOptions.CompareOptions); + + [Benchmark] + public int IndexOf_SecondHalf() => CultureOptions.CultureInfo.CompareInfo.IndexOf(_value, _secondHalf, CultureOptions.CompareOptions); + + [Benchmark] + public int LastIndexOf_FirstHalf() => CultureOptions.CultureInfo.CompareInfo.LastIndexOf(_value, _firstHalf, CultureOptions.CompareOptions); + } +} diff --git a/src/benchmarks/micro/corefx/System.IO.Compression/CompressedFile.cs b/src/benchmarks/micro/corefx/System.IO.Compression/CompressedFile.cs index c805347a7aa..b6e13c90b36 100644 --- a/src/benchmarks/micro/corefx/System.IO.Compression/CompressedFile.cs +++ b/src/benchmarks/micro/corefx/System.IO.Compression/CompressedFile.cs @@ -32,7 +32,7 @@ public CompressedFile(string fileName, CompressionLevel compressionLevel, Func Name; - private static string GetFilePath(string fileName) + internal static string GetFilePath(string fileName) => Path.Combine( Path.GetDirectoryName(typeof(CompressedFile).Assembly.Location), "corefx", "System.IO.Compression", "TestData", From 10f96961bc9dbfd5ab28074760da4622855acc2d Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Fri, 20 Sep 2019 17:02:31 +0200 Subject: [PATCH 3/7] remove benchmarks that are now duplicated with the new ones --- .../System.Globalization/Perf.CompareInfo.cs | 136 ------------------ .../corefx/System.Runtime/Perf.String.cs | 109 +------------- .../System.Runtime/Perf.StringComparer.cs | 57 -------- 3 files changed, 2 insertions(+), 300 deletions(-) delete mode 100644 src/benchmarks/micro/corefx/System.Globalization/Perf.CompareInfo.cs delete mode 100644 src/benchmarks/micro/corefx/System.Runtime/Perf.StringComparer.cs diff --git a/src/benchmarks/micro/corefx/System.Globalization/Perf.CompareInfo.cs b/src/benchmarks/micro/corefx/System.Globalization/Perf.CompareInfo.cs deleted file mode 100644 index a6f55b6ca88..00000000000 --- a/src/benchmarks/micro/corefx/System.Globalization/Perf.CompareInfo.cs +++ /dev/null @@ -1,136 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; -using BenchmarkDotNet.Attributes; -using MicroBenchmarks; - -namespace System.Globalization.Tests -{ - [BenchmarkCategory(Categories.CoreFX)] - public class Perf_CompareInfo - { - private static string GenerateInputString(char source, int count, char replaceChar, int replacePos) - { - char[] str = new char[count]; - for (int i = 0; i < count; i++) - { - str[i] = source; - } - str[replacePos] = replaceChar; - - return new string(str); - } - - public static IEnumerable s_compareTestData => new List - { - new object[] { new CultureInfo(""), "string1", "string2", CompareOptions.None }, - new object[] { new CultureInfo("tr-TR"), "StrIng", "string", CompareOptions.IgnoreCase }, - new object[] { new CultureInfo("en-US"), "StrIng", "string", CompareOptions.OrdinalIgnoreCase }, - new object[] { new CultureInfo(""), "\u3060", "\u305F", CompareOptions.None }, - new object[] { new CultureInfo("ja-JP"), "ABCDE", "c", CompareOptions.None }, - new object[] { new CultureInfo("es-ES"), "$", "&", CompareOptions.IgnoreSymbols }, - new object[] { new CultureInfo(""), GenerateInputString('A', 10, '5', 5), GenerateInputString('A', 10, '5', 6), CompareOptions.Ordinal }, - new object[] { new CultureInfo(""), GenerateInputString('A', 100, 'X', 70), GenerateInputString('A', 100, 'X', 70), CompareOptions.OrdinalIgnoreCase }, - new object[] { new CultureInfo("ja-JP"), GenerateInputString('A', 100, 'X', 70), GenerateInputString('A', 100, 'x', 70), CompareOptions.OrdinalIgnoreCase }, - new object[] { new CultureInfo("en-US"), GenerateInputString('A', 1000, 'X', 500), GenerateInputString('A', 1000, 'X', 500), CompareOptions.None }, - new object[] { new CultureInfo("en-US"), GenerateInputString('\u3060', 1000, 'x', 500), GenerateInputString('\u3060', 1000, 'x', 10), CompareOptions.None }, - new object[] { new CultureInfo("es-ES"), GenerateInputString('\u3060', 100, '\u3059', 50), GenerateInputString('\u3060', 100, '\u3059', 50), CompareOptions.Ordinal }, - new object[] { new CultureInfo("tr-TR"), GenerateInputString('\u3060', 5000, '\u3059', 2501), GenerateInputString('\u3060', 5000, '\u3059', 2500), CompareOptions.Ordinal } - }; - - [Benchmark] - [ArgumentsSource(nameof(s_compareTestData))] - public void Compare(CultureInfo culture, string string1, string string2, CompareOptions options) - => culture.CompareInfo.Compare(string1, string2, options); - - public static IEnumerable s_indexTestData => new List - { - new object[] { new CultureInfo(""), "string1", "string2", CompareOptions.None }, - new object[] { new CultureInfo(""), "foobardzsdzs", "rddzs", CompareOptions.IgnoreCase }, - new object[] { new CultureInfo("en-US"), "StrIng", "string", CompareOptions.OrdinalIgnoreCase }, - new object[] { new CultureInfo(""), "\u3060", "\u305F", CompareOptions.None }, - new object[] { new CultureInfo("ja-JP"), "ABCDE", "c", CompareOptions.None }, - new object[] { new CultureInfo(""), "More Test's", "Tests", CompareOptions.IgnoreSymbols }, - new object[] { new CultureInfo("es-ES"), "TestFooBA\u0300R", "FooB\u00C0R", CompareOptions.IgnoreNonSpace }, - new object[] { new CultureInfo("en-US"), "Hello WorldbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbareallyreallylongHello WorldbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbareallyreallylongHello Worldbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbareallyreallylong!xyz", "~", CompareOptions.Ordinal }, - new object[] { new CultureInfo("en-US"), "Hello WorldbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbareallyreallylongHello WorldbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbareallyreallylongHello Worldbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbareallyreallylong!xyz", "w", CompareOptions.OrdinalIgnoreCase }, - new object[] { new CultureInfo("es-ES"), "Hello Worldbbbbbbbbbbbbbbcbbbbbbbbbbbbbbbbbbba!", "y", CompareOptions.Ordinal }, - new object[] { new CultureInfo(""), GenerateInputString('A', 10, '5', 5), "5", CompareOptions.Ordinal }, - new object[] { new CultureInfo(""), GenerateInputString('A', 100, 'X', 70), "x", CompareOptions.OrdinalIgnoreCase }, - new object[] { new CultureInfo("ja-JP"), GenerateInputString('A', 100, 'X', 70), "x", CompareOptions.OrdinalIgnoreCase }, - new object[] { new CultureInfo("en-US"), GenerateInputString('A', 1000, 'X', 500), "X", CompareOptions.None }, - new object[] { new CultureInfo("en-US"), GenerateInputString('\u3060', 1000, 'x', 500), "x", CompareOptions.None }, - new object[] { new CultureInfo("es-ES"), GenerateInputString('\u3060', 100, '\u3059', 50), "\u3059", CompareOptions.Ordinal } - }; - - [Benchmark] - [ArgumentsSource(nameof(s_indexTestData))] - public int IndexOf(CultureInfo culture, string source, string value, CompareOptions options) - => culture.CompareInfo.IndexOf(source, value, options); - - [Benchmark] - [ArgumentsSource(nameof(s_indexTestData))] - public int LastIndexOf(CultureInfo culture, string source, string value, CompareOptions options) - => culture.CompareInfo.LastIndexOf(source, value, options); - - public static IEnumerable s_prefixTestData => new List - { - new object[] { new CultureInfo(""), "string1", "str", CompareOptions.None }, - new object[] { new CultureInfo(""), "foobardzsdzs", "FooBarDZ", CompareOptions.IgnoreCase }, - new object[] { new CultureInfo("en-US"), "StrIng", "str", CompareOptions.OrdinalIgnoreCase }, - new object[] { new CultureInfo(""), "\u3060", "\u305F", CompareOptions.None }, - new object[] { new CultureInfo("ja-JP"), "ABCDE", "cd", CompareOptions.None }, - new object[] { new CultureInfo(""), "$", "&", CompareOptions.IgnoreSymbols }, - new object[] { new CultureInfo(""), "More's Test's", "More", CompareOptions.IgnoreSymbols }, - new object[] { new CultureInfo("es-ES"), "TestFooBA\u0300R", "FooB\u00C0R", CompareOptions.IgnoreNonSpace }, - new object[] { new CultureInfo("es-ES"), "Hello Worldbbbbbbbbbbbbbbcbbbbbbbbbbbbbbbbbbba!", "Hello World", CompareOptions.Ordinal }, - new object[] { new CultureInfo(""), GenerateInputString('A', 10, '5', 5), "AAAAA", CompareOptions.Ordinal }, - new object[] { new CultureInfo(""), GenerateInputString('A', 100, 'X', 70), new string('a', 30), CompareOptions.OrdinalIgnoreCase }, - new object[] { new CultureInfo("ja-JP"), GenerateInputString('A', 100, 'X', 70), new string('a', 70), CompareOptions.OrdinalIgnoreCase }, - new object[] { new CultureInfo("en-US"), GenerateInputString('A', 1000, 'X', 500), new string('A', 500), CompareOptions.None }, - new object[] { new CultureInfo("en-US"), GenerateInputString('\u3060', 1000, 'x', 500), new string('\u3060', 30), CompareOptions.None }, - new object[] { new CultureInfo("es-ES"), GenerateInputString('\u3060', 100, '\u3059', 50), "\u3060text", CompareOptions.Ordinal } - }; - - [Benchmark] - [ArgumentsSource(nameof(s_prefixTestData))] - public bool IsPrefix(CultureInfo culture, string source, string prefix, CompareOptions options) - => culture.CompareInfo.IsPrefix(source, prefix, options); - - public static IEnumerable s_suffixTestData => new List - { - new object[] { new CultureInfo(""), "string1", "ing1", CompareOptions.None }, - new object[] { new CultureInfo(""), "foobardzsdzs", "DZsDzS", CompareOptions.IgnoreCase }, - new object[] { new CultureInfo("en-US"), "StrIng", "str", CompareOptions.OrdinalIgnoreCase }, - new object[] { new CultureInfo(""), "\u3060", "\u305F", CompareOptions.IgnoreSymbols }, - new object[] { new CultureInfo("ja-JP"), "ABCDE", "E", CompareOptions.None }, - new object[] { new CultureInfo(""), "$", "&", CompareOptions.IgnoreSymbols }, - new object[] { new CultureInfo(""), "More's Test's", "Test", CompareOptions.IgnoreSymbols }, - new object[] { new CultureInfo("es-ES"), "TestFooBA\u0300R", "FooB\u00C0R", CompareOptions.IgnoreNonSpace }, - new object[] { new CultureInfo(""), GenerateInputString('A', 10, '5', 5), "5AAAA", CompareOptions.Ordinal }, - new object[] { new CultureInfo(""), GenerateInputString('A', 100, 'X', 70), new string('a', 30), CompareOptions.OrdinalIgnoreCase }, - new object[] { new CultureInfo("ja-JP"), GenerateInputString('A', 100, 'X', 70), "x" + new string('a', 29), CompareOptions.OrdinalIgnoreCase }, - new object[] { new CultureInfo("en-US"), GenerateInputString('A', 1000, 'X', 100), new string('A', 900), CompareOptions.None }, - new object[] { new CultureInfo("en-US"), GenerateInputString('\u3060', 1000, 'x', 500), new string('\u3060', 30), CompareOptions.None }, - new object[] { new CultureInfo("es-ES"), GenerateInputString('\u3060', 100, '\u3059', 50), "\u3060text", CompareOptions.Ordinal } - }; - - [Benchmark] - [ArgumentsSource(nameof(s_suffixTestData))] - public bool IsSuffix(CultureInfo culture, string source, string suffix, CompareOptions options) - => culture.CompareInfo.IsSuffix(source, suffix, options); - - [Benchmark] - [Arguments("foo")] - [Arguments("Exhibit \u00C0")] - [Arguments("TestFooBA\u0300RnotsolongTELLme")] - [Arguments("More Test's")] - [Arguments("$")] - [Arguments("\u3060")] - [Arguments("Hello WorldbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbareallyreallylongHello WorldbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbareallyreallylongHello Worldbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbareallyreallylong!xyz")] - public bool IsSortable(string text) - => CompareInfo.IsSortable(text); - } -} diff --git a/src/benchmarks/micro/corefx/System.Runtime/Perf.String.cs b/src/benchmarks/micro/corefx/System.Runtime/Perf.String.cs index 639d80e540f..595dc855717 100644 --- a/src/benchmarks/micro/corefx/System.Runtime/Perf.String.cs +++ b/src/benchmarks/micro/corefx/System.Runtime/Perf.String.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using System.Globalization; using System.Linq; using BenchmarkDotNet.Attributes; using MicroBenchmarks; @@ -13,6 +12,8 @@ namespace System.Tests [BenchmarkCategory(Categories.CoreCLR, Categories.CoreFX)] public class Perf_String { + // the culture-specific methods are tested in Perf_StringCultureSpecific class + public static IEnumerable TestStringSizes() { yield return new StringArguments(100); @@ -45,30 +46,6 @@ public string Concat_str_str_str_str(StringArguments size) public string Concat_CharEnumerable() => string.Concat(s_longCharEnumerable); - [Benchmark] - [ArgumentsSource(nameof(TestStringSizes))] - public bool Contains(StringArguments size) - => size.TestString1.Contains(size.Q3); - - [Benchmark] - [ArgumentsSource(nameof(TestStringSizes))] - public bool StartsWith(StringArguments size) - => size.TestString1.StartsWith(size.Q1); - - [Benchmark] - [Arguments("")] - [Arguments("TeSt!")] - [Arguments("dzsdzsDDZSDZSDZSddsz")] - public int GetHashCode(string s) - => s.GetHashCode(); - - [Benchmark] - [Arguments("")] - [Arguments("TeSt!")] - [Arguments("dzsdzsDDZSDZSDZSddsz")] - public int GetHashCodeOrdinalIgnoreCase(string s) - => StringComparer.OrdinalIgnoreCase.GetHashCode(s); - [Benchmark] [Arguments("Test", 2, " Test")] [Arguments("dzsdzsDDZSDZSDZSddsz", 7, "Test")] @@ -169,25 +146,6 @@ public string Replace_Char(string text, char oldChar, char newChar) public string Replace_String(string text, string oldValue, string newValue) => text.Replace(oldValue, newValue); - private static readonly string s_longString = - "\n"; - - private static readonly string s_tagName = " s_longString.IndexOf(s_tagName, options); - - [Benchmark] - [Arguments(StringComparison.CurrentCultureIgnoreCase)] - [Arguments(StringComparison.Ordinal)] - [Arguments(StringComparison.OrdinalIgnoreCase)] - public int LastIndexOf(StringComparison options) - => s_longString.LastIndexOf(s_tagName, options); - private static readonly char[] s_colonAndSemicolon = { ':', ';' }; [Benchmark] @@ -195,57 +153,6 @@ public int IndexOfAny() => "All the world's a stage, and all the men and women merely players: they have their exits and their entrances; and one man in his time plays many parts, his acts being seven ages." .IndexOfAny(s_colonAndSemicolon); - private CultureInfo _cultureInfo; - - [Benchmark] - [Arguments("The quick brown fox", "The quick brown fox")] - [Arguments("Th\u00e9 quick brown f\u00f2x", "Th\u00e9 quick brown f\u00f2x")] - public int Compare_Culture_invariant(string s1, string s2) - => CultureInfo.InvariantCulture.CompareInfo.Compare(s1, s2, CompareOptions.None); - - [GlobalSetup(Target = nameof(Compare_Culture_en_us))] - public void SetupCompare_Culture_en_us() => _cultureInfo = new CultureInfo("en-us"); - - [Benchmark] - [Arguments("The quick brown fox", "The quick brown fox")] - [Arguments("Th\u00e9 quick brown f\u00f2x", "Th\u00e9 quick brown f\u00f2x")] - public int Compare_Culture_en_us(string s1, string s2) - => _cultureInfo.CompareInfo.Compare(s1, s2, CompareOptions.None); - - [GlobalSetup(Target = nameof(Compare_Culture_ja_jp))] - public void SetupCompare_Culture_ja_jp() => _cultureInfo = new CultureInfo("ja-jp"); - - [Benchmark] - [Arguments("The quick brown fox", "The quick brown fox")] - [Arguments("Th\u00e9 quick brown f\u00f2x", "Th\u00e9 quick brown f\u00f2x")] - public int Compare_Culture_ja_jp(string s1, string s2) - => _cultureInfo.CompareInfo.Compare(s1, s2, CompareOptions.None); - - [Benchmark] - [Arguments(new [] {"The quick brown fox", "THE QUICK BROWN FOX"}, StringComparison.CurrentCultureIgnoreCase)] - [Arguments(new [] {"The quick brown fox", "THE QUICK BROWN FOX"}, StringComparison.Ordinal)] - [Arguments(new [] {"The quick brown fox", "THE QUICK BROWN FOX"}, StringComparison.OrdinalIgnoreCase)] - [Arguments(new [] {"Th\u00e9 quick brown f\u00f2x", "Th\u00e9 quick BROWN f\u00f2x"}, StringComparison.CurrentCultureIgnoreCase)] - [Arguments(new [] {"Th\u00e9 quick brown f\u00f2x", "Th\u00e9 quick BROWN f\u00f2x"}, StringComparison.Ordinal)] - [Arguments(new [] {"Th\u00e9 quick brown f\u00f2x", "Th\u00e9 quick BROWN f\u00f2x"}, StringComparison.OrdinalIgnoreCase)] - public int Compare(string[] strings, StringComparison comparison) // we should have two separate string arguments but we keep it that way to don't change the ID of the benchmark - => string.Compare(strings[0], strings[1], comparison); - - [Benchmark] - [Arguments("dzsdzsDDZSDZSDZSddsz", "dzsdzsDDZSDZSDZSddsz")] - public bool Equality(string s1, string s2) - => s1 == s2; - - [Benchmark] - [Arguments("dzsdzsDDZSDZSDZSddsz", "dzsdzsDDZSDZSDZSddsz")] - public bool Equals(string s1, string s2) - => s1.Equals(s2); - - [Benchmark] - [Arguments("dzsdzsDDZSDZSDZSddsz", "dzsdzsDDZSDZSDZSddsz")] - public bool EqualsIgnoreCase(string s1, string s2) - => s1.Equals(s2, StringComparison.OrdinalIgnoreCase); - [Benchmark] [Arguments("Testing {0}, {0:C}, {0:D5}, {0:E} - {0:F4}{0:G}{0:N} {0:X} !!", 8)] [Arguments("Testing {0}, {0:C}, {0:E} - {0:F4}{0:G}{0:N} , !!", 3.14159)] @@ -345,18 +252,6 @@ private static char getStringCharNoInline(string str, int index) { return str[index]; } - -#if !NETFRAMEWORK // API added in .NET Core 2.1 - [Benchmark] - [Arguments("This is a very nice sentence", "bad", StringComparison.CurrentCultureIgnoreCase)] - [Arguments("This is a very nice sentence", "bad", StringComparison.Ordinal)] - [Arguments("This is a very nice sentence", "bad", StringComparison.OrdinalIgnoreCase)] - [Arguments("This is a very nice sentence", "nice", StringComparison.CurrentCultureIgnoreCase)] - [Arguments("This is a very nice sentence", "nice", StringComparison.Ordinal)] - [Arguments("This is a very nice sentence", "nice", StringComparison.OrdinalIgnoreCase)] - public bool Contains(String text, String value, StringComparison comparisonType) - => text.Contains(value, comparisonType); -#endif } public class StringArguments diff --git a/src/benchmarks/micro/corefx/System.Runtime/Perf.StringComparer.cs b/src/benchmarks/micro/corefx/System.Runtime/Perf.StringComparer.cs deleted file mode 100644 index a93d21deec3..00000000000 --- a/src/benchmarks/micro/corefx/System.Runtime/Perf.StringComparer.cs +++ /dev/null @@ -1,57 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Extensions; -using MicroBenchmarks; - -namespace System.Tests -{ - [BenchmarkCategory(Categories.CoreFX, Categories.CoreCLR)] - public class Perf_StringComparer - { - [Params( - 128, // stackalloc path - 1024 * 256)] // ArrayPool.Shared.Rent without allocation - public int Count { get; set; } - - [ParamsAllValues] - public StringComparison Comparison { get; set; } - - private string _input, _same; - private StringComparer _comparer; - - [GlobalSetup] - public void Setup() - { - _comparer = GetStringComparer(); - char[] characters = ValuesGenerator.Array(Count); - _input = new string(characters); - _same = new string(characters); - } - - [Benchmark] - public int GetStringHashCode() => _comparer.GetHashCode(_input); - - [Benchmark] - public int CompareSame() => _comparer.Compare(_input, _same); - - private StringComparer GetStringComparer() - { - switch (Comparison) - { - case StringComparison.CurrentCulture: - return StringComparer.CurrentCulture; - case StringComparison.CurrentCultureIgnoreCase: - return StringComparer.CurrentCultureIgnoreCase; - case StringComparison.InvariantCulture: - return StringComparer.InvariantCulture; - case StringComparison.InvariantCultureIgnoreCase: - return StringComparer.InvariantCultureIgnoreCase; - case StringComparison.Ordinal: - return StringComparer.Ordinal; - case StringComparison.OrdinalIgnoreCase: - return StringComparer.OrdinalIgnoreCase; - default: - throw new NotSupportedException($"{Comparison} is not supported"); - } - } - } -} From d0ab5bd83b89a8a5762e0c642d4651ec341f63bf Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 23 Sep 2019 11:51:07 +0200 Subject: [PATCH 4/7] move string hashing benchmark to a dedicated type (it has two size-dependent execution paths) --- .../corefx/System.Globalization/StringHash.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/benchmarks/micro/corefx/System.Globalization/StringHash.cs diff --git a/src/benchmarks/micro/corefx/System.Globalization/StringHash.cs b/src/benchmarks/micro/corefx/System.Globalization/StringHash.cs new file mode 100644 index 00000000000..e6595ba00bd --- /dev/null +++ b/src/benchmarks/micro/corefx/System.Globalization/StringHash.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using BenchmarkDotNet.Attributes; +using MicroBenchmarks; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; + +namespace System.Globalization.Tests +{ + [BenchmarkCategory(Categories.CoreFX, Categories.CoreCLR)] + public class StringHash + { + public static IEnumerable<(CultureInfo CultureInfo, CompareOptions CompareOptions)> GetOptions() + { + // Ordinal and OrdinalIgnoreCase use single execution path for all cultures, so we test it only for "en-US" + yield return (new CultureInfo("en-US"), CompareOptions.Ordinal); + yield return (new CultureInfo("en-US"), CompareOptions.OrdinalIgnoreCase); + + yield return (new CultureInfo("en-US"), CompareOptions.None); + yield return (new CultureInfo("en-US"), CompareOptions.IgnoreCase); + + yield return (CultureInfo.InvariantCulture, CompareOptions.None); + yield return (CultureInfo.InvariantCulture, CompareOptions.IgnoreCase); + } + + [ParamsSource(nameof(GetOptions))] + public (CultureInfo CultureInfo, CompareOptions CompareOptions) Options; + + [Params( + 128, // small input that fits into stack-allocated array https://github.com/dotnet/coreclr/blob/c6675ef2e22474d6222d054ae3d022c01eda9b6d/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs#L824 + 1024 * 128)] // medium size input that fits into an array rented from ArrayPool.Shared without allocation + public int Count { get; set; } + + private string _value; + + [GlobalSetup] // we are using part of Alice's Adventures in Wonderland text as test data + public void Setup() => _value = new string(File.ReadAllText(CompressedFile.GetFilePath("alice29.txt")).Take(Count).ToArray()); + + [Benchmark] + public new void GetHashCode() => Options.CultureInfo.CompareInfo.GetHashCode(_value, Options.CompareOptions); + } +} From a575558b82fa5e1b01bca230b55a1958db450b8b Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 23 Sep 2019 11:51:47 +0200 Subject: [PATCH 5/7] move string compare benchmarks to a dedicated type (it has single execution path, not dependent on size or high chars) --- .../System.Globalization/StringEquality.cs | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/benchmarks/micro/corefx/System.Globalization/StringEquality.cs diff --git a/src/benchmarks/micro/corefx/System.Globalization/StringEquality.cs b/src/benchmarks/micro/corefx/System.Globalization/StringEquality.cs new file mode 100644 index 00000000000..a464112b0a3 --- /dev/null +++ b/src/benchmarks/micro/corefx/System.Globalization/StringEquality.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using BenchmarkDotNet.Attributes; +using MicroBenchmarks; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; + +namespace System.Globalization.Tests +{ + [BenchmarkCategory(Categories.CoreFX, Categories.CoreCLR)] + public class StringEquality + { + private string _value, _same, _sameUpper, _diffAtFirstChar; + + public static IEnumerable<(CultureInfo CultureInfo, CompareOptions CompareOptions)> GetOptions() + { + // Ordinal and OrdinalIgnoreCase use single execution path for all cultures, so we test it only for "en-US" + yield return (new CultureInfo("en-US"), CompareOptions.Ordinal); + yield return (new CultureInfo("en-US"), CompareOptions.OrdinalIgnoreCase); + + // the most popular culture: + yield return (new CultureInfo("en-US"), CompareOptions.None); + yield return (new CultureInfo("en-US"), CompareOptions.IgnoreCase); + + // two very common use cases: + yield return (CultureInfo.InvariantCulture, CompareOptions.None); + yield return (CultureInfo.InvariantCulture, CompareOptions.IgnoreCase); + + // IgnoreSymbols and IgnoreNonSpace are rarely used, this is why we test it only for a single culture + yield return (new CultureInfo("en-US"), CompareOptions.IgnoreSymbols); + yield return (new CultureInfo("en-US"), CompareOptions.IgnoreNonSpace); + + // Polish language has a lot of special characters, for example 'ch', 'rz', 'sz', 'cz' use two chars to express one ;) + // it also has a lot of characters with accent so we use it as an example of a "complex" language + yield return (new CultureInfo("pl-PL"), CompareOptions.None); + } + + [ParamsSource(nameof(GetOptions))] + public (CultureInfo CultureInfo, CompareOptions CompareOptions) Options; + + [Params(1024)] // single execution path = single test case + public int Count { get; set; } + + [GlobalSetup] + public void Setup() + { + // we are using part of Alice's Adventures in Wonderland text as test data + char[] characters = File.ReadAllText(CompressedFile.GetFilePath("alice29.txt")).Take(Count).ToArray(); + _value = new string(characters); + _same = new string(characters); + _sameUpper = _same.ToUpper(); + char[] copy = characters.ToArray(); + copy[0] = (char)(copy[0] + 1); + _diffAtFirstChar = new string(copy); + } + + [Benchmark] // the most work to do: the strings have same conent, but don't point to the same memory + public int Compare_Same() => Options.CultureInfo.CompareInfo.Compare(_value, _same, Options.CompareOptions); + + [Benchmark] // the most work to do for IgnoreCase: every char needs to be compared and uppercased + public int Compare_Same_Upper() => Options.CultureInfo.CompareInfo.Compare(_value, _sameUpper, Options.CompareOptions); + + [Benchmark] // this should return quickly + public int Compare_DifferentFirstChar() => Options.CultureInfo.CompareInfo.Compare(_value, _diffAtFirstChar, Options.CompareOptions); + } +} From 261f15167cae0aefad0cbe1a05c951957a37d4e2 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 23 Sep 2019 11:52:54 +0200 Subject: [PATCH 6/7] address code review feedback --- .../Perf.StringCultureSpecific.cs | 94 ------------------- .../System.Globalization/StringSearch.cs | 94 +++++++++++++++++++ 2 files changed, 94 insertions(+), 94 deletions(-) delete mode 100644 src/benchmarks/micro/corefx/System.Globalization/Perf.StringCultureSpecific.cs create mode 100644 src/benchmarks/micro/corefx/System.Globalization/StringSearch.cs diff --git a/src/benchmarks/micro/corefx/System.Globalization/Perf.StringCultureSpecific.cs b/src/benchmarks/micro/corefx/System.Globalization/Perf.StringCultureSpecific.cs deleted file mode 100644 index 145ebb9bf10..00000000000 --- a/src/benchmarks/micro/corefx/System.Globalization/Perf.StringCultureSpecific.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using BenchmarkDotNet.Attributes; -using MicroBenchmarks; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; - -namespace System.Globalization.Tests -{ - [BenchmarkCategory(Categories.CoreFX, Categories.CoreCLR)] - public class Perf_StringCultureSpecific - { - private string _value, _same, _diffAtFirstChar, _diffAtLastChar, _firstHalf, _secondHalf; - - public static IEnumerable<(CultureInfo CultureInfo, CompareOptions CompareOptions)> GetCultureOptions() - { - // Ordinal and OrdinalIgnoreCase use single execution path for all cultures, so we test it only for "en-US" - yield return (new CultureInfo("en-US"), CompareOptions.Ordinal); - yield return (new CultureInfo("en-US"), CompareOptions.OrdinalIgnoreCase); - - yield return (new CultureInfo("en-US"), CompareOptions.None); - yield return (new CultureInfo("en-US"), CompareOptions.IgnoreCase); - yield return (new CultureInfo("en-US"), CompareOptions.IgnoreSymbols); - - yield return (CultureInfo.InvariantCulture, CompareOptions.None); - yield return (CultureInfo.InvariantCulture, CompareOptions.IgnoreCase); - - // both en-US and Invariant cultures have an optimized execution path on Unix systems: - // https://github.com/dotnet/coreclr/blob/cd6bc26bdc4ac06fe2165b283eaf9fb5ff5293f4/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs#L35 - - // so we need one more culture to test the "slow path" - // Polish language has a lot of special characters, for example 'ch', 'rz', 'sz', 'cz' use two chars to express one ;) - // it also has a lot of characters with accent so we use it as an example of "complex" language - yield return (new CultureInfo("pl-PL"), CompareOptions.None); - } - - [ParamsSource(nameof(GetCultureOptions))] - public (CultureInfo CultureInfo, CompareOptions CompareOptions) CultureOptions; - - [Params( - 128, // small input that fits into stack-allocated array - 1024 * 128)] // medium size input that fits into an array rented from ArrayPool.Shared without allocation - public int Count { get; set; } - - [GlobalSetup] - public void Setup() - { - // we are using part of Alice's Adventures in Wonderland text as test data - // it contains mostly simply ascii characters, but also some "high" chars that get special treatment and hit the slow path - char[] characters = File.ReadAllText(CompressedFile.GetFilePath("alice29.txt")).ToCharArray().Take(Count).ToArray(); - _value = new string(characters); - _same = new string(characters); - _firstHalf = new string(characters.Take(Count / 2).ToArray()); - _secondHalf = new string(characters.Skip(Count / 2).ToArray()); - char[] copy = characters.ToArray(); - copy[0] = (char)(copy[0] + 1); - _diffAtFirstChar = new string(copy); - copy = characters.ToArray(); - copy[Count - 1] = (char)(copy[Count - 1] + 1); - _diffAtLastChar = new string(copy); - } - - [Benchmark] - public new void GetHashCode() => CultureOptions.CultureInfo.CompareInfo.GetHashCode(_value, CultureOptions.CompareOptions); - - [Benchmark] // the most work to do: the strings have same conent, but don't point to the same memory - public int Compare_Same() => CultureOptions.CultureInfo.CompareInfo.Compare(_value, _same, CultureOptions.CompareOptions); - - [Benchmark] // this should return quickly - public int Compare_DifferentFirstChar() => CultureOptions.CultureInfo.CompareInfo.Compare(_value, _diffAtFirstChar, CultureOptions.CompareOptions); - - [Benchmark] - public bool IsPrefix_FirstHalf() => CultureOptions.CultureInfo.CompareInfo.IsPrefix(_value, _firstHalf, CultureOptions.CompareOptions); - - [Benchmark] // this should return quickly - public bool IsPrefix_DifferentFirstChar() => CultureOptions.CultureInfo.CompareInfo.IsPrefix(_value, _diffAtFirstChar, CultureOptions.CompareOptions); - - [Benchmark] - public bool IsSuffix_SecondHalf() => CultureOptions.CultureInfo.CompareInfo.IsSuffix(_value, _secondHalf, CultureOptions.CompareOptions); - - [Benchmark] // this should return quickly - public bool IsSuffix_DifferentLastChar() => CultureOptions.CultureInfo.CompareInfo.IsSuffix(_value, _diffAtLastChar, CultureOptions.CompareOptions); - - [Benchmark] - public int IndexOf_SecondHalf() => CultureOptions.CultureInfo.CompareInfo.IndexOf(_value, _secondHalf, CultureOptions.CompareOptions); - - [Benchmark] - public int LastIndexOf_FirstHalf() => CultureOptions.CultureInfo.CompareInfo.LastIndexOf(_value, _firstHalf, CultureOptions.CompareOptions); - } -} diff --git a/src/benchmarks/micro/corefx/System.Globalization/StringSearch.cs b/src/benchmarks/micro/corefx/System.Globalization/StringSearch.cs new file mode 100644 index 00000000000..43ae001b610 --- /dev/null +++ b/src/benchmarks/micro/corefx/System.Globalization/StringSearch.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using BenchmarkDotNet.Attributes; +using MicroBenchmarks; +using System.Collections.Generic; +using System.Linq; + +namespace System.Globalization.Tests +{ + [BenchmarkCategory(Categories.CoreFX, Categories.CoreCLR)] + public class StringSearch + { + private string _value, _diffAtFirstChar, _diffAtLastChar, _firstHalf, _secondHalf; + + public static IEnumerable<(CultureInfo CultureInfo, CompareOptions CompareOptions, bool highChars)> GetOptions() + { + // Ordinal and OrdinalIgnoreCase use single execution path for all cultures, so we test it only for "en-US" + // without enforcing highChars - the execution path would be the same + yield return (new CultureInfo("en-US"), CompareOptions.Ordinal, false); + yield return (new CultureInfo("en-US"), CompareOptions.OrdinalIgnoreCase, false); + + yield return (new CultureInfo("en-US"), CompareOptions.None, false); // no high chars = fast path (managed code on Unix) + yield return (new CultureInfo("en-US"), CompareOptions.None, true); // high chars = slow path (call to ICU on Unix) + yield return (new CultureInfo("en-US"), CompareOptions.IgnoreCase, false); + yield return (new CultureInfo("en-US"), CompareOptions.IgnoreCase, true); + + // IgnoreSymbols always uses the slow path + // https://github.com/dotnet/coreclr/blob/cd6bc26bdc4ac06fe2165b283eaf9fb5ff5293f4/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs#L911-L912 + yield return (new CultureInfo("en-US"), CompareOptions.IgnoreSymbols, false); + + // too rarely used to test both slow and fast path + yield return (new CultureInfo("en-US"), CompareOptions.IgnoreNonSpace, false); + + yield return (CultureInfo.InvariantCulture, CompareOptions.None, false); + yield return (CultureInfo.InvariantCulture, CompareOptions.None, true); + yield return (CultureInfo.InvariantCulture, CompareOptions.IgnoreCase, false); + yield return (CultureInfo.InvariantCulture, CompareOptions.IgnoreCase, true); + + // both en-US and Invariant cultures have an optimized execution path on Unix systems: + // https://github.com/dotnet/coreclr/blob/cd6bc26bdc4ac06fe2165b283eaf9fb5ff5293f4/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs#L35 + // so we need one more culture to test the "slow path" + // Polish language has a lot of special characters, for example 'ch', 'rz', 'sz', 'cz' use two chars to express one ;) + // it also has a lot of characters with accent so we use it as an example of "complex" language + yield return (new CultureInfo("pl-PL"), CompareOptions.None, false); + } + + [ParamsSource(nameof(GetOptions))] + public (CultureInfo CultureInfo, CompareOptions CompareOptions, bool highChars) Options; + + [GlobalSetup] + public void Setup() + { + // we are using simple input to mimic "real world test case" + char[] characters = ".NET Conf provides a wide selection of live sessions streaming here that feature speakers from the community and .NET product teams. It is a chance to learn, ask questions live, and get inspired for your next software project.".ToArray(); + + // we ensure that high chars are present by inserting one + if (Options.highChars) + { + characters[0] = (char)0x81; // at the begning for IndexOf and StartsWith + characters[characters.Length - 1] = (char)0x81; // at the begning for LastIndexOf and EndsWith + } + + _value = new string(characters); + _firstHalf = new string(characters.Take(characters.Length / 2).ToArray()); + _secondHalf = new string(characters.Skip(characters.Length / 2).ToArray()); + char[] copy = characters.ToArray(); + copy[0] = (char)(copy[0] + 1); + _diffAtFirstChar = new string(copy); + copy = characters.ToArray(); + copy[characters.Length - 1] = (char)(copy[characters.Length - 1] + 1); + _diffAtLastChar = new string(copy); + } + + [Benchmark] + public bool IsPrefix_FirstHalf() => Options.CultureInfo.CompareInfo.IsPrefix(_value, _firstHalf, Options.CompareOptions); + + [Benchmark] // this should return quickly + public bool IsPrefix_DifferentFirstChar() => Options.CultureInfo.CompareInfo.IsPrefix(_value, _diffAtFirstChar, Options.CompareOptions); + + [Benchmark] + public bool IsSuffix_SecondHalf() => Options.CultureInfo.CompareInfo.IsSuffix(_value, _secondHalf, Options.CompareOptions); + + [Benchmark] // this should return quickly + public bool IsSuffix_DifferentLastChar() => Options.CultureInfo.CompareInfo.IsSuffix(_value, _diffAtLastChar, Options.CompareOptions); + + [Benchmark] + public int IndexOf_Word_NotFound() => Options.CultureInfo.CompareInfo.IndexOf(_value, "word", Options.CompareOptions); + + [Benchmark] + public int LastIndexOf_Word_NotFound() => Options.CultureInfo.CompareInfo.LastIndexOf(_value, "word", Options.CompareOptions); + } +} From 4c858c2ceee577eacc1e083a85f09db8a3590c6a Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 23 Sep 2019 14:48:13 +0200 Subject: [PATCH 7/7] fix style consistency --- .../micro/corefx/System.Globalization/StringEquality.cs | 2 +- src/benchmarks/micro/corefx/System.Globalization/StringHash.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/benchmarks/micro/corefx/System.Globalization/StringEquality.cs b/src/benchmarks/micro/corefx/System.Globalization/StringEquality.cs index a464112b0a3..ccfb464851a 100644 --- a/src/benchmarks/micro/corefx/System.Globalization/StringEquality.cs +++ b/src/benchmarks/micro/corefx/System.Globalization/StringEquality.cs @@ -43,7 +43,7 @@ public class StringEquality public (CultureInfo CultureInfo, CompareOptions CompareOptions) Options; [Params(1024)] // single execution path = single test case - public int Count { get; set; } + public int Count; [GlobalSetup] public void Setup() diff --git a/src/benchmarks/micro/corefx/System.Globalization/StringHash.cs b/src/benchmarks/micro/corefx/System.Globalization/StringHash.cs index e6595ba00bd..47123d55841 100644 --- a/src/benchmarks/micro/corefx/System.Globalization/StringHash.cs +++ b/src/benchmarks/micro/corefx/System.Globalization/StringHash.cs @@ -33,7 +33,7 @@ public class StringHash [Params( 128, // small input that fits into stack-allocated array https://github.com/dotnet/coreclr/blob/c6675ef2e22474d6222d054ae3d022c01eda9b6d/src/System.Private.CoreLib/shared/System/Globalization/CompareInfo.Unix.cs#L824 1024 * 128)] // medium size input that fits into an array rented from ArrayPool.Shared without allocation - public int Count { get; set; } + public int Count; private string _value;