2013-11-09 45 views
4

我試圖編寫一個高級網絡庫(主要是爲了好玩),以便用戶可以通過派生類輕鬆定義它們的數據包。這種解析消息的方式非常簡單。用戶數據包應只包含基本值types注入泛型getter和setter以獲得比反射更好的性能

爲了做到這一點,我需要訪問每個用戶定義數據包的每個字段。這個問題很容易用反射來完成,但由於反射速度很慢,所以我不能使用它。爲了做到這一點,我做了一個類,在運行時爲每個用戶定義的數據包字段注入getter和setter(在StackOverflow的某處找到它)。獲得者是Func<UserDefinedPacket, fieldType>,並且制定者是Action<UserDefinedPAcket, setValue>

這裏的問題是:因爲用戶分組在另一組件I所定義不可能知道的吸氣劑或設置器在編譯時的類型。我可以用於getter和setter的派生類最多的是Delegate。這意味着我只能使用DynamicInvoke,所以我再次進行反射......但是因爲我知道這些方法實際上是Funcs和Actions,但實際上並不能轉換它們,所以我可以使用動態類型並調用Invoke。動態類型將性能提高了約5倍,但與正常字段訪問相比,設置和獲取值的速度仍然較慢(約快100倍),導致此庫無法使用。

最終我希望能夠將用戶定義的類轉換爲字節數組。我也不想序列化這個類,因爲序列化包含了一些垃圾數據,我真的不想通過網絡發送,而且序列化看起來很慢。

這裏是問題:我真的可以讓GetVaue和SetValue方法更快嗎?我嘗試將代理投射到所需的功能/操作(將圖庫中的自定義數據包類移動,所以這不太好),並且安裝程序花費了大約50ms而不是300ms。我希望能夠獲得通用數據包的性能。

namespace MirrorNet 
{ 
    // Base class for packets 
    // This will be derived by users and should only contain basic type fields 
    public class UserPacket 
    { 
    } 

public class MirrorNetManager 
{ 
    private static MirrorNetManager instance = new MirrorNetManager(); 

    public static MirrorNetManager Instance 
    { 
     get { return instance; } 
    } 

    // Dictionary: packetType -> field -> getter | setter 
    private class PropertyDictionary : Dictionary<Type, Dictionary<FieldInfo, Delegate>> 
    { 
    } 

    private Dictionary<int, PacketConstructor> m_packetConstructors = new Dictionary<int, PacketConstructor>(); 
    private PropertyDictionary m_packetFieldGetters = new PropertyDictionary(); 
    private PropertyDictionary m_packetFieldSetters = new PropertyDictionary(); 

    public void SetValue(UserPacket packet, FieldInfo field, object value) 
    { 
     var setDelegate = m_packetFieldSetters[packet.GetType()][field]; 

     dynamic setAction = setDelegate; //Convert.ChangeType(setDelegate, setDelegate.GetType()); 
     dynamic setObject = packet;  //Convert.ChangeType(packet, packet.GetType()); 
     dynamic setValue = value;  //Convert.ChangeType(value, value.GetType()); 

     setAction.Invoke(setObject, setValue); 

     //setDelegate.DynamicInvoke(packet, value); 
    } 

    public object GetValue(UserPacket packet, FieldInfo field) 
    { 
     var getDelegate = m_packetFieldGetters[packet.GetType()][field]; 

     dynamic getFunction = getDelegate; //Convert.ChangeType(getDelegate, getDelegate.GetType()); 
     dynamic getObject = packet;  //Convert.ChangeType(packet, packet.GetType()); 

     return getFunction.Invoke(getObject); 

     //return getDelegate.DynamicInvoke(packet); 
    } 

    public void InitializePackets(Assembly packetsAssembly) 
    { 
     var typesArray = packetsAssembly.GetTypes(); 

     foreach (Type type in typesArray) 
     { 
      if (type.BaseType == typeof(UserPacket)) 
      { 
       InsertPacketConstructor(type); 
       InsertSettersAndGetters(type); 
      } 
     } 
    } 

    private void InsertPacketConstructor(Type packetType) 
    { 
     foreach (var member in packetType.GetFields()) 
     { 
      Console.WriteLine(member); 
      // TODO: Implement 
     } 
    } 

    private void InsertSettersAndGetters(Type type) 
    { 
     Dictionary<FieldInfo, Delegate> getters = new Dictionary<FieldInfo, Delegate>(); 
     Dictionary<FieldInfo, Delegate> setters = new Dictionary<FieldInfo, Delegate>(); 

     foreach (FieldInfo field in type.GetFields()) 
     { 
      Delegate getDelegate = CreateGetter(type, field.FieldType, field); 
      Delegate setDelegate = CreateSetter(type, field.FieldType, field); 

      getters.Add(field, getDelegate); 
      setters.Add(field, setDelegate); 
     } 

     m_packetFieldGetters.Add(type, getters); 
     m_packetFieldSetters.Add(type, setters); 
    } 

    private Delegate CreateGetter(Type classType, Type getReturnType, FieldInfo field) 
    { 
     string methodName = field.ReflectedType.FullName + ".get_" + field.Name; 

     Type[] parameterTypes  = new Type[1] { classType }; 
     DynamicMethod getterMethod = new DynamicMethod(methodName, getReturnType, parameterTypes, true); 
     ILGenerator gen = getterMethod.GetILGenerator(); 

     if (field.IsStatic) 
     { 
      gen.Emit(OpCodes.Ldsfld, field); 
     } 
     else 
     { 
      gen.Emit(OpCodes.Ldarg_0); 
      gen.Emit(OpCodes.Ldfld, field); 
     } 
     gen.Emit(OpCodes.Ret); 

     // Create the specific Func<,> instance 
     Type[] typeArgs = new Type[] { classType, getReturnType }; 
     Type generic  = typeof(Func<,>); 
     Type genInstance = generic.MakeGenericType(typeArgs); 

     Delegate getterDelegate = getterMethod.CreateDelegate(genInstance); 

     return getterDelegate; 
    } 
    private Delegate CreateSetter(Type classType, Type setValueType, FieldInfo field) 
    { 
     string methodName = field.ReflectedType.FullName + ".set_" + field.Name; 
     Type[] parameters = new Type[2] 
     { 
      classType, 
      setValueType 
     }; 
     DynamicMethod setterMethod = new DynamicMethod(methodName, null, parameters); 
     ILGenerator gen = setterMethod.GetILGenerator(); 

     if (field.IsStatic) 
     { 
      gen.Emit(OpCodes.Ldarg_1); 
      gen.Emit(OpCodes.Stsfld, field); 
     } 
     else 
     { 
      gen.Emit(OpCodes.Ldarg_0); 
      gen.Emit(OpCodes.Ldarg_1); 
      gen.Emit(OpCodes.Stfld, field); 
     } 
     gen.Emit(OpCodes.Ret); 

     // Create the specific Action<,> instance 
     Type[] typeArgs = new Type[] { classType, setValueType }; 
     Type generic  = typeof(Action<,>); 
     Type genInstance = generic.MakeGenericType(typeArgs); 

     Delegate ret = setterMethod.CreateDelegate(genInstance); 
     return ret; 
     } 


    } 
} 

// THIS IS IN A DIFERENT ASSEMBLY 
namespace MirrorNetTesting 

{ 
// This is just an example packet 
public class StudentPacket : UserPacket 
{ 
    public int age; 
    public int height; 
    public double grades; 
    public string firstName; 
    public string lastName; 
} 
class Program 
{ 
    static void Main(string[] args) 
    { 
     Assembly asm = Assembly.GetAssembly(typeof(StudentPacket)); 
     MirrorNetManager.Instance.InitializePackets(asm); 

     PerformanceTesting(); 

     Console.ReadLine(); 
    } 

    public static void PerformanceTesting() 
    { 
     int studentsCount = 1000 * 100; 
     StudentPacket[] studentsArray = new StudentPacket[studentsCount]; 

     ////////////////////////////////////////////////////////////////////////// 

     Random rnd = new Random(); 

     for (int i = 0; i < studentsArray.Length; i++) 
     { 
      StudentPacket student = new StudentPacket(); 

      student.age = rnd.Next(); 
      student.height = rnd.Next(); 
      student.grades = rnd.NextDouble(); 
      student.firstName = "First " + rnd.Next().ToString(); 
      student.lastName = "Last " + rnd.Next().ToString(); 

      studentsArray[i] = student; 
     } 

     var fieldsArray = typeof(StudentPacket).GetFields().ToArray(); 

     ////////////////////////////////////////////////////////////////////////// 

     // Begin normal getter test 
     Console.WriteLine("Testing normal getters"); 
     Stopwatch normalGetterSw = new Stopwatch(); 

     normalGetterSw.Start(); 

     foreach (var student in studentsArray) 
     { 
      //object getValue; 

      var getAge  = student.age; 
      var getHeight = student.height; 
      var getGrades = student.grades; 
      var getFirstName = student.firstName; 
      var getLastName = student.lastName; 
     } 

     normalGetterSw.Stop(); 

     ////////////////////////////////////////////////////////////////////////// 

     // Begin reflection getter test 
     Console.WriteLine("Testing reflection getters"); 
     Stopwatch reflectionGetterSw = new Stopwatch(); 

     reflectionGetterSw.Start(); 

     foreach (var student in studentsArray) 
     { 
      object getValue; 

      for (int i = 0; i < fieldsArray.Length; i++) 
      { 
       FieldInfo field = fieldsArray[i]; 
       getValue = MirrorNetManager.Instance.GetValue(student, field); 
      } 
     } 

     reflectionGetterSw.Stop(); 

     ////////////////////////////////////////////////////////////////////////// 

     // Begin normal setter test 
     Console.WriteLine("Testing normal setters"); 
     Stopwatch normalSetterSw = new Stopwatch(); 

     int age  = 10; 
     int height = 12; 
     double grades = 1432.523d; 
     string firstName = "first name"; 
     string lastName = "last name"; 

     normalSetterSw.Start(); 

     foreach (var student in studentsArray) 
     { 
      student.age  = age; 
      student.height = height; 
      student.grades = grades; 
      student.firstName = firstName; 
      student.lastName = lastName; 
     } 

     normalSetterSw.Stop(); 

     ////////////////////////////////////////////////////////////////////////// 

     // Begin reflection setter test 
     Console.WriteLine("Testing reflection setters "); 
     Stopwatch reflectionSetterSw = new Stopwatch(); 

     object[] setValues = new object[] 
     { 
      age, 
      height, 
      grades, 
      firstName, 
      lastName 
     }; 

     reflectionSetterSw.Start(); 

     foreach (var student in studentsArray) 
     { 
      for (int i = 0; i < fieldsArray.Length; i++) 
      { 
       FieldInfo field = fieldsArray[i]; 
       MirrorNetManager.Instance.SetValue(student, field, setValues[i]); 
      } 
     } 

     reflectionSetterSw.Stop(); 

     ////////////////////////////////////////////////////////////////////////// 

     Console.WriteLine("Normal getter:  \t {0}",  normalGetterSw.ElapsedMilliseconds); 
     Console.WriteLine("Normal setter:  \t {0}",  normalSetterSw.ElapsedMilliseconds); 
     Console.WriteLine("Reflection getter: \t {0}", reflectionGetterSw.ElapsedMilliseconds); 
     Console.WriteLine("Reflection setter: \t {0}", reflectionSetterSw.ElapsedMilliseconds); 

     ////////////////////////////////////////////////////////////////////////// 
    } 
} 

}

輸出(相關的東西只):

Normal getter:   3 
Normal setter:   4 
Reflection getter:  261 
Reflection setter:  183 

反思getter和setter實際上是動態調用,他們通常最終以300毫秒。

此外,由於代碼相當長,我也發佈了它here也。

回答

2

你有沒有考慮一個稍微不同的方法?在這種情況下,作爲getter和setter的主要目的是序列化和反序列化,也許你應該專注於動態生成序列化方法,這將消除使用動態方法逐個訪問這些字段的開銷。

例如,假設你想使用BinaryFormatter序列化(雖然你可能會選擇更好的東西),一個目標將是動態生成的方法,如:

static private byte[] SerializeStudentPacket(StudentPacket packet) 
    { 
     var bf = new BinaryFormatter(); 
     var ms = new MemoryStream(); 
     bf.Serialize(ms, packet.age); 
     bf.Serialize(ms, packet.firstName); 
     bf.Serialize(ms, packet.grades); 
     bf.Serialize(ms, packet.height); 
     bf.Serialize(ms, packet.lastName); 
     return ms.ToArray(); 
    } 

哪些可以比簡單通過的ILGenerator通過使用LINQ表達式:

ParameterExpression @object = Expression.Parameter(typeof(StudentPacket), "@object"); 
    MethodInfo serializeMethodInfo = typeof(BinaryFormatter).GetMethod("Serialize", new Type[] { typeof(Stream), typeof(object) }); 
    MethodInfo toArrayMethodInfo = typeof(MemoryStream).GetMethod("ToArray"); 
    var bf = Expression.Variable(typeof(BinaryFormatter), "bf"); 
    var ms = Expression.Variable(typeof(System.IO.MemoryStream), "ms"); 
    List<Expression> expressions = new List<Expression>(); 
    expressions.Add(
     Expression.Assign(bf, Expression.New(typeof(BinaryFormatter)))); 
    expressions.Add(
     Expression.Assign(ms, Expression.New(typeof(MemoryStream)))); 
    foreach (FieldInfo field in typeof(StudentPacket).GetFields()) 
    { 
     expressions.Add(
      Expression.Call(bf, serializeMethodInfo, ms, 
          Expression.Convert(Expression.Field(@object, field.Name), 
               typeof(object)))); 
    } 
    expressions.Add(Expression.Call(ms, toArrayMethodInfo)); 
    var lambda = Expression.Lambda(
     Expression.Block(
      new[] { bf, ms }, 
      expressions 
     ), 
     @object); 

那麼你當然可以存儲lambda.Compile()結果序列化StudentPacket。同樣的方法也可以用於反序列化。

+1

謝謝。我專注於使代碼更好地執行,實際上我忘記了它的目的。 – dburner

1

爲了完整起見,我設法降低了獲取者和設置者的訪問時間(kkokosa的確更好地解決了我的問題,但問題是我可以獲得更好的獲取者和設置者的性能)。

我做了getter和setter方法是這樣的:

get_firstName(UserPacket packet) 
{ 
    var pk = (StudentPacket)packet; 
    return pk.firstName; 
} 

void set_firstName(UserPacket packet, object value) 
{ 
    var pk = (StudentPacket)packet; 
    pk.firstName = (string)value; 
} 

而且使用表達式樹,而不是注入的ILGenerator他們。現在我知道運行時代表的類型,我只能用拳擊和拆箱的開銷逃脫。 這個beeing說我讓吸氣劑和吸附劑運行在大約50-70ms(5場* 100000對象組並獲得)。