今天我一直在寻找存储我的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();
}
}
}
这是输出:
以下是序列化和反序列化的代码(我在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不会更快?是否有一些我可以做得更好的角色转移,以便反序列化变得更快?
答案 0 :(得分:1)
首先尝试从内部循环中创建二进制格式化程序。请注意,在反序列化时,BinaryFormatter也将“创建数组等等” - 没有任何神奇的方法可以避免对象图的实现。
此外,您还可以查看更快的序列化工具,例如Protobuf-net。查看http://maxondev.com/serialization-performance-comparison-c-net-formats-frameworks-xmldatacontractserializer-xmlserializer-binaryformatter-json-newtonsoft-servicestack-text/