为什么对象反序列化比原始重新初始化慢?

时间:2016-05-15 16:21:56

标签: c# serialization

今天我一直在寻找存储我的Wavefront模型的方法,并希望也能提高性能。我想查看序列化,主要是因为我之前从未使用过它。在我看来,序列化/反序列化应该比解析和重新初始化Wavefront模型更快,但是,我的基准测试显示其他方式。

以下是我的基准测试的代码:

using System;
using System.Diagnostics;
using GrimoireTactics.Framework.OpenGL.Modeling;
using GrimoireTactics.Framework.Utilities;

namespace GrimoireDevelopmentKit.DevelopmentKit
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {

            WavefrontModel model;
            Stopwatch benchmark = new Stopwatch();
            //
            // Benchmark Deserialization
            //

            // Do a warm up 
            for (int i = 0; i < 500; i++)
            {
                model = ResourceCompiler.ReadFromBinaryFile<WavefrontModel>("C:/Users/Krythic/Desktop/Compiled.sfg"); // Sfg is an extension I wanted to use
            }
            benchmark.Start();
            model = ResourceCompiler.ReadFromBinaryFile<WavefrontModel>("C:/Users/Krythic/Desktop/Compiled.sfg"); // Sfg is an extension I wanted to use
            benchmark.Stop();
            Console.WriteLine("Deserialization: "+ benchmark.Elapsed);
            benchmark.Reset();


            //
            // Benchmark Plain Old Initialization
            //
            model = new WavefrontModel();

            // Do a Warm up
            for (int i = 0; i < 500; i++)
            {
                model.Load("C:/Users/Krythic/Desktop/Closet.obj");
            }
            benchmark.Start();
            model.Load("C:/Users/Krythic/Desktop/Closet.obj");
            benchmark.Stop();
            Console.WriteLine("Plain Old Initialization: " + benchmark.Elapsed);
            Console.Read();
        }
    }
}

这是输出:

enter image description here

以下是序列化和反序列化的代码(我在Stackoverflow上找到了:

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace GrimoireTactics.Framework.Utilities
{
    public class ResourceCompiler
    {
        /// <summary>
        /// Writes the given object instance to a binary file.
        /// <para>Object type (and all child types) must be decorated with the [Serializable] attribute.</para>
        /// <para>To prevent a variable from being serialized, decorate it with the [NonSerialized] attribute; cannot be applied to properties.</para>
        /// </summary>
        /// <typeparam name="T">The type of object being written to the XML file.</typeparam>
        /// <param name="filePath">The file path to write the object instance to.</param>
        /// <param name="objectToWrite">The object instance to write to the XML file.</param>
        /// <param name="append">If false the file will be overwritten if it already exists. If true the contents will be appended to the file.</param>
        public static void WriteToBinaryFile<T>(string filePath, T objectToWrite, bool append = false)
        {
            using (Stream stream = File.Open(filePath, append ? FileMode.Append : FileMode.Create))
            {
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                binaryFormatter.Serialize(stream, objectToWrite);
            }
        }

        /// <summary>
        /// Reads an object instance from a binary file.
        /// </summary>
        /// <typeparam name="T">The type of object to read from the XML.</typeparam>
        /// <param name="filePath">The file path to read the object instance from.</param>
        /// <returns>Returns a new instance of the object read from the binary file.</returns>
        public static T ReadFromBinaryFile<T>(string filePath)
        {
            using (Stream stream = File.Open(filePath, FileMode.Open))
            {
                BinaryFormatter binaryFormatter = new BinaryFormatter();
                return (T)binaryFormatter.Deserialize(stream);
            }
        }
    }
}

这是我的WavefrontModel类:

using System;
using System.Collections.Generic;
using System.IO;
using GrimoireTactics.Framework.OpenGL.Texturing;
using OpenTK;
using OpenTK.Graphics.OpenGL;

namespace GrimoireTactics.Framework.OpenGL.Modeling
{
    [Serializable]
    public class WavefrontModel
    {
        public Vector3[] Vertices;
        public Vector2[] TexCoords;
        public Vector3[] Normals;
        public Face[] Faces;
        public string ModelSource;
        public string Name;
        public Material Material;
        /// <summary>
        /// A static buffer used by all models when they are loaded.
        /// </summary>
        private static readonly string[] FileBuffer = new string[15];
        /// <summary>
        /// A static buffer used by all models when they are loaded.
        /// </summary>
        private static readonly string[] IndiceBuffer = new string[3];
        /// <summary>
        /// A static buffer used by all models when they are loaded.
        /// </summary>
        private static readonly FaceIndices[] VerticesIndexBuffer = new FaceIndices[3];

        /// <summary>
        /// The Triangle Count of this model.
        /// </summary>
        public int TriCount
        {
            get
            {
                return Faces.Length;
            }
        }

        public WavefrontModel()
        {

        }

        public WavefrontModel(string modelPath, Material material)
        {
            this.ModelSource = modelPath;
            this.Material = material;
        }

        public WavefrontModel(string modelPath)
        {
            this.ModelSource = modelPath;
            this.Material = null;
        }

        public WavefrontModel(string[] data)
        {
            this.ModelSource = String.Empty;
            this.Material = null;
            Load(data);
        }

        public WavefrontModel(string[] data, Material material)
        {
            this.ModelSource = String.Empty;
            this.Material = material;
            Load(data);
        }

        /// <summary>
        /// Loads a model from the desired Wavefront.obj source given
        /// at constructor initialization.
        /// </summary>
        public void Load()
        {
            Load(this.ModelSource);
        }

        /// <summary>
        /// Loads a model from a Wavefront.obj located on disk.
        /// </summary>
        /// <param name="file"></param>
        public void Load(string file)
        {
            Parse(File.ReadAllLines(file), this);
        }

        /// <summary>
        /// Initializes this model with the data provided.
        /// </summary>
        /// <param name="data"></param>
        public void Load(string[] data)
        {
            Parse(data, this);
        }

        /// <summary>
        /// Current Benchmarked time(Warm boot)
        /// </summary>
        /// <param name="data"></param>
        /// <param name="model"></param>
        public static void Parse(string[] data, WavefrontModel model)
        {
            // Create Header
            int totalVertices = 0;
            int totalNormals = 0;
            int totalTextureCoordinates = 0;
            int totalFaces = 0;
            for (int i = 0; i < data.Length; i++)
            {
                switch (data[i][0])
                {
                    case 'v': // Geometric Parameter
                        switch (data[i][1])
                        {
                            case ' ': // Detect Vertices
                                totalVertices++;
                                break;
                            case 't': // Detect TexCoords
                                totalTextureCoordinates++;
                                break;
                            case 'n': // Detect Normals
                                totalNormals++;
                                break;
                        }
                        break;
                    case 'f':
                        totalFaces++;
                        break;
                }
            }
            // Create the Buffers
            model.Vertices = new Vector3[totalVertices];
            model.Normals = new Vector3[totalNormals];
            model.TexCoords = new Vector2[totalTextureCoordinates];
            model.Faces = new Face[totalFaces];
            // Load the Data
            // Iterators
            int verticesIterator = 0;
            int normalsIterator = 0;
            int textureCoordinatesIterator = 0;
            int facesIterator = 0;
            for (int line = 0; line < data.Length; line++)
            {
                string[] lineTokens = SplitStringFast(data[line], ' ', FileBuffer);
                switch (lineTokens[0])
                {
                    case "v": // Vector
                        Vector3 vertex = new Vector3
                        {
                            X = ParseFloatFast(lineTokens[1]),
                            Y = ParseFloatFast(lineTokens[2]),
                            Z = ParseFloatFast(lineTokens[3])
                        };
                        model.Vertices[verticesIterator] = vertex;
                        verticesIterator++;
                        break;
                    case "vt": // Texture Coordinate
                        Vector2 textureCoordinate = new Vector2
                        {
                            X = ParseFloatFast(lineTokens[1]), // U
                            Y = -ParseFloatFast(lineTokens[2]) // V (Inverted)
                        };
                        model.TexCoords[textureCoordinatesIterator] = textureCoordinate;
                        textureCoordinatesIterator++;
                        break;
                    case "vn": // Normal
                        Vector3 normal = new Vector3
                        {
                            X = ParseFloatFast(lineTokens[1]),
                            Y = ParseFloatFast(lineTokens[2]),
                            Z = ParseFloatFast(lineTokens[3])
                        };
                        model.Normals[normalsIterator] = normal;
                        normalsIterator++;
                        break;
                    case "f": // Face (Triangle indices)
                        for (int i = 0; i < 3; i++)
                        {
                            string[] parameters = SplitStringFast(lineTokens[i + 1], '/', IndiceBuffer);
                            FaceIndices indices = new FaceIndices
                            {
                                Vertex = ParseUInt32Fast(parameters[0]) - 1,
                                Texture = ParseUInt32Fast(parameters[1]) - 1,
                                Normal = ParseUInt32Fast(parameters[2]) - 1
                            };
                            VerticesIndexBuffer[i] = indices;
                        }
                        model.Faces[facesIterator] = new Face(VerticesIndexBuffer[0], VerticesIndexBuffer[1], VerticesIndexBuffer[2]);
                        facesIterator++;
                        break;
                }
            }
        }

        /// <summary>
        /// A custom implementation of Int32.Parse. This
        /// function is, on average, 5-6x faster than the one
        /// offered by .NET. This function assumes that the string
        /// given will yield a positive integer.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static int ParseUInt32Fast(string value)
        {
            int result = 0;
            for (int i = 0; i < value.Length; i++)
            {
                result = 10 * result + (value[i] - 48);
            }
            return result;
        }

        /// <summary>
        /// A custom implementation of String.Split(). Realistically, this 
        /// function is not much faster than what .NET offers; it gains speed
        /// more from a preset buffer mechanism.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="delimiter"></param>
        /// <param name="buffer"></param>
        /// <returns></returns>
        private static string[] SplitStringFast(string value, char delimiter, string[] buffer)
        {
            int resultIndex = 0;
            int startIndex = 0;
            for (int i = 0; i < value.Length; i++)
            {
                if (value[i] == delimiter)
                {
                    buffer[resultIndex] = value.Substring(startIndex, i - startIndex);
                    resultIndex++;
                    startIndex = i + 1;
                }
            }
            buffer[resultIndex] = value.Substring(startIndex, value.Length - startIndex);
            return buffer;
        }

        /// <summary>
        /// A custom implementation of Float.Parse. This
        /// function is, on average, 5-6x faster than the one
        /// offered by .NET
        /// </summary>
        /// <param name="inputData">The inputData.</param>
        /// <returns></returns>
        private static float ParseFloatFast(string inputData)
        {
            float result = 0;
            int position = 0;
            int inputLength = inputData.Length;
            char firstCharacter = inputData[0];
            float negativeSign = 1;
            if (firstCharacter == '-')
            {
                negativeSign = -1;
                ++position;
            }
            while (true)
            {
                if (position >= inputLength)
                {
                    return negativeSign * result;
                }
                firstCharacter = inputData[position++];
                if (firstCharacter < '0' || firstCharacter > '9')
                {
                    break;
                }
                result = (float)((result * 10.0) + (firstCharacter - '0'));
            }
            float exponent = 0.1f;
            while (position < inputLength)
            {
                firstCharacter = inputData[position++];
                result += (firstCharacter - '0') * exponent;
                exponent *= 0.1f;
            }
            return negativeSign * result;
        }

        /// <summary>
        /// Renders the Model using deprecated immediate mode. This
        /// function exists only for testing purposes.
        /// </summary>
        public void Render()
        {
            GL.Enable(EnableCap.Texture2D);
            GL.Color3(Material.AmbientColor);
            GL.BindTexture(TextureTarget.Texture2D, Material.Diffuse);
            GL.Begin(PrimitiveType.Triangles);
            for (int i = 0; i < Faces.Length; i++)
            {
                for (int index = 0; index < Faces[i].Indices.Length; index++)
                {
                    Vector3 v = Vertices[Faces[i].Indices[index].Vertex];
                    Vector3 n = Normals[Faces[i].Indices[index].Normal];
                    Vector2 tc = TexCoords[Faces[i].Indices[index].Texture];
                    GL.Normal3(n.X, n.Y, n.Z);
                    GL.TexCoord2(tc.X, tc.Y);
                    GL.Vertex3(v.X, v.Y, v.Z);
                }
            }
            GL.End();
        }
    }
}

对不起所有的代码,我只想向你们展示我正在做的一切。现在,在我看来,反序列化应该比重新解析模型,创建数组等等更快。所以我的问题是:为什么deserializtaion不会更快?是否有一些我可以做得更好的角色转移,以便反序列化变得更快?

1 个答案:

答案 0 :(得分:1)

首先尝试从内部循环中创建二进制格式化程序。请注意,在反序列化时,BinaryFormatter也将“创建数组等等” - 没有任何神奇的方法可以避免对象图的实现。

此外,您还可以查看更快的序列化工具,例如Protobuf-net。查看http://maxondev.com/serialization-performance-comparison-c-net-formats-frameworks-xmldatacontractserializer-xmlserializer-binaryformatter-json-newtonsoft-servicestack-text/