Newtonsoft json.net JsonTextReader垃圾收集器密集型
我们正在使用一个通过http序列化为JSON的大型(GBs)网络流,使用Newtonsoft.JSON nuget包将响应流反序列化到内存记录中,以便进一步操作 鉴于数据量过大,我们使用流式处理一次接收一大块响应,并希望在达到CPU限制时优化此过程 优化的候选对象之一似乎是JsonTextReader,它不断地分配新对象,从而触发垃圾收集 我们遵循了Newtonsoft的建议 我创建了一个示例.net控制台应用程序,模拟当JsonTextReader读取响应流时分配新对象的行为,分配表示属性名称和值的字符串 问题: 如果在现实世界中有95%的属性名/值实例是重复的(在测试中是相同的记录,所以100%重复),那么我们还可以调整/重写哪些内容来重用已经分配的属性名/值实例 示例应用程序:Newtonsoft json.net JsonTextReader垃圾收集器密集型,.net,performance,garbage-collection,json.net,.net,Performance,Garbage Collection,Json.net,我们正在使用一个通过http序列化为JSON的大型(GBs)网络流,使用Newtonsoft.JSON nuget包将响应流反序列化到内存记录中,以便进一步操作 鉴于数据量过大,我们使用流式处理一次接收一大块响应,并希望在达到CPU限制时优化此过程 优化的候选对象之一似乎是JsonTextReader,它不断地分配新对象,从而触发垃圾收集 我们遵循了Newtonsoft的建议 我创建了一个示例.net控制台应用程序,模拟当JsonTextReader读取响应流时分配新对象的行为,分配表示属性名称
Install-Package Newtonsoft.Json -Version 12.0.2
Install-Package System.Buffers -Version 4.5.0
Program.cs
使用系统;
使用系统缓冲区;
使用System.IO;
使用System.Linq;
使用系统文本;
使用Newtonsoft.Json;
名称空间JSONNETSTER
{
班级计划
{
静态void Main(字符串[]参数)
{
使用(var sr=new MockedStreamReader())
使用(var jtr=新的JsonTextReader(sr))
{
//似乎没有什么区别
//jtr.ArrayPool=JsonArrayPool.Instance;
//每次读取都会分配新对象
while(jtr.Read())
{
}
}
}
//模拟序列化为json的连续记录流
公共类MockedStreamReader:StreamReader
{
private bool initialProvided=false;
私有字节[]initialBytes=Encoding.Default.GetBytes(“[”);
私有静态只读字节[]记录字节;
int nextStart=0;
静态MockedStreamReader()
{
var recordSb=新的StringBuilder(“{”);
//生成{“键[i]:“值[i]”的[i],
Enumerable.Range(0,50).ToList().ForEach(i=>
{
如果(i>0)
{
记录b.附加(“,”);
}
recordSb.Append($“\”键{i}\:\”值{i}\”);
});
recordSb.Append(“},”);
recordBytes=Encoding.Default.GetBytes(recordSb.ToString());
}
public-MockedStreamReader():基(新内存流())
{ }
公共重写整型读取(字符[]缓冲区,整型索引,整型计数)
{
//继续读取循环中的相同记录
如果(提供此首字母缩写)
{
var start=nextStart;
var length=Math.Min(recordBytes.length-start,count);
var end=开始+长度;
nextStart=end>=recordBytes。长度?0:end;
复制(记录字节、开始、缓冲区、索引、长度);
返回长度;
}
其他的
{
initialProvided=true;
复制(initialBytes、缓冲区、initialBytes.Length);
返回initialBytes.Length;
}
}
}
//尝试在序列化中重用数据
公共类JsonArrayPool:IArrayPool
{
公共静态只读JsonArrayPool实例=新JsonArrayPool();
公共字符[]租金(整数最小长度)
{
返回ArrayPool.Shared.Rent(最小长度);
}
公共void返回(char[]数组)
{
ArrayPool.Shared.Return(数组);
}
}
}
}
可以通过Visual Studio Debug>Performance Profiler>.NET对象分配跟踪或Performance Monitor#Gen 0/1集合来观察分配情况分部分回答:
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;
}
}
这将大大减少由于属性名称而产生的内存压力
请注意,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;
}
}
}
然后在需要的地方添加以下逻辑(在两个p中
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.
}
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;
}
}
}
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);
var text = StringValueNameTable?.Get(_stringReference.Chars, _stringReference.StartIndex, _stringReference.Length) ?? _stringReference.ToString();