首页 > 解决方案 > 基准测试 Newtonsoft.Json 反序列化:来自流和来自字符串

问题描述

我对如何使用Newtonsoft.Json反序列化 HTTP 响应 JSON 有效负载的两种方法的性能(速度、内存使用)比较感兴趣。

我知道Newtonsoft.Json 的使用流的性能提示,但我想知道更多并有确切的数字。我已经使用BenchmarkDotNet编写了简单的基准测试,但我对结果感到有些困惑(见下面的数字)。

我得到了什么:

我没有时间进行正确的分析(还),我对流方法的内存开销感到有点惊讶(如果没有错误的话)。完整的代码在这里

?

基准设置

我正准备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 次

中等 JSON

重复 1000 次

大型 JSON

重复 100 次


更新

我通过源代码JsonConvert发现它在反序列化时在内部JsonTextReader使用:JsonConvert:816。流也参与其中(当然!)。StringReaderstring

然后我决定更深入地StreamReader研究它自己,第一眼看到我就惊呆了——它总是分配数组缓冲区 ( byte[]): StreamReader:244,这解释了它的内存使用情况。

这给了我“为什么”的答案。解决方案很简单 - 实例化时使用较小的缓冲区大小StreamReader- 最小缓冲区大小默认为 128(请参阅参考资料StreamReader.MinBufferSize),但您可以提供任何值> 0(检查 ctor 过载之一)。

当然缓冲区大小处理数据有影响。回答我应该使用什么缓冲区大小:这取决于. 当期望较小的 JSON 响应时,我认为坚持使用小缓冲区是安全的。

标签: c#json.netdeserializationbenchmarkdotnet

解决方案


经过一番摆弄,我找到了使用StreamReader. 原帖已更新,但在这里回顾一下:

StreamReader使用默认bufferSize设置为 1024。 then 的每个实例化都StreamReader分配该大小的字节数组。这就是我在基准测试中看到这些数字的原因。

当我设置bufferSize为尽可能低的值128时,结果似乎要好得多。


推荐阅读