From 7baa18b5dd9d93c6eff9f956d3747388363779a2 Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Sat, 4 Apr 2020 18:31:55 +0100 Subject: [PATCH] add stringbuilder pool for tokenizers we could replace these with spans in the next net core however for now our pools seem to increase performance by reducing gc load. --- .../NumericTokenizer.cs | 5 +- .../PlainTokenizer.cs | 6 +- .../StringBuilderPool.cs | 65 +++++++++++++++++++ .../StringTokenizer.cs | 5 +- 4 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 src/UglyToad.PdfPig.Tokenization/StringBuilderPool.cs diff --git a/src/UglyToad.PdfPig.Tokenization/NumericTokenizer.cs b/src/UglyToad.PdfPig.Tokenization/NumericTokenizer.cs index c6b75a4f..e5c56fc8 100644 --- a/src/UglyToad.PdfPig.Tokenization/NumericTokenizer.cs +++ b/src/UglyToad.PdfPig.Tokenization/NumericTokenizer.cs @@ -8,6 +8,8 @@ internal class NumericTokenizer : ITokenizer { + private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(10); + private const byte Zero = 48; private const byte Nine = 57; @@ -21,7 +23,7 @@ if ((currentByte >= Zero && currentByte <= Nine) || currentByte == '-' || currentByte == '+' || currentByte == '.') { - characters = new StringBuilder(); + characters = StringBuilderPool.Borrow(); characters.Append((char)currentByte); } else @@ -51,6 +53,7 @@ try { var str = characters.ToString(); + StringBuilderPool.Return(characters); switch (str) { diff --git a/src/UglyToad.PdfPig.Tokenization/PlainTokenizer.cs b/src/UglyToad.PdfPig.Tokenization/PlainTokenizer.cs index 5d1f8486..8ef87512 100644 --- a/src/UglyToad.PdfPig.Tokenization/PlainTokenizer.cs +++ b/src/UglyToad.PdfPig.Tokenization/PlainTokenizer.cs @@ -1,11 +1,12 @@ namespace UglyToad.PdfPig.Tokenization { - using System.Text; using Core; using Tokens; internal class PlainTokenizer : ITokenizer { + private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(10); + public bool ReadsNextByte { get; } = true; public bool TryTokenize(byte currentByte, IInputBytes inputBytes, out IToken token) @@ -17,7 +18,7 @@ return false; } - var builder = new StringBuilder(); + var builder = StringBuilderPool.Borrow(); builder.Append((char)currentByte); while (inputBytes.MoveNext()) { @@ -38,6 +39,7 @@ } var text = builder.ToString(); + StringBuilderPool.Return(builder); switch (text) { diff --git a/src/UglyToad.PdfPig.Tokenization/StringBuilderPool.cs b/src/UglyToad.PdfPig.Tokenization/StringBuilderPool.cs new file mode 100644 index 00000000..7ff0ba92 --- /dev/null +++ b/src/UglyToad.PdfPig.Tokenization/StringBuilderPool.cs @@ -0,0 +1,65 @@ +namespace UglyToad.PdfPig.Tokenization +{ + using System.Collections.Generic; + using System.Text; + + /// + /// A pool for s to reduce allocations during tokenization. + /// + public class StringBuilderPool + { + private readonly int capacity; + private readonly object locker = new object(); + private readonly Stack pool = new Stack(); + + /// + /// Create a new holding the number of items specified by the capacity. + /// + public StringBuilderPool(int capacity = 5) + { + this.capacity = capacity; + + for (var i = 0; i < capacity; i++) + { + pool.Push(new StringBuilder()); + } + } + + /// + /// Get an item from the pool, remember to return it using at the end. + /// + public StringBuilder Borrow() + { + lock (locker) + { + if (pool.Count == 0) + { + return new StringBuilder(); + } + + return pool.Pop(); + } + } + + /// + /// Returns an item to the pool of available builders. + /// + public void Return(StringBuilder instance) + { + if (instance == null) + { + return; + } + + instance.Clear(); + + lock (locker) + { + if (pool.Count < capacity) + { + pool.Push(instance); + } + } + } + } +} diff --git a/src/UglyToad.PdfPig.Tokenization/StringTokenizer.cs b/src/UglyToad.PdfPig.Tokenization/StringTokenizer.cs index d701df67..bc68e919 100644 --- a/src/UglyToad.PdfPig.Tokenization/StringTokenizer.cs +++ b/src/UglyToad.PdfPig.Tokenization/StringTokenizer.cs @@ -6,11 +6,11 @@ internal class StringTokenizer : ITokenizer { + private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(16); public bool ReadsNextByte { get; } = false; public bool TryTokenize(byte currentByte, IInputBytes inputBytes, out IToken token) { - var builder = new StringBuilder(); token = null; if (inputBytes == null) @@ -23,6 +23,7 @@ return false; } + var builder = StringBuilderPool.Borrow(); var numberOfBrackets = 1; var isEscapeActive = false; var isLineBreaking = false; @@ -177,6 +178,8 @@ encodedWith = StringToken.Encoding.Iso88591; } + StringBuilderPool.Return(builder); + token = new StringToken(tokenStr, encodedWith); return true;