[英]Benchmarking Newtonsoft.Json deserialization: from stream and from string
I'm interested in performance (speed, memory usage) comparison of two approaches how to deserialize HTTP response JSON payload using Newtonsoft.Json . 我对如何使用Newtonsoft.Json反序列化HTTP响应JSON有效负载的两种方法的性能(速度,内存使用)比较感兴趣。
I'm aware of Newtonsoft.Json's Performance Tips to use streams, but I wanted to know more and have hard numbers. 我知道Newtonsoft.Json的使用流的性能提示 ,但我想知道更多并且有更多的数字。 I've written simple benchmark using BenchmarkDotNet , but I'm bit puzzled by results (see numbers below). 我使用BenchmarkDotNet编写了简单的基准测试 ,但我对结果感到有些困惑(见下面的数字)。
What I got: 我得到了什么:
I didn't have time to do proper profiling (yet), I'm bit surprised by memory overhead with stream approach (if there's no error). 我没有时间进行适当的分析(但是),我对使用流方法的内存开销感到惊讶(如果没有错误)。 Whole code is here . 整个代码在这里 。
MemoryStream
; simulating HttpResponseMessage
and its content; ...) (使用MemoryStream
;模拟HttpResponseMessage
及其内容; ......) I'm preparing MemoryStream
to be used over and over within benchmark run: 我正在准备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);
}
}
}
We first read JSON from stream to string, and then run deserialization - another string is being allocated, and after that used for deserialization. 我们首先从流到字符串读取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);
Repeated 10000 times 重复10000次
Repeated 1000 times 重复1000次
Repeated 100 times 重复100次
I went trough source of JsonConvert
and found out that it internally uses JsonTextReader
with StringReader
when deserializing from string
: JsonConvert:816 . 我去的低谷源JsonConvert
,并发现它在内部使用JsonTextReader
与StringReader
从反序列化时string
: JsonConvert:816 。 Stream is involved there as well (of course!). 流也参与其中(当然!)。
Then I decided to dig more into StreamReader
itself and I was stunned at first sight - it is always allocating array buffer ( byte[]
): StreamReader:244 , which explains its memory use. 然后我决定深入挖掘StreamReader
本身,我一见钟情就惊呆了 - 它总是分配数组缓冲区( byte[]
): StreamReader:244 ,它解释了它的内存使用。
This gives me answer to "why". 这让我回答“为什么”。 Solution is simple - use smaller buffer size when instantiating StreamReader
- minimum buffer size defaults to 128 (see StreamReader.MinBufferSize
), but you can supply any value > 0
(check one of ctor overload). 解决方案很简单 - 在实例化StreamReader
时使用较小的缓冲区大小 - 最小缓冲区大小默认为128(请参阅StreamReader.MinBufferSize
),但您可以提供任何> 0
值(检查ctor重载之一)。
Of course buffer size has effect on processing data. 当然缓冲器大小对处理数据的效果。 Answering what buffer size I should then use: it depends . 回答我应该使用的缓冲区大小: 它取决于 。 When expecting smaller JSON responses, I think it is safe to stick with small buffer. 当期望更小的JSON响应时,我认为坚持使用小缓冲区是安全的。
After some fiddling I found reason behind memory allocation when using StreamReader
. 在一些摆弄之后,我发现在使用StreamReader
时内存分配背后的原因。 Original post is updated, but recap here: 原帖更新,但请回顾一下:
StreamReader
uses default bufferSize
set to 1024. Every instantiation of StreamReader
then allocates byte array of that size. StreamReader
使用默认的bufferSize
设置为1024.然后, StreamReader
每个实例化都会分配该大小的字节数组。 That's the reason why I saw such numbers in my benchmark. 这就是为什么我在我的基准测试中看到这样的数字的原因。
When I set bufferSize
to its lowest possible value 128
, results seem to be much better. 当我将bufferSize
设置为其最低可能值128
,结果似乎要好得多。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.