C# Protobuf 메모리 0 할당을 서열화하는 방법

10402 단어
제목은 간단하다. 바로 IMessage 대상이 어떻게 Byte가 되는가[]
 
정답1:
msg.ToByteArray()

이것은 틀림없이 우리의 요구에 부합되지 않을 것이다
 
정답2:
using var memoryStream = new MemoryStream();
using var codedOutputStream = new CodedOutputStream(memoryStream);
msg.WriteTo(codedOutputStream);
codedOutputStream.Flush();
memoryStream.ToArray();

이 안에는 memory Stream, coded Output Stream, 그리고 ToArray가 하나의 대상을 만들었고 Memory Stream 내부에byte[] 대상이 하나 더 생겼다.
부적합
 
정답3:
어떤 이는 네가 Memory Stream에게 byte[] slice를 전달할 수 있다고 한다. Memory Stream이 직접 byte[]를 사용하도록 한다.
var bytes = new byte[msg.CalculateSize()];
using var memoryStream = new MemoryStream();
using var codedOutputStream = new CodedOutputStream(memoryStream);
msg.WriteTo(codedOutputStream);
codedOutputStream.Flush();

이번 소식은 바로 bytes에 서열화되었다. 그러나memory Stream 대상,codec Output Stream 그리고memory Stream 내부의byte[]가 모두 있었다. 나는 대상을 서열화했는데 3개의 쓰레기 대상이 생겼다.
 
따라서 Coded OutputStream 클래스를 자세히 살펴보십시오.
        /// 
        /// Creates a new CodedOutputStream that writes directly to the given
        /// byte array. If more bytes are written than fit in the array,
        /// OutOfSpaceException will be thrown.
        /// 
        public CodedOutputStream(byte[] flatArray) : this(flatArray, 0, flatArray.Length)
        {
        }

        /// 
        /// Creates a new CodedOutputStream that writes directly to the given
        /// byte array slice. If more bytes are written than fit in the array,
        /// OutOfSpaceException will be thrown.
        /// 
        private CodedOutputStream(byte[] buffer, int offset, int length)
        {
            this.output = null;
            this.buffer = buffer;
            this.position = offset;
            this.limit = offset + length;
            leaveOpen = true; // Simple way of avoiding trying to dispose of a null reference
        }

提供了一个byte[]的构造函数, 但是没提供slice的构造函数, 好在有一个私有的构造函数

 

答案4:

这边就不写代码了, 大概意思就是通过反射私有构造函数来构造一个CodedOutputStream对象, 来省掉MemoryStream和他内部的byte[]

现在离答案已经比较接近了

 

那我们的问题是, 能不能连CodedOutputStream也省掉呢?

 

答案5来了:

经过仔细观察, 发现这个类没有使用Stream的情况下, 就只需要修改buffer, limit, 和position几个成员就行了, 虽然是private成员, 但是C#还是能修改

下来立马实践

        delegate void ClearCodedOutputStream(CodedOutputStream stream, byte[] buffer, int offset, int count);
        static ClearCodedOutputStream ResetCodedOutputStream;
        static CodedOutputStream codedOutputStream = new CodedOutputStream(new byte[10]);

        static unsafe void Encode(IMessage msg, byte[] buffer)
        {
            ResetCodedOutputStream(codedOutputStream, buffer, 0, buffer.Length);
            msg.WriteTo(codedOutputStream);
            codedOutputStream.Flush();
        }

        static Action MakeSetter(FieldInfo field)
        {
            DynamicMethod m = new DynamicMethod(
                "setter", typeof(void), new Type[] { typeof(T), typeof(TValue) }, typeof(Program));
            ILGenerator cg = m.GetILGenerator();

            cg.Emit(OpCodes.Ldarg_0);
            cg.Emit(OpCodes.Ldarg_1);
            cg.Emit(OpCodes.Stfld, field);
            cg.Emit(OpCodes.Ret);

            return (Action)m.CreateDelegate(typeof(Action));
        }

        static void Main(string[] args)
        {
            var bufferField = typeof(CodedOutputStream).GetField("buffer", BindingFlags.NonPublic | BindingFlags.Instance);
            var limitField = typeof(CodedOutputStream).GetField("limit", BindingFlags.NonPublic | BindingFlags.Instance);
            var positionField = typeof(CodedOutputStream).GetField("position", BindingFlags.NonPublic | BindingFlags.Instance);

            var setLimit = MakeSetterint>(limitField);
            var setPosition = MakeSetterint>(positionField);
            var setBuffer = MakeSetterbyte[]>(bufferField);

            ResetCodedOutputStream = (stream, buffer, offset, length) => 
            {
                //this.buffer = buffer;
                //this.position = offset;
                //this.limit = offset + length;
                setBuffer(stream, buffer);
                setPosition(stream, offset);
                setLimit(stream, offset + length);
            };

var buffer = new byte[msg.CalculateSize()]; Encode(msg, buffer); }

이 실례 코드에는 static의 전역 코드인 Coded Output Stream을 사용했는데, 진정으로 사용할 때는 반드시 라인의 안전을 보장해야 한다.
 
그래서 다음 문제는:
1. CodedOutputStream 객체 스레드의 보안을 보장하는 방법
2. var buffer = new byte[msg.CalculateSize()]를 어떻게 사용하는가;이것도 아껴요.
 
이 두 문제는 독자에게 생각하도록 남겨 두어라.

좋은 웹페이지 즐겨찾기