From dcae8e220da5637527d79ffbdf2b1b5e4d3cdf75 Mon Sep 17 00:00:00 2001 From: Jon Hanna Date: Fri, 13 Jan 2017 07:41:18 +0000 Subject: [PATCH] Some optimisations to async reading. (#1171) MoveToContentAsync is often called in states where it will immediately return true without any reading. In this case return AsyncUtils.True avoiding allocation or async state-machine. DoReadAsync is a very heavily hit method, and can often be fulfilled without creating another state machine, so do this. The remaining looping async case is the only use of ParsePostValueAsync, so inline it into the calling method, so there is one less task allocation. Return value of EatWhitespaceAsync is never used, returning void reduces the number of locals an hence the size of the state-machine. Do the same for EatWhitespace though the impact is less. --- Src/Newtonsoft.Json/JsonReader.Async.cs | 25 +- Src/Newtonsoft.Json/JsonTextReader.Async.cs | 238 ++++++++++---------- Src/Newtonsoft.Json/JsonTextReader.cs | 23 +- 3 files changed, 141 insertions(+), 145 deletions(-) diff --git a/Src/Newtonsoft.Json/JsonReader.Async.cs b/Src/Newtonsoft.Json/JsonReader.Async.cs index d3bee93c..5a884b9a 100644 --- a/Src/Newtonsoft.Json/JsonReader.Async.cs +++ b/Src/Newtonsoft.Json/JsonReader.Async.cs @@ -27,7 +27,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json.Utilities; @@ -210,19 +209,31 @@ namespace Newtonsoft.Json return await ReadAsync(cancellationToken).ConfigureAwait(false) && await MoveToContentAsync(cancellationToken).ConfigureAwait(false); } - internal async Task MoveToContentAsync(CancellationToken cancellationToken) + internal Task MoveToContentAsync(CancellationToken cancellationToken) + { + switch (TokenType) + { + case JsonToken.None: + case JsonToken.Comment: + return MoveToContentFromNonContentAsync(cancellationToken); + default: + return AsyncUtils.True; + } + } + + private async Task MoveToContentFromNonContentAsync(CancellationToken cancellationToken) { while (true) { + if (!await ReadAsync(cancellationToken).ConfigureAwait(false)) + { + return false; + } + switch (TokenType) { case JsonToken.None: case JsonToken.Comment: - if (!await ReadAsync(cancellationToken).ConfigureAwait(false)) - { - return false; - } - break; default: return true; diff --git a/Src/Newtonsoft.Json/JsonTextReader.Async.cs b/Src/Newtonsoft.Json/JsonTextReader.Async.cs index c0152997..4eb340b0 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.Async.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.Async.cs @@ -58,59 +58,125 @@ namespace Newtonsoft.Json return _safeAsync ? DoReadAsync(cancellationToken) : base.ReadAsync(cancellationToken); } - internal async Task DoReadAsync(CancellationToken cancellationToken) + internal Task DoReadAsync(CancellationToken cancellationToken) { EnsureBuffer(); - - while (true) + switch (_currentState) { - switch (_currentState) - { - case State.Start: - case State.Property: - case State.Array: - case State.ArrayStart: - case State.Constructor: - case State.ConstructorStart: - return await ParseValueAsync(cancellationToken).ConfigureAwait(false); - case State.Object: - case State.ObjectStart: - return await ParseObjectAsync(cancellationToken).ConfigureAwait(false); - case State.PostValue: + case State.Start: + case State.Property: + case State.Array: + case State.ArrayStart: + case State.Constructor: + case State.ConstructorStart: + return ParseValueAsync(cancellationToken); + case State.Object: + case State.ObjectStart: + return ParseObjectAsync(cancellationToken); + case State.PostValue: + return LoopReadAsync(cancellationToken); + case State.Finished: + return ReadFromFinishedAsync(cancellationToken); + default: + throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState)); + } + } - // returns true if it hits - // end of object or array - if (await ParsePostValueAsync(cancellationToken).ConfigureAwait(false)) + private async Task LoopReadAsync(CancellationToken cancellationToken) + { + while (_currentState == State.PostValue) + { + char currentChar = _chars[_charPos]; + + switch (currentChar) + { + case '\0': + if (_charsUsed == _charPos) { - return true; + if (await ReadDataAsync(false, cancellationToken).ConfigureAwait(false) == 0) + { + _currentState = State.Finished; + } + } + else + { + _charPos++; } break; - case State.Finished: - if (await EnsureCharsAsync(0, false, cancellationToken).ConfigureAwait(false)) + case '}': + _charPos++; + SetToken(JsonToken.EndObject); + return true; + case ']': + _charPos++; + SetToken(JsonToken.EndArray); + return true; + case ')': + _charPos++; + SetToken(JsonToken.EndConstructor); + return true; + case '/': + await ParseCommentAsync(true, cancellationToken).ConfigureAwait(false); + return true; + case ',': + _charPos++; + + // finished parsing + SetStateBasedOnCurrent(); + break; + case ' ': + case StringUtils.Tab: + + // eat + _charPos++; + break; + case StringUtils.CarriageReturn: + await ProcessCarriageReturnAsync(false, cancellationToken).ConfigureAwait(false); + break; + case StringUtils.LineFeed: + ProcessLineFeed(); + break; + default: + if (char.IsWhiteSpace(currentChar)) { - await EatWhitespaceAsync(false, cancellationToken).ConfigureAwait(false); - if (_isEndOfFile) - { - SetToken(JsonToken.None); - return false; - } - - if (_chars[_charPos] == '/') - { - await ParseCommentAsync(true, cancellationToken).ConfigureAwait(false); - return true; - } - - throw JsonReaderException.Create(this, "Additional text encountered after finished reading JSON content: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos])); + // eat + _charPos++; + } + else + { + throw JsonReaderException.Create(this, "After parsing a value an unexpected character was encountered: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); } - SetToken(JsonToken.None); - return false; - default: - throw JsonReaderException.Create(this, "Unexpected state: {0}.".FormatWith(CultureInfo.InvariantCulture, CurrentState)); + break; } } + + return await DoReadAsync(cancellationToken).ConfigureAwait(false); + } + + private async Task ReadFromFinishedAsync(CancellationToken cancellationToken) + { + if (await EnsureCharsAsync(0, false, cancellationToken).ConfigureAwait(false)) + { + await EatWhitespaceAsync(cancellationToken).ConfigureAwait(false); + if (_isEndOfFile) + { + SetToken(JsonToken.None); + return false; + } + + if (_chars[_charPos] == '/') + { + await ParseCommentAsync(true, cancellationToken).ConfigureAwait(false); + return true; + } + + throw JsonReaderException.Create(this, "Additional text encountered after finished reading JSON content: {0}.".FormatWith(CultureInfo.InvariantCulture, _chars[_charPos])); + } + + SetToken(JsonToken.None); + return false; } private Task ReadDataAsync(bool append, CancellationToken cancellationToken) @@ -631,7 +697,7 @@ namespace Newtonsoft.Json } } - private async Task ParsePostValueAsync(CancellationToken cancellationToken) + private async Task EatWhitespaceAsync(CancellationToken cancellationToken) { while (true) { @@ -644,81 +710,7 @@ namespace Newtonsoft.Json { if (await ReadDataAsync(false, cancellationToken).ConfigureAwait(false) == 0) { - _currentState = State.Finished; - return false; - } - } - else - { - _charPos++; - } - - break; - case '}': - _charPos++; - SetToken(JsonToken.EndObject); - return true; - case ']': - _charPos++; - SetToken(JsonToken.EndArray); - return true; - case ')': - _charPos++; - SetToken(JsonToken.EndConstructor); - return true; - case '/': - await ParseCommentAsync(true, cancellationToken).ConfigureAwait(false); - return true; - case ',': - _charPos++; - - // finished parsing - SetStateBasedOnCurrent(); - return false; - case ' ': - case StringUtils.Tab: - - // eat - _charPos++; - break; - case StringUtils.CarriageReturn: - await ProcessCarriageReturnAsync(false, cancellationToken).ConfigureAwait(false); - break; - case StringUtils.LineFeed: - ProcessLineFeed(); - break; - default: - if (char.IsWhiteSpace(currentChar)) - { - // eat - _charPos++; - } - else - { - throw JsonReaderException.Create(this, "After parsing a value an unexpected character was encountered: {0}.".FormatWith(CultureInfo.InvariantCulture, currentChar)); - } - - break; - } - } - } - - private async Task EatWhitespaceAsync(bool oneOrMore, CancellationToken cancellationToken) - { - bool finished = false; - bool ateWhitespace = false; - while (!finished) - { - char currentChar = _chars[_charPos]; - - switch (currentChar) - { - case '\0': - if (_charsUsed == _charPos) - { - if (await ReadDataAsync(false, cancellationToken).ConfigureAwait(false) == 0) - { - finished = true; + return; } } else @@ -735,18 +727,15 @@ namespace Newtonsoft.Json default: if (currentChar == ' ' || char.IsWhiteSpace(currentChar)) { - ateWhitespace = true; _charPos++; } else { - finished = true; + return; } break; } } - - return !oneOrMore || ateWhitespace; } private async Task ParseStringAsync(char quote, ReadType readType, CancellationToken cancellationToken) @@ -811,7 +800,7 @@ namespace Newtonsoft.Json { if (await MatchValueWithTrailingSeparatorAsync("new", cancellationToken).ConfigureAwait(false)) { - await EatWhitespaceAsync(false, cancellationToken).ConfigureAwait(false); + await EatWhitespaceAsync(cancellationToken).ConfigureAwait(false); int initialPosition = _charPos; int endPosition; @@ -871,7 +860,7 @@ namespace Newtonsoft.Json _stringReference = new StringReference(_chars, initialPosition, endPosition - initialPosition); string constructorName = _stringReference.ToString(); - EatWhitespace(false); + await EatWhitespaceAsync(cancellationToken).ConfigureAwait(false); if (_chars[_charPos] != '(') { @@ -958,7 +947,7 @@ namespace Newtonsoft.Json propertyName = _stringReference.ToString(); } - await EatWhitespaceAsync(false, cancellationToken).ConfigureAwait(false); + await EatWhitespaceAsync(cancellationToken).ConfigureAwait(false); if (_chars[_charPos] != ':') { @@ -1079,9 +1068,10 @@ namespace Newtonsoft.Json { if (await EnsureCharsAsync(0, false, cancellationToken).ConfigureAwait(false)) { - await EatWhitespaceAsync(false, cancellationToken).ConfigureAwait(false); + await EatWhitespaceAsync(cancellationToken).ConfigureAwait(false); if (_isEndOfFile) { + SetToken(JsonToken.None); return; } diff --git a/Src/Newtonsoft.Json/JsonTextReader.cs b/Src/Newtonsoft.Json/JsonTextReader.cs index 8bbb6a9b..69041c7c 100644 --- a/Src/Newtonsoft.Json/JsonTextReader.cs +++ b/Src/Newtonsoft.Json/JsonTextReader.cs @@ -404,7 +404,7 @@ namespace Newtonsoft.Json case State.Finished: if (EnsureChars(0, false)) { - EatWhitespace(false); + EatWhitespace(); if (_isEndOfFile) { SetToken(JsonToken.None); @@ -1018,7 +1018,7 @@ namespace Newtonsoft.Json { if (EnsureChars(0, false)) { - EatWhitespace(false); + EatWhitespace(); if (_isEndOfFile) { return; @@ -1513,7 +1513,7 @@ namespace Newtonsoft.Json propertyName = _stringReference.ToString(); } - EatWhitespace(false); + EatWhitespace(); if (_chars[_charPos] != ':') { @@ -1727,11 +1727,9 @@ namespace Newtonsoft.Json OnNewLine(_charPos); } - private bool EatWhitespace(bool oneOrMore) + private void EatWhitespace() { - bool finished = false; - bool ateWhitespace = false; - while (!finished) + while (true) { char currentChar = _chars[_charPos]; @@ -1742,7 +1740,7 @@ namespace Newtonsoft.Json { if (ReadData(false) == 0) { - finished = true; + return; } } else @@ -1759,25 +1757,22 @@ namespace Newtonsoft.Json default: if (currentChar == ' ' || char.IsWhiteSpace(currentChar)) { - ateWhitespace = true; _charPos++; } else { - finished = true; + return; } break; } } - - return (!oneOrMore || ateWhitespace); } private void ParseConstructor() { if (MatchValueWithTrailingSeparator("new")) { - EatWhitespace(false); + EatWhitespace(); int initialPosition = _charPos; int endPosition; @@ -1837,7 +1832,7 @@ namespace Newtonsoft.Json _stringReference = new StringReference(_chars, initialPosition, endPosition - initialPosition); string constructorName = _stringReference.ToString(); - EatWhitespace(false); + EatWhitespace(); if (_chars[_charPos] != '(') {