2016-05-15 10 views
1

Heute suchte ich nach Möglichkeiten, meine Wavefront-Modelle zu speichern und hoffentlich auch die Leistung zu steigern. Ich wollte die Serialisierung untersuchen, hauptsächlich weil ich sie vorher noch nie benutzt hatte. Meiner Meinung nach sollte die Serialisierung/Deserialisierung schneller sein als das Parsing und Reinitialisieren eines Wavefront-Modells, jedoch zeigt meine Benchmark das Gegenteil. HierWarum ist die Objektdeserialisierung langsamer als die rohe Neuinitialisierung?

ist der Code für meine Benchmark:

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(); 
     } 
    } 
} 

Und hier ist der Ausgang:

enter image description here

Hier ist der Code für die Serialisierung und Deserialisierung (was ich auf Stackoverflow gefunden:

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); 
      } 
     } 
    } 
} 

Und hier ist mein WavefrontModel Klasse:

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(); 
     } 
    } 
} 

Sorry für den ganzen Code, ich wollte euch nur zeigen, was ich tat. Nun, meiner Meinung nach sollte die Deserialisierung schneller sein als die Reparatur des Modells, die Erstellung der Arrays usw. usw. Also meine Frage ist wirklich: Warum ist Deserialisierung nicht schneller? Gibt es etwas, das ich besser machen könnte, um die Rollen zu verschieben, so dass die Deserialisierung schneller wird?

+0

'BinaryFormatter' ist langsam. Ihr Textformat ähnelt JSON und ist langsamer als die meisten .Net JSON-Serialisierer, siehe http://james.newtonking.com/archive/2010/01/01/net-serialization-performance-comparison oder http: // maxondev .com/serialisierung-leistungsvergleich-c-net-formats-frameworks-xmldatacontractserializer-xmlserializer-binaryformatter-json-newtonsoft-servicestack-text/oder https://blogs.msdn.microsoft.com/youssefm/2009/07/ 10/Vergleichen-der-Leistung-von-Net-Serialisierer /. Siehe auch http://StackOverflow.com/Questions/703073 – dbc

+0

Und auch http://StackOverflow.com/Questions/4143421/fastest-way-to-serialize-and-deserialize-net-Object/4143437 – dbc

Antwort

1

Zunächst einmal versuchen, die Erstellung der binären Formatierer aus der inneren Schleife. Beachten Sie, dass "Erstellen der Arrays usw. usw." auch von BinaryFormatter beim Deserialisieren ausgeführt wird - es gibt keinen magischen Weg, das Objektdiagramm nicht zu erkennen.

Sie könnten auch in schnelleren Serialisierern wie Protobuf-net suchen. Check out http://maxondev.com/serialization-performance-comparison-c-net-formats-frameworks-xmldatacontractserializer-xmlserializer-binaryformatter-json-newtonsoft-servicestack-text/

+0

Ich habe zwei weitere erstellt Überladungen, die einen BinaryFormatter verwenden, um die Reinitialisierung von BinaryFormatter in der Schleife zu umgehen. Die Leistung ist gleich ... wahrscheinlich aufgrund einer Compiler-Optimierung, die es sowieso aus der Schleife entfernt hat. Ich werde versuchen, Zeit zu nehmen, um Protobuf zu benchmarken. – Krythic

Verwandte Themen