Настройка JsonTextReader.ArrayPool
, как вы уже делаете (что также показано в DemoTests.ArrayPooling()
), должна помочь минимизировать нагрузку на память из-за выделения промежуточного звена массивы символов во время синтаксического анализа. Это, однако, не уменьшит использование памяти из-за выделения строк , что кажется вашей жалобой.
Начиная с Выпуск 12.0.1 , Json.NET имеет возможность повторно использовать экземпляры строк с именами свойств , установив JsonTextReader.PropertyNameTable
в некоторые соответствующие JsonNameTable
подкласс.
Этот механизм используется во время десериализации, JsonSerializer.SetupReader()
, чтобы установить таблицу имен в считывателе, которая возвращает имена свойств, хранящиеся в распознавателе контракта , таким образом предотвращая повторное выделение известных имен свойств, ожидаемых сериализатором.
Однако вы не используете сериализатор, вы читаете напрямую и поэтому не пользуетесь этим механизмом. Чтобы включить его, вы можете создать свой собственный JsonNameTable
для кэширования имен свойств, с которыми вы действительно сталкиваетесь:
public class AutomaticJsonNameTable : DefaultJsonNameTable
{
int nAutoAdded = 0;
int maxToAutoAdd;
public AutomaticJsonNameTable(int maxToAdd)
{
this.maxToAutoAdd = maxToAdd;
}
public override string Get(char[] key, int start, int length)
{
var s = base.Get(key, start, length);
if (s == null && nAutoAdded < maxToAutoAdd)
{
s = new string(key, start, length);
Add(s);
nAutoAdded++;
}
return s;
}
}
А затем используйте его следующим образом:
const int MaxPropertyNamesToCache = 200; // Set through experiment.
var nameTable = new AutomaticJsonNameTable(MaxPropertyNamesToCache);
using (var sr = new MockedStreamReader())
using (var jtr = new JsonTextReader(sr) { PropertyNameTable = nameTable })
{
// Process as before.
}
Это должно существенно снизить нагрузку на память из-за имен свойств.
Обратите внимание, что AutomaticJsonNameTable
будет автоматически кэшировать только определенное конечное количество имен, чтобы предотвратить атаки выделения памяти. Вам нужно будет определить это максимальное количество, хотя экспериментально. Вы также можете вручную жестко закодировать добавление ожидаемых, известных имен свойств.
Обратите внимание, что, указав таблицу имен вручную, вы предотвращаете использование таблицы имен, определенной сериализатором, во время десериализации. Если ваш алгоритм синтаксического анализа включает в себя чтение файла для поиска определенных вложенных объектов, а затем десериализацию этих объектов, вы можете получить более высокую производительность, временно обнуляя таблицу имен перед десериализацией, например, со следующим методом расширения:
public static class JsonSerializerExtensions
{
public static T DeserializeWithDefaultNameTable<T>(this JsonSerializer serializer, JsonReader reader)
{
JsonNameTable old = null;
var textReader = reader as JsonTextReader;
if (textReader != null)
{
old = textReader.PropertyNameTable;
textReader.PropertyNameTable = null;
}
try
{
return serializer.Deserialize<T>(reader);
}
finally
{
if (textReader != null)
textReader.PropertyNameTable = old;
}
}
}
Экспериментально необходимо определить, дает ли использование таблицы имен сериализатора лучшую производительность, чем ваша (и я не проводил никаких подобных экспериментов в рамках написания этого ответа).
В настоящее время нет способа помешать JsonTextReader
выделять строки для значений свойств, даже если пропустить или иным образом игнорировать эти значения. См. , пожалуйста, поддержите реальный пропуск (без материализации свойств и т. Д.) # 1021 для аналогичного запроса на улучшение.
Похоже, что единственным вариантом здесь является создание собственной версии JsonTextReader
и добавление этой возможности самостоятельно. Вам нужно будет найти все вызовы на SetToken(JsonToken.String, _stringReference.ToString(), ...)
и заменить вызов на __stringReference.ToString()
чем-то, что безоговорочно не выделяет память.
Например, если у вас большой кусок JSON, который вы хотели бы пропустить, вы можете добавить string DummyValue
к JsonTextReader
:
public partial class MyJsonTextReader : JsonReader, IJsonLineInfo
{
public string DummyValue { get; set; }
А затем добавьте следующую логику, где требуется (в двух местах в настоящее время):
string text = DummyValue ?? _stringReference.ToString();
SetToken(JsonToken.String, text, false);
Или
SetToken(JsonToken.String, DummyValue ?? _stringReference.ToString(), false);
Затем, когда считанные значения можно пропустить, вы установите MyJsonTextReader.DummyValue
для некоторой заглушки, скажем, "dummy value"
.
В качестве альтернативы, если у вас есть много повторяющихся значений свойств, которые нельзя пропустить, вы можете заранее предсказать, вы можете создать секунду JsonNameTable StringValueNameTable
и, если не ноль, попытаться найти StringReference
в нем вот так:
var text = StringValueNameTable?.Get(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length) ?? _stringReference.ToString();
К сожалению, для создания своей собственной JsonTextReader
может потребоваться существенное текущее обслуживание, поскольку вам также потребуется разветвить все утилиты Newtonsoft, используемые читателем (их много), и обновить их до любых критических изменений в исходной библиотеке.
Вы также можете проголосовать или прокомментировать запрос на улучшение # 1021 , запрашивающий эту способность, или добавить аналогичный запрос самостоятельно.