From 2565538fd30a9669198b2c611b9fcdabd602bac0 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Sun, 8 Jan 2023 03:37:17 +0300 Subject: [PATCH 1/8] [Text] improve benchmarks. add BuildManySmallTexts benchmark --- .../Text/HugeTextLayout.cs | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs index e1b3c6a99c..e696b976ad 100644 --- a/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs +++ b/tests/Avalonia.Benchmarks/Text/HugeTextLayout.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Avalonia.Media; using Avalonia.Media.TextFormatting; using Avalonia.UnitTests; @@ -10,9 +11,18 @@ namespace Avalonia.Benchmarks.Text; public class HugeTextLayout : IDisposable { private readonly IDisposable _app; + private string[] _manySmallStrings; + private static Random _rand = new Random(); + + private static string RandomString(int length) + { + const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&?%$@"; + return new string(Enumerable.Repeat(chars, length).Select(s => s[_rand.Next(s.Length)]).ToArray()); + } public HugeTextLayout() { + _manySmallStrings = Enumerable.Range(0, 1000).Select(x => RandomString(_rand.Next(2, 15))).ToArray(); _app = UnitTestApplication.Start( TestServices.StyledWindow.With( renderInterface: new NullRenderingPlatform(), @@ -29,11 +39,13 @@ One should, however, not forget that concentration of violations of the strategi In a loose sense concentration of the center of the critical thinking provides a deep insight into the emergency planning. The comparison is quite a powerful matter. Resulting from review or analysis of threats and opportunities, we can presume that either significant improvement or basics of planning and scheduling reveals the patterns of the final draft. Therefore, the concept of the crucial component can be treated as the only solution. One should, nevertheless, consider that the exceptional results of the diverse sources of information gives an overview of the production cycle. It should rather be regarded as an integral part of the direct access to key resources. -Admitting that the possibility of achieving the results of the constructive criticism, as far as the strategic management is questionable, cannot rely only on the critical thinking. It may reveal how the systems approach partially the comprehensive project management. We must be ready for outline design stage and network development investigation of every contradiction between the effective time management and the efficient decision the network development. Everyone understands what it takes to the draft analysis and prior decisions and early design solutions. In any case, we can systematically change the mechanism of the sources and influences of the continuing financing doctrine. This could exceedingly be a result of a task analysis the hardware maintenance. The real reason of the strategic planning seemingly the influence on eventual productivity. Everyone understands what it takes to the well-known practice. Therefore, the concept of the productivity boost can be treated as the only solution the driving factor. It may reveal how the matters of peculiar interest slowly the goals and objectives or the diverse sources of information the positive influence of any major outcomes complete failure of the supposed theory. +Admitting that the possibility of achieving the results of the constructive criticism, as far as the strategic management is questionable, cannot rely only on the critical thinking. It may reveal how the systems approach partially the comprehensive project management. We must be ready for outline design stage and network development investigation of every contradiction between the effective time management and the efficient decision the network development. +Everyone understands what it takes to the draft analysis and prior decisions and early design solutions. In any case, we can systematically change the mechanism of the sources and influences of the continuing financing doctrine. This could exceedingly be a result of a task analysis the hardware maintenance. The real reason of the strategic planning seemingly the influence on eventual productivity. Everyone understands what it takes to the well-known practice. Therefore, the concept of the productivity boost can be treated as the only solution the driving factor. +It may reveal how the matters of peculiar interest slowly the goals and objectives or the diverse sources of information the positive influence of any major outcomes complete failure of the supposed theory. In respect that the structure of the sufficient amount poses problems and challenges for both the set of related commands and controls and the ability bias."; [Benchmark] - public TextLayout BuildTextLayout() => new TextLayout(Text, Typeface.Default, 12d, Brushes.Black); + public TextLayout BuildTextLayout() => MakeLayout(Text); private const string Emojis = @"๐Ÿ˜€ ๐Ÿ˜ ๐Ÿ˜‚ ๐Ÿคฃ ๐Ÿ˜ƒ ๐Ÿ˜„ ๐Ÿ˜… ๐Ÿ˜† ๐Ÿ˜‰ ๐Ÿ˜Š ๐Ÿ˜‹ ๐Ÿ˜Ž ๐Ÿ˜ ๐Ÿ˜˜ ๐Ÿฅฐ ๐Ÿ˜— ๐Ÿ˜™ ๐Ÿ˜š โ˜บ๏ธ ๐Ÿ™‚ ๐Ÿค— ๐Ÿคฉ ๐Ÿค” ๐Ÿคจ ๐Ÿ˜ ๐Ÿ˜‘ ๐Ÿ˜ถ ๐Ÿ™„ ๐Ÿ˜ ๐Ÿ˜ฃ ๐Ÿ˜ฅ ๐Ÿ˜ฎ ๐Ÿค ๐Ÿ˜ฏ ๐Ÿ˜ช ๐Ÿ˜ซ ๐Ÿ˜ด ๐Ÿ˜Œ ๐Ÿ˜› ๐Ÿ˜œ ๐Ÿ˜ ๐Ÿคค ๐Ÿ˜’ ๐Ÿ˜“ ๐Ÿ˜” ๐Ÿ˜• ๐Ÿ™ƒ ๐Ÿค‘ ๐Ÿ˜ฒ โ˜น๏ธ ๐Ÿ™ ๐Ÿ˜– ๐Ÿ˜ž ๐Ÿ˜Ÿ ๐Ÿ˜ค ๐Ÿ˜ข ๐Ÿ˜ญ ๐Ÿ˜ฆ ๐Ÿ˜ง ๐Ÿ˜จ ๐Ÿ˜ฉ ๐Ÿคฏ ๐Ÿ˜ฌ ๐Ÿ˜ฐ ๐Ÿ˜ฑ ๐Ÿฅต ๐Ÿฅถ ๐Ÿ˜ณ ๐Ÿคช ๐Ÿ˜ต ๐Ÿ˜ก ๐Ÿ˜  ๐Ÿคฌ ๐Ÿ˜ท ๐Ÿค’ ๐Ÿค• ๐Ÿคข ๐Ÿคฎ ๐Ÿคง ๐Ÿ˜‡ ๐Ÿค  ๐Ÿคก ๐Ÿฅณ ๐Ÿฅด ๐Ÿฅบ ๐Ÿคฅ ๐Ÿคซ ๐Ÿคญ ๐Ÿง ๐Ÿค“ ๐Ÿ˜ˆ ๐Ÿ‘ฟ ๐Ÿ‘น ๐Ÿ‘บ ๐Ÿ’€ ๐Ÿ‘ป ๐Ÿ‘ฝ ๐Ÿค– ๐Ÿ’ฉ ๐Ÿ˜บ ๐Ÿ˜ธ ๐Ÿ˜น ๐Ÿ˜ป ๐Ÿ˜ผ ๐Ÿ˜ฝ ๐Ÿ™€ ๐Ÿ˜ฟ ๐Ÿ˜พ ๐Ÿ‘ถ ๐Ÿ‘ง ๐Ÿง’ ๐Ÿ‘ฆ ๐Ÿ‘ฉ ๐Ÿง‘ ๐Ÿ‘จ ๐Ÿ‘ต ๐Ÿง“ ๐Ÿ‘ด ๐Ÿ‘ฒ ๐Ÿ‘ณโ€โ™€๏ธ ๐Ÿ‘ณโ€โ™‚๏ธ ๐Ÿง• ๐Ÿง” ๐Ÿ‘ฑโ€โ™‚๏ธ ๐Ÿ‘ฑโ€โ™€๏ธ ๐Ÿ‘จโ€๐Ÿฆฐ ๐Ÿ‘ฉโ€๐Ÿฆฐ ๐Ÿ‘จโ€๐Ÿฆฑ ๐Ÿ‘ฉโ€๐Ÿฆฑ ๐Ÿ‘จโ€๐Ÿฆฒ ๐Ÿ‘ฉโ€๐Ÿฆฒ ๐Ÿ‘จโ€๐Ÿฆณ ๐Ÿ‘ฉโ€๐Ÿฆณ ๐Ÿฆธโ€โ™€๏ธ ๐Ÿฆธโ€โ™‚๏ธ ๐Ÿฆนโ€โ™€๏ธ ๐Ÿฆนโ€โ™‚๏ธ ๐Ÿ‘ฎโ€โ™€๏ธ ๐Ÿ‘ฎโ€โ™‚๏ธ ๐Ÿ‘ทโ€โ™€๏ธ ๐Ÿ‘ทโ€โ™‚๏ธ ๐Ÿ’‚โ€โ™€๏ธ ๐Ÿ’‚โ€โ™‚๏ธ ๐Ÿ•ต๏ธโ€โ™€๏ธ ๐Ÿ•ต๏ธโ€โ™‚๏ธ ๐Ÿ‘ฉโ€โš•๏ธ ๐Ÿ‘จโ€โš•๏ธ ๐Ÿ‘ฉโ€๐ŸŒพ ๐Ÿ‘จโ€๐ŸŒพ ๐Ÿ‘ฉโ€๐Ÿณ ๐Ÿ‘จโ€๐Ÿณ ๐Ÿ‘ฉโ€๐ŸŽ“ ๐Ÿ‘จโ€๐ŸŽ“ ๐Ÿ‘ฉโ€๐ŸŽค ๐Ÿ‘จโ€๐ŸŽค ๐Ÿ‘ฉโ€๐Ÿซ ๐Ÿ‘จโ€๐Ÿซ ๐Ÿ‘ฉโ€๐Ÿญ ๐Ÿ‘จโ€๐Ÿญ ๐Ÿ‘ฉโ€๐Ÿ’ป ๐Ÿ‘จโ€๐Ÿ’ป ๐Ÿ‘ฉโ€๐Ÿ’ผ ๐Ÿ‘จโ€๐Ÿ’ผ ๐Ÿ‘ฉโ€๐Ÿ”ง ๐Ÿ‘จโ€๐Ÿ”ง ๐Ÿ‘ฉโ€๐Ÿ”ฌ ๐Ÿ‘จโ€๐Ÿ”ฌ ๐Ÿ‘ฉโ€๐ŸŽจ ๐Ÿ‘จโ€๐ŸŽจ ๐Ÿ‘ฉโ€๐Ÿš’ ๐Ÿ‘จโ€๐Ÿš’ ๐Ÿ‘ฉโ€โœˆ๏ธ ๐Ÿ‘จโ€โœˆ๏ธ ๐Ÿ‘ฉโ€๐Ÿš€ ๐Ÿ‘จโ€๐Ÿš€ ๐Ÿ‘ฉโ€โš–๏ธ ๐Ÿ‘จโ€โš–๏ธ ๐Ÿ‘ฐ ๐Ÿคต ๐Ÿ‘ธ ๐Ÿคด ๐Ÿคถ ๐ŸŽ… ๐Ÿง™โ€โ™€๏ธ ๐Ÿง™โ€โ™‚๏ธ ๐Ÿงโ€โ™€๏ธ ๐Ÿงโ€โ™‚๏ธ ๐Ÿง›โ€โ™€๏ธ ๐Ÿง›โ€โ™‚๏ธ ๐ŸงŸโ€โ™€๏ธ ๐ŸงŸโ€โ™‚๏ธ ๐Ÿงžโ€โ™€๏ธ ๐Ÿงžโ€โ™‚๏ธ ๐Ÿงœโ€โ™€๏ธ ๐Ÿงœโ€โ™‚๏ธ ๐Ÿงšโ€โ™€๏ธ ๐Ÿงšโ€โ™‚๏ธ ๐Ÿ‘ผ ๐Ÿคฐ ๐Ÿคฑ ๐Ÿ™‡โ€โ™€๏ธ ๐Ÿ™‡โ€โ™‚๏ธ ๐Ÿ’โ€โ™€๏ธ ๐Ÿ’โ€โ™‚๏ธ ๐Ÿ™…โ€โ™€๏ธ ๐Ÿ™…โ€โ™‚๏ธ ๐Ÿ™†โ€โ™€๏ธ ๐Ÿ™†โ€โ™‚๏ธ ๐Ÿ™‹โ€โ™€๏ธ ๐Ÿ™‹โ€โ™‚๏ธ ๐Ÿคฆโ€โ™€๏ธ ๐Ÿคฆโ€โ™‚๏ธ ๐Ÿคทโ€โ™€๏ธ ๐Ÿคทโ€โ™‚๏ธ ๐Ÿ™Žโ€โ™€๏ธ ๐Ÿ™Žโ€โ™‚๏ธ ๐Ÿ™โ€โ™€๏ธ ๐Ÿ™โ€โ™‚๏ธ ๐Ÿ’‡โ€โ™€๏ธ ๐Ÿ’‡โ€โ™‚๏ธ ๐Ÿ’†โ€โ™€๏ธ ๐Ÿ’†โ€โ™‚๏ธ ๐Ÿง–โ€โ™€๏ธ ๐Ÿง–โ€โ™‚๏ธ ๐Ÿ’… ๐Ÿคณ ๐Ÿ’ƒ ๐Ÿ•บ ๐Ÿ‘ฏโ€โ™€๏ธ ๐Ÿ‘ฏโ€โ™‚๏ธ ๐Ÿ•ด ๐Ÿšถโ€โ™€๏ธ ๐Ÿšถโ€โ™‚๏ธ ๐Ÿƒโ€โ™€๏ธ ๐Ÿƒโ€โ™‚๏ธ ๐Ÿ‘ซ ๐Ÿ‘ญ ๐Ÿ‘ฌ ๐Ÿ’‘ ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ‘ฉ ๐Ÿ‘จโ€โค๏ธโ€๐Ÿ‘จ ๐Ÿ’ ๐Ÿ‘ฉโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘ฉ ๐Ÿ‘จโ€โค๏ธโ€๐Ÿ’‹โ€๐Ÿ‘จ ๐Ÿ‘ช ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ง ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿ‘ฉโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ง ๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘ง ๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘ฆโ€๐Ÿ‘ฆ ๐Ÿ‘จโ€๐Ÿ‘งโ€๐Ÿ‘ง ๐Ÿคฒ ๐Ÿ‘ ๐Ÿ™Œ ๐Ÿ‘ ๐Ÿค ๐Ÿ‘ ๐Ÿ‘Ž ๐Ÿ‘Š โœŠ ๐Ÿค› ๐Ÿคœ ๐Ÿคž โœŒ๏ธ ๐ŸคŸ ๐Ÿค˜ ๐Ÿ‘Œ ๐Ÿ‘ˆ ๐Ÿ‘‰ ๐Ÿ‘† ๐Ÿ‘‡ โ˜๏ธ โœ‹ ๐Ÿคš ๐Ÿ– ๐Ÿ–– ๐Ÿ‘‹ ๐Ÿค™ ๐Ÿ’ช ๐Ÿฆต ๐Ÿฆถ ๐Ÿ–• โœ๏ธ ๐Ÿ™ ๐Ÿ’ ๐Ÿ’„ ๐Ÿ’‹ ๐Ÿ‘„ ๐Ÿ‘… ๐Ÿ‘‚ ๐Ÿ‘ƒ ๐Ÿ‘ฃ ๐Ÿ‘ ๐Ÿ‘€ ๐Ÿง  ๐Ÿฆด ๐Ÿฆท ๐Ÿ—ฃ ๐Ÿ‘ค ๐Ÿ‘ฅ @@ -53,7 +65,13 @@ In respect that the structure of the sufficient amount poses problems and challe ๐Ÿฅฑ ๐Ÿค ๐Ÿฆพ ๐Ÿฆฟ ๐Ÿฆป ๐Ÿง ๐Ÿงโ€โ™‚๏ธ ๐Ÿงโ€โ™€๏ธ ๐Ÿง ๐Ÿงโ€โ™‚๏ธ ๐Ÿงโ€โ™€๏ธ ๐ŸงŽ ๐ŸงŽโ€โ™‚๏ธ ๐ŸงŽโ€โ™€๏ธ ๐Ÿ‘จโ€๐Ÿฆฏ ๐Ÿ‘ฉโ€๐Ÿฆฏ ๐Ÿ‘จโ€๐Ÿฆผ ๐Ÿ‘ฉโ€๐Ÿฆผ ๐Ÿ‘จโ€๐Ÿฆฝ ๐Ÿ‘ฉโ€๐Ÿฆฝ ๐Ÿฆง ๐Ÿฆฎ ๐Ÿ•โ€๐Ÿฆบ ๐Ÿฆฅ ๐Ÿฆฆ ๐Ÿฆจ ๐Ÿฆฉ ๐Ÿง„ ๐Ÿง… ๐Ÿง‡ ๐Ÿง† ๐Ÿงˆ ๐Ÿฆช ๐Ÿงƒ ๐Ÿง‰ ๐ŸงŠ ๐Ÿ›• ๐Ÿฆฝ ๐Ÿฆผ ๐Ÿ›บ ๐Ÿช‚ ๐Ÿช ๐Ÿคฟ ๐Ÿช€ ๐Ÿช ๐Ÿฆบ ๐Ÿฅป ๐Ÿฉฑ ๐Ÿฉฒ ๐Ÿฉณ ๐Ÿฉฐ ๐Ÿช• ๐Ÿช” ๐Ÿช“ ๐Ÿฆฏ ๐Ÿฉธ ๐Ÿฉน ๐Ÿฉบ ๐Ÿช‘ ๐Ÿช’ ๐ŸคŽ ๐Ÿค ๐ŸŸ  ๐ŸŸก ๐ŸŸข ๐ŸŸฃ ๐ŸŸค ๐ŸŸฅ ๐ŸŸง ๐ŸŸจ ๐ŸŸฉ ๐ŸŸฆ ๐ŸŸช ๐ŸŸซ"; [Benchmark] - public TextLayout BuildEmojisTextLayout() => new TextLayout(Emojis, Typeface.Default, 12d, Brushes.Black); + public TextLayout BuildEmojisTextLayout() => MakeLayout(Emojis); + + [Benchmark] + public TextLayout[] BuildManySmallTexts() => _manySmallStrings.Select(MakeLayout).ToArray(); + + private static TextLayout MakeLayout(string str) + => new TextLayout(str, Typeface.Default, 12d, Brushes.Black, maxWidth:120); public void Dispose() { From ae8e6cc12045aa7c4e9fe449d66eebacdd356537 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 02:18:20 +0300 Subject: [PATCH 2/8] [Text] [Optimization] [Alloc] Bidi Pooling --- .../Media/TextFormatting/TextFormatterImpl.cs | 4 ++-- .../TextFormatting/Unicode/BiDiAlgorithm.cs | 10 +++++++++- .../Media/TextFormatting/Unicode/BiDiData.cs | 12 +++++++++++- src/Avalonia.Base/Utilities/ArrayBuilder.cs | 18 ++++++++++++++---- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index ef2abdfea0..c5ded72a43 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -159,7 +159,7 @@ namespace Avalonia.Media.TextFormatting { var flowDirection = paragraphProperties.FlowDirection; var drawableTextRuns = new List(); - var biDiData = new BidiData((sbyte)flowDirection); + using var biDiData = new BidiData((sbyte)flowDirection); foreach (var textRun in textRuns) { @@ -177,7 +177,7 @@ namespace Avalonia.Media.TextFormatting } } - var biDi = new BidiAlgorithm(); + using var biDi = new BidiAlgorithm(); biDi.Process(biDiData); diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs index 17ec9b1df2..3372d2be51 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs @@ -27,7 +27,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// as much as possible. /// /// - internal sealed class BidiAlgorithm + internal sealed class BidiAlgorithm : IDisposable { /// /// The original BiDiClass classes as provided by the caller @@ -1714,5 +1714,13 @@ namespace Avalonia.Media.TextFormatting.Unicode public BidiClass Eos { get; } } + + public void Dispose() + { + _workingClassesBuffer.Dispose(); + _resolvedLevelsBuffer.Dispose(); + _x9Map.Dispose(); + _isolatedRunMapping.Dispose(); + } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs index 0c51b0898d..7e5b3a6620 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs @@ -11,7 +11,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// Represents a unicode string and all associated attributes /// for each character required for the bidirectional Unicode algorithm /// - internal class BidiData + internal class BidiData : IDisposable { private ArrayBuilder _classes; private ArrayBuilder _pairedBracketTypes; @@ -181,5 +181,15 @@ namespace Avalonia.Media.TextFormatting.Unicode return _tempLevelBuffer.Add(length, false); } + + public void Dispose() + { + _classes.Dispose(); + _pairedBracketTypes.Dispose(); + _pairedBracketValues.Dispose(); + _savedClasses.Dispose(); + _savedPairedBracketTypes.Dispose(); + _tempLevelBuffer.Dispose(); + } } } diff --git a/src/Avalonia.Base/Utilities/ArrayBuilder.cs b/src/Avalonia.Base/Utilities/ArrayBuilder.cs index 60bf2c7586..d63eaddb7d 100644 --- a/src/Avalonia.Base/Utilities/ArrayBuilder.cs +++ b/src/Avalonia.Base/Utilities/ArrayBuilder.cs @@ -3,6 +3,7 @@ // Ported from: https://github.com/SixLabors/Fonts/ using System; +using System.Buffers; using System.Runtime.CompilerServices; namespace Avalonia.Utilities @@ -11,7 +12,7 @@ namespace Avalonia.Utilities /// A helper type for avoiding allocations while building arrays. /// /// The type of item contained in the array. - internal struct ArrayBuilder + internal struct ArrayBuilder : IDisposable where T : struct { private const int DefaultCapacity = 4; @@ -135,7 +136,7 @@ namespace Avalonia.Utilities } // Same expansion algorithm as List. - var newCapacity = length == 0 ? DefaultCapacity : (uint)length * 2u; + var newCapacity = length == 0 ? DefaultCapacity : length * 2; if (newCapacity > MaxCoreClrArrayLength) { @@ -144,10 +145,11 @@ namespace Avalonia.Utilities if (newCapacity < min) { - newCapacity = (uint)min; + newCapacity = min; } - var array = new T[newCapacity]; + Dispose(); + var array = ArrayPool.Shared.Rent(newCapacity); if (_size > 0) { @@ -180,5 +182,13 @@ namespace Avalonia.Utilities /// The . [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArraySlice AsSlice(int start, int length) => new ArraySlice(_data!, start, length); + + public void Dispose() + { + if (_data != null) + { + ArrayPool.Shared.Return(_data); + } + } } } From f8c2288f4a738900be1b75c6c507c403f153e056 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 03:04:34 +0300 Subject: [PATCH 3/8] [Text] [Bidi] fix EnsureCapacity return --- src/Avalonia.Base/Utilities/ArrayBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Utilities/ArrayBuilder.cs b/src/Avalonia.Base/Utilities/ArrayBuilder.cs index d63eaddb7d..e6b67bd383 100644 --- a/src/Avalonia.Base/Utilities/ArrayBuilder.cs +++ b/src/Avalonia.Base/Utilities/ArrayBuilder.cs @@ -147,13 +147,13 @@ namespace Avalonia.Utilities { newCapacity = min; } - - Dispose(); + var array = ArrayPool.Shared.Rent(newCapacity); if (_size > 0) { Array.Copy(_data!, array, _size); + ArrayPool.Shared.Return(_data!); } _data = array; From dc25b802a758abd8cd6c8329224c6cb9ada45be5 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 03:30:06 +0300 Subject: [PATCH 4/8] [Text] add pooled list and pooled stack usage --- .../TextFormatting/Unicode/BiDiAlgorithm.cs | 22 ++++++++++++------- .../Media/TextFormatting/Unicode/BiDiData.cs | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs index 3372d2be51..0bea4def28 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Threading; +using Avalonia.Collections.Pooled; using Avalonia.Utilities; namespace Avalonia.Media.TextFormatting.Unicode @@ -27,7 +28,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// as much as possible. /// /// - internal sealed class BidiAlgorithm : IDisposable + internal struct BidiAlgorithm : IDisposable { /// /// The original BiDiClass classes as provided by the caller @@ -97,7 +98,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// The status stack used during resolution of explicit /// embedding and isolating runs /// - private readonly Stack _statusStack = new Stack(); + private readonly PooledStack _statusStack = new PooledStack(); /// /// Mapping used to virtually remove characters for rule X9 @@ -107,7 +108,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// /// Re-usable list of level runs /// - private readonly List _levelRuns = new List(); + private readonly PooledList _levelRuns = new PooledList(); /// /// Mapping for the current isolating sequence, built @@ -118,7 +119,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// /// A stack of pending isolate openings used by FindIsolatePairs() /// - private readonly Stack _pendingIsolateOpenings = new Stack(); + private readonly PooledStack _pendingIsolateOpenings = new PooledStack(); /// /// The level of the isolating run currently being processed @@ -174,17 +175,17 @@ namespace Avalonia.Media.TextFormatting.Unicode /// Reusable list of pending opening brackets used by the /// LocatePairedBrackets method /// - private readonly List _pendingOpeningBrackets = new List(); + private readonly PooledList _pendingOpeningBrackets = new PooledList(); /// /// Resolved list of paired brackets /// - private readonly List _pairedBrackets = new List(); + private readonly PooledList _pairedBrackets = new PooledList(); /// /// Initializes a new instance of the class. /// - internal BidiAlgorithm() + public BidiAlgorithm() { } @@ -1272,7 +1273,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// Locate all pair brackets in the current isolating run /// /// A sorted list of BracketPairs - private List LocatePairedBrackets() + private PooledList LocatePairedBrackets() { // Clear work collections _pendingOpeningBrackets.Clear(); @@ -1719,8 +1720,13 @@ namespace Avalonia.Media.TextFormatting.Unicode { _workingClassesBuffer.Dispose(); _resolvedLevelsBuffer.Dispose(); + _statusStack.Dispose(); _x9Map.Dispose(); + _levelRuns.Dispose(); _isolatedRunMapping.Dispose(); + _pendingIsolateOpenings.Dispose(); + _pendingOpeningBrackets.Dispose(); + _pairedBrackets.Dispose(); } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs index 7e5b3a6620..644f7e9a8a 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiData.cs @@ -11,7 +11,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// Represents a unicode string and all associated attributes /// for each character required for the bidirectional Unicode algorithm /// - internal class BidiData : IDisposable + internal struct BidiData : IDisposable { private ArrayBuilder _classes; private ArrayBuilder _pairedBracketTypes; From 22a41933bedfddb81c1d4db82a078263e7632a0c Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 03:43:55 +0300 Subject: [PATCH 5/8] [Text] bidi - do not use pooled collections --- .../TextFormatting/Unicode/BiDiAlgorithm.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs index 0bea4def28..093c7f3f56 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs @@ -98,7 +98,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// The status stack used during resolution of explicit /// embedding and isolating runs /// - private readonly PooledStack _statusStack = new PooledStack(); + private readonly Stack _statusStack = new Stack(); /// /// Mapping used to virtually remove characters for rule X9 @@ -108,7 +108,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// /// Re-usable list of level runs /// - private readonly PooledList _levelRuns = new PooledList(); + private readonly List _levelRuns = new List(); /// /// Mapping for the current isolating sequence, built @@ -119,7 +119,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// /// A stack of pending isolate openings used by FindIsolatePairs() /// - private readonly PooledStack _pendingIsolateOpenings = new PooledStack(); + private readonly Stack _pendingIsolateOpenings = new Stack(); /// /// The level of the isolating run currently being processed @@ -175,12 +175,12 @@ namespace Avalonia.Media.TextFormatting.Unicode /// Reusable list of pending opening brackets used by the /// LocatePairedBrackets method /// - private readonly PooledList _pendingOpeningBrackets = new PooledList(); + private readonly List _pendingOpeningBrackets = new List(); /// /// Resolved list of paired brackets /// - private readonly PooledList _pairedBrackets = new PooledList(); + private readonly List _pairedBrackets = new List(); /// /// Initializes a new instance of the class. @@ -1273,7 +1273,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// Locate all pair brackets in the current isolating run /// /// A sorted list of BracketPairs - private PooledList LocatePairedBrackets() + private List LocatePairedBrackets() { // Clear work collections _pendingOpeningBrackets.Clear(); @@ -1720,13 +1720,8 @@ namespace Avalonia.Media.TextFormatting.Unicode { _workingClassesBuffer.Dispose(); _resolvedLevelsBuffer.Dispose(); - _statusStack.Dispose(); _x9Map.Dispose(); - _levelRuns.Dispose(); _isolatedRunMapping.Dispose(); - _pendingIsolateOpenings.Dispose(); - _pendingOpeningBrackets.Dispose(); - _pairedBrackets.Dispose(); } } } From 8ece18c53dcedf2475f9cbf97a25c4ea66f564e5 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 04:09:19 +0300 Subject: [PATCH 6/8] [Text] optimize CoalesceLevels alloc --- .../Media/TextFormatting/TextFormatterImpl.cs | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index a69c56eee1..517372648f 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -187,10 +187,7 @@ namespace Avalonia.Media.TextFormatting var processedRuns = new List(textRuns.Count); - foreach (var coalescedRuns in CoalesceLevels(textRuns, biDi.ResolvedLevels)) - { - processedRuns.AddRange(coalescedRuns); - } + CoalesceLevels(textRuns, biDi.ResolvedLevels, processedRuns); for (var index = 0; index < processedRuns.Count; index++) { @@ -282,12 +279,14 @@ namespace Avalonia.Media.TextFormatting /// /// The text characters to form from. /// The bidi levels. + /// /// - private static IEnumerable> CoalesceLevels(IReadOnlyList textCharacters, ArraySlice levels) + private static void CoalesceLevels(IReadOnlyList textCharacters, ArraySlice levels, + List processedRuns) { if (levels.Length == 0) { - yield break; + return; } var levelIndex = 0; @@ -306,7 +305,7 @@ namespace Avalonia.Media.TextFormatting { var drawableRun = textCharacters[i]; - yield return new[] { drawableRun }; + processedRuns.Add(drawableRun); levelIndex += drawableRun.Length; @@ -329,7 +328,7 @@ namespace Avalonia.Media.TextFormatting if (j == runText.Length) { - yield return currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties); + processedRuns.AddRange(currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties)); runLevel = levels[levelIndex]; @@ -342,7 +341,7 @@ namespace Avalonia.Media.TextFormatting } // End of this run - yield return currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties); + processedRuns.AddRange(currentRun.GetShapeableCharacters(runText.Take(j), runLevel, ref previousProperties)); runText = runText.Skip(j); @@ -355,10 +354,10 @@ namespace Avalonia.Media.TextFormatting if (currentRun is null || runText.IsEmpty) { - yield break; + return; } - yield return currentRun.GetShapeableCharacters(runText, runLevel, ref previousProperties); + processedRuns.AddRange(currentRun.GetShapeableCharacters(runText, runLevel, ref previousProperties)); } /// From 31beefe487a3f3d09027d835c7897d0d082947f6 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 04:12:02 +0300 Subject: [PATCH 7/8] [Bidi] Optimize alloc of double dictionary --- .../Media/TextFormatting/Unicode/BiDiAlgorithm.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs index 093c7f3f56..8014762ce8 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs @@ -67,7 +67,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// The forward mapping maps the start index to the end index. /// The reverse mapping maps the end index to the start index. /// - private readonly BidiDictionary _isolatePairs = new BidiDictionary(); + private BidiDictionary? _isolatePairs; /// /// The working BiDi classes @@ -228,7 +228,7 @@ namespace Avalonia.Media.TextFormatting.Unicode ArraySlice? outLevels) { // Reset state - _isolatePairs.Clear(); + _isolatePairs?.Clear(); _workingClassesBuffer.Clear(); _levelRuns.Clear(); _resolvedLevelsBuffer.Clear(); @@ -324,7 +324,7 @@ namespace Avalonia.Media.TextFormatting.Unicode // Skip isolate pairs // (Because we're working with a slice, we need to adjust the indices // we're using for the isolatePairs map) - if (_isolatePairs.TryGetValue(data.Start + i, out i)) + if (_isolatePairs?.TryGetValue(data.Start + i, out i) == true) { i -= data.Start; } @@ -379,6 +379,7 @@ namespace Avalonia.Media.TextFormatting.Unicode { if (_pendingIsolateOpenings.Count > 0) { + _isolatePairs ??= new BidiDictionary(); _isolatePairs.Add(_pendingIsolateOpenings.Pop(), i); } @@ -499,7 +500,7 @@ namespace Avalonia.Media.TextFormatting.Unicode if (resolvedIsolate == BidiClass.FirstStrongIsolate) { - if (!_isolatePairs.TryGetValue(i, out var endOfIsolate)) + if (_isolatePairs == null || !_isolatePairs.TryGetValue(i, out var endOfIsolate)) { endOfIsolate = _originalClasses.Length; } @@ -830,7 +831,7 @@ namespace Avalonia.Media.TextFormatting.Unicode var lastCharacterIndex = _isolatedRunMapping[_isolatedRunMapping.Length - 1]; var lastType = _originalClasses[lastCharacterIndex]; if ((lastType == BidiClass.LeftToRightIsolate || lastType == BidiClass.RightToLeftIsolate || lastType == BidiClass.FirstStrongIsolate) && - _isolatePairs.TryGetValue(lastCharacterIndex, out var nextRunIndex)) + _isolatePairs?.TryGetValue(lastCharacterIndex, out var nextRunIndex) == true) { // Find the continuing run index runIndex = FindRunForIndex(nextRunIndex); From 97f2a26941c492cd7bcc5447dd78aac60ce12b80 Mon Sep 17 00:00:00 2001 From: Sergey Mikolaitis Date: Wed, 11 Jan 2023 04:18:01 +0300 Subject: [PATCH 8/8] [Text] [Bidi] do not create collections if not need them --- .../Media/TextFormatting/Unicode/BiDiAlgorithm.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs index 8014762ce8..100d381afe 100644 --- a/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs +++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/BiDiAlgorithm.cs @@ -119,7 +119,7 @@ namespace Avalonia.Media.TextFormatting.Unicode /// /// A stack of pending isolate openings used by FindIsolatePairs() /// - private readonly Stack _pendingIsolateOpenings = new Stack(); + private Stack? _pendingIsolateOpenings; /// /// The level of the isolating run currently being processed @@ -359,7 +359,7 @@ namespace Avalonia.Media.TextFormatting.Unicode _hasIsolates = false; // BD9... - _pendingIsolateOpenings.Clear(); + _pendingIsolateOpenings?.Clear(); for (var i = 0; i < _originalClasses.Length; i++) { @@ -371,13 +371,14 @@ namespace Avalonia.Media.TextFormatting.Unicode case BidiClass.RightToLeftIsolate: case BidiClass.FirstStrongIsolate: { + _pendingIsolateOpenings ??= new Stack(); _pendingIsolateOpenings.Push(i); _hasIsolates = true; break; } case BidiClass.PopDirectionalIsolate: { - if (_pendingIsolateOpenings.Count > 0) + if (_pendingIsolateOpenings?.Count > 0) { _isolatePairs ??= new BidiDictionary(); _isolatePairs.Add(_pendingIsolateOpenings.Pop(), i);