如何让System.Text.Json处理性能更高效

        .Net内System.Text.Json组件在序列化的性能方面已经非常出色了,如果你想追求更极端的JSON处理性能那下面所分享的内容对你来说应该是会有帮助的。接下来但要一下普通和极端的使用方式。

MemoryStream stream = new MemoryStream();
World wd = new World();
JsonSerializer.Serialize(stream, wd);
var bytes = stream.GetBuffer();
var arraySegment = new ArraySegment<byte>(bytes, 0, (int)stream.Length);
//var bytes=stream.ToArray();

如果你想把一个对象序化成Byte[]对象,相信大部分人都这样写就完事了。然后再针对MemoryStream建个Pool或[ThreadState]那似已经很完美了!
       其实Serialize每次调用都会创建Utf8JsonWriter对象,而真正工作的即是这个对象,如果你想节省这个对象可以创建这样一个对象并复用它

public class JsonExten
{


    public static Utf8JsonWriter GetJsonWriter(Stream stream)
    {
        Utf8JsonWriter utf8JsonWriter = _utf8JsonWriter ??= new Utf8JsonWriter(stream, new JsonWriterOptions { SkipValidation = true });
        utf8JsonWriter.Reset(stream);
        return utf8JsonWriter;
    }
    [ThreadStatic]
    private static Utf8JsonWriter _utf8JsonWriter;
}

可以针对Utf8JsonWriter建个Pool,但获取和回收也是有代码损耗的,并没有[ThreaStatic]来得简单实在,其实System.Text.Json设计者已经考虑到这问题所以添加了Reset方法来重置它。接下来就可以复用这个对象来序列化了

MemoryStream stream = new MemoryStream();
World wd = new World();
var writer = JsonExten.GetJsonWriter(stream);
JsonSerializer.Serialize(writer, wd);

这样就能节省每次Utf8JsonWriter的开销,不过这样序列化对象一定会存在反射或JIT过程生成一个额外的开销处理;如果你想节省这一步的损耗就可以更极端的去手动写入每一个成员

MemoryStream stream = new MemoryStream();
World wd = new World();
var writer = JsonExten.GetJsonWriter(stream);
writer.WriteStartObject();
writer.WriteNumber("Id", wd.Id);
writer.WriteNumber("RandomNumber", wd.RandomNumber);
writer.WriteEndObject();
writer.Flush();

以上手动处理每个成员效率是高,但使用起来就不灵活了,只有极个别追求性能而结构类型单一的服务适合使用。
        上面介绍了如果更优地使用Utf8JsonWriter,其实以上使用还是有很大优化空间的,主要原因是使用了MemoryStream序列化流的作为载体。即使使用MemoryStream Pool或[ThreaStatic]也无法解决这些内存复制的缺陷!接下来看下几段代码

Utf8JsonWriter基于Stream构建的方法和Reset方法

public Utf8JsonWriter(Stream utf8Json, JsonWriterOptions options = default(JsonWriterOptions))
    {
        if (utf8Json == null)
        {
            ThrowHelper.ThrowArgumentNullException("utf8Json");
        }


        if (!utf8Json.CanWrite)
        {
            throw new ArgumentException(System.SR.StreamNotWritable);
        }


        _stream = utf8Json;
        SetOptions(options);
        _arrayBufferWriter = new ArrayBufferWriter<byte>();
    }
    public void Reset(Stream utf8Json)
    {
        CheckNotDisposed();
        if (utf8Json == null)
        {
            throw new ArgumentNullException("utf8Json");
        }


        if (!utf8Json.CanWrite)
        {
            throw new ArgumentException(System.SR.StreamNotWritable);
        }


        _stream = utf8Json;
        if (_arrayBufferWriter == null)
        {
            _arrayBufferWriter = new ArrayBufferWriter<byte>();
        }
        else
        {
            _arrayBufferWriter.Clear();
        }


        _output = null;
        ResetHelper();
    }

Utf8JsonWriter.Flush的代码

public void Flush()
    {
        CheckNotDisposed();
        _memory = default(Memory<byte>);
        if (_stream != null)
        {
            if (BytesPending != 0)
            {
                _arrayBufferWriter.Advance(BytesPending);
                BytesPending = 0;
                _stream.Write(_arrayBufferWriter.WrittenSpan);
                BytesCommitted += _arrayBufferWriter.WrittenCount;
                _arrayBufferWriter.Clear();
            }


            _stream.Flush();
        }
        else if (BytesPending != 0)
        {
            _output.Advance(BytesPending);
            BytesCommitted += BytesPending;
            BytesPending = 0;
        }
    }

Stream的 Write(ReadOnlySpan<byte> buffer)代码

public virtual void Write(ReadOnlySpan<byte> buffer)
        {
            byte[] sharedBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length);
            try
            {
                buffer.CopyTo(sharedBuffer);
                Write(sharedBuffer, 0, buffer.Length);
            }
            finally
            {
                ArrayPool<byte>.Shared.Return(sharedBuffer);
            }
        }

细心的你有没发现问题?Utf8JsonWriter并不会直接把内容写入到Stream中,而是先写入到_arrayBufferWriter中,然后再提交的时候才写入Stream这是第一次复制。然而更让你无语的是Write(ReadOnlySpan<byte> buffer)方法,由于ReadOnlySpan<byte>是不能获取到引用Byte[]的...所以只能是先复制到一个临时的缓冲区后再调用Write(byte[] buffer, int offset, int count)写入!看上去是不是有点脱库子放屁?d64766ef9cb08059e26ab915dfdba17e.png

     通过以上分析想更好性能地使用Utf8JsonWriter应该基于IBufferWriter<byte>的构造函数创建它,IBufferWriter<byte>的默认实现是ArrayBufferWriter<byte>,它是一个自动扩容的缓冲区,比起Stream功能就简单多了。如果想达到更好的性能还是和Pool结合使用。

         BeetleX为了更好的解决多次复制问题也重写了Steam的方法,直接把内容写到网络游的缓冲区中

public override void Write(ReadOnlySpan<byte> buffer)
        {
            int count = buffer.Length;
            while (count > 0)
            {
                var memory = WriteSocketStream.GetWriteSpan(count);
                buffer.Slice(0, memory.Length).CopyTo(memory);
                WriteAdvance(memory.Length);
                buffer = buffer.Slice(memory.Length);
                count -= memory.Length;
            }
        }

然而这只能节省一次的复制,最好的办法就还是基于Stream实现IBufferWriter<byte>,这样就能直接序列化到网络流中一次复制环节也不存在了。

        总的来说以后各家的序列化组件都应该会兼容IBufferWriter<byte>的了,毕竟这种可以预分配后更新的结构在很多时候有着很大的性能优势。Stream这种一旦写入就无法更改之前写入的个别区域,很多时候为了计算长度不得先写入临时缓冲区,计算长度后再写入长度复制回来。

BeetleX

开源跨平台通讯框架(支持TLS)

提供HTTP,Websocket,MQTT,Redis,RPC和服务网关开源组件

个人微信:henryfan128    QQ:28304340
有丰富的高吞吐网络服务设计经验

关注公众号

041d14722c4536f2c5d6353638620334.jpeg

https://github.com/beetlex-io/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值