Меня интересует сравнение производительности (скорости, использования памяти) двух подходов к десериализации полезной нагрузки JSON ответа HTTP с использованием Newtonsoft.Json .
Мне известно о * 1005Советы по повышению производительности * Newtonsoft.Json для использования потоков, но я хотел знать больше и иметь точные цифры.Я написал простой тест, используя BenchmarkDotNet , но я немного озадачен результатами (см. Цифры ниже).
Что я получил:
- парсинг отпоток всегда быстрее, но на самом деле не очень
- разбирает маленький и "средний" JSON имеет лучшее или равное использование памяти при использовании строки в качестве ввода
- значительная разница в использовании памяти начинает замечаться при большихJSON (где сама строка заканчивается в LOH)
У меня не было времени, чтобы выполнить правильное профилирование (пока), я немного удивлен перегрузкой памяти при потоковом подходе (если нет ошибки),Весь код здесь .
?
- Правильный ли мой подход?(использование
MemoryStream
; моделирование HttpResponseMessage
и его содержимого; ...) - Есть ли какие-либо проблемы с кодом сравнения?
- Почему я вижу такие результаты?
Настройка бенчмарка
Я готовлю MemoryStream
для многократного использования в тестовом прогоне:
[GlobalSetup]
public void GlobalSetup()
{
var resourceName = _resourceMapping[typeof(T)];
using (var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
{
_memory = new MemoryStream();
resourceStream.CopyTo(_memory);
}
_iterationRepeats = _repeatMapping[typeof(T)];
}
Десериализация потока
[Benchmark(Description = "Stream d13n")]
public async Task DeserializeStream()
{
for (var i = 0; i < _iterationRepeats; i++)
{
var response = BuildResponse(_memory);
using (var streamReader = BuildNonClosingStreamReader(await response.Content.ReadAsStreamAsync()))
using (var jsonReader = new JsonTextReader(streamReader))
{
_serializer.Deserialize<T>(jsonReader);
}
}
}
Десериализация строк
Сначала мы читаем JSON из потока в строку, а затем запускаем десериализацию - выделяется другая строка, а затем используется для десериализации.
[Benchmark(Description = "String d13n")]
public async Task DeserializeString()
{
for (var i = 0; i < _iterationRepeats; i++)
{
var response = BuildResponse(_memory);
var content = await response.Content.ReadAsStringAsync();
JsonConvert.DeserializeObject<T>(content);
}
}
Общие методы
private static HttpResponseMessage BuildResponse(Stream stream)
{
stream.Seek(0, SeekOrigin.Begin);
var content = new StreamContent(stream);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = content
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static StreamReader BuildNonClosingStreamReader(Stream inputStream) =>
new StreamReader(
stream: inputStream,
encoding: Encoding.UTF8,
detectEncodingFromByteOrderMarks: true,
bufferSize: 1024,
leaveOpen: true);
Результаты
Малый JSON
Повтор 10000 раз
- Поток: среднее значение 25,69 мс, выделено 61,34 МБ
- Строка: среднее значение31,22 мс, 36,01 мб выделено
средний JSON
повторяется 1000 раз
- Поток: в среднем 24,07 мс, 12 мб выделено
- Строка: среднее значение 25,09 мс, выделено 12,85 МБ
Большой JSON
Повторяется 100 раз
- Stream: среднее значение 229,6 мс, 47,54 МБ выделено, объекты получены для Gen 1
- Строка: среднее значение 240,8 мс, 92,42 МБ выделено, объекты получены для Gen 2!
Обновление
Я прошел через источник JsonConvert
и обнаружил, что он внутренне использует JsonTextReader
с StringReader
при десериализации из string
: JsonConvert: 816 .Там также задействован поток (конечно!).
Тогда я решил больше копаться в StreamReader
, и с первого взгляда был потрясен - он всегда выделяет буфер массива (byte[]
): StreamReader: 244 , что объясняет использование памяти.
Это дает мне ответ на вопрос «почему».Решение простое - используйте меньший размер буфера при создании экземпляра StreamReader
- минимальный размер буфера по умолчанию равен 128 (см. StreamReader.MinBufferSize
), но вы можете указать любое значение > 0
(проверьте одно из значений перегрузки ctor).
Конечно размер буфера оказывает влияние на обработку данных.Отвечая на вопрос, какой размер буфера мне следует использовать: это зависит .Ожидая меньших ответов JSON, я думаю, что безопасно придерживаться небольшого буфера.