2013-11-09 2 views
4

Я пытаюсь написать сетевую библиотеку высокого уровня (в основном для удовольствия), поскольку пользователь может легко определить свои пакеты, выведя класс. Таким образом, синтаксический анализ сообщений выполняется легко. Пакет пользователя должен содержать только базовое значение types.Внедрение родовых геттеров и сеттеров для повышения производительности, чем отражение

Для этого мне нужно получить доступ к каждому полю каждого пользовательского пакета. Эту проблему можно легко сделать с помощью рефлексии, но поскольку рефлексия медленна, я не могу ее использовать. Чтобы сделать это быстро, я сделал класс, чтобы вводить геттеры и сеттеры для каждого пользовательского поля пакета во время выполнения (нашел это где-то в StackOverflow). Геттеры - Func<UserDefinedPacket, fieldType>, а сеттеры - Action<UserDefinedPAcket, setValue>.

В этой статье:, потому что пользовательские пакеты определены в другой сборке. Я не могу знать тип получателей или сеттеров во время компиляции. Самый производный класс, который я могу использовать для геттеров и сеттеров, - Delegate. Это означает, что я могу использовать DynamicInvoke, поэтому снова получаю отражение ... но поскольку я знаю, что методы являются действительными Funcs и Actions, но не могут их отличать, я могу использовать динамический тип и вызывать Invoke. Динамический тип улучшил производительность примерно в 5 раз, но настройка и получение значения по-прежнему медленнее по сравнению с обычным полевым доступом (что примерно в 100 раз быстрее), что делает эту библиотеку непригодной.

В конечном счете, я хочу, чтобы иметь возможность преобразовать определенный пользователем класс в массив байтов. Также я не хочу сериализовать класс, потому что сериализация включает в себя некоторые нежелательные данные, которые я действительно не хочу отправлять по сети, и сериализация выглядит довольно медленной.

Вот вопрос: Могу ли я на самом деле сделать методы GetVaue и SetValue быстрее? Я попробовал лить делегатов на нужные функции/действия (переместил пользовательский класс пакета в библиотеку, чтобы это было плохо), а сеттеры заняли около 50 мс вместо 300 мс. Я надеялся получить эту производительность для общих пакетов.

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

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

}

Output (соответствующие вещи только):

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

Отражение геттер и сеттер на самом деле динамические вызовы, и они, как правило, в конечном итоге принимает 300 мс.

Также, поскольку код довольно длинный, я отправил его также here.

ответ

2

Вы считали несколько иной подход? Поскольку основной целью геттеров и сеттеров в этом случае является сериализация и десериализация, возможно, вам следует сосредоточиться на динамическом создании методов сериализации, что позволило бы устранить накладные расходы на доступ к этим полям один за другим с использованием динамических методов.

Например, давайте предположим, что вы хотите использовать 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 действительно решает мою проблему лучше, но вопрос в том, что я мог бы повысить производительность для геттеров и сеттеров).

Я сделал добытчиками и сеттеров, как это:

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. Теперь я знаю типы делегатов во время выполнения, и я могу уйти только из-за боковых и ненужных накладных расходов. Это говорит о том, что я заставлял геттеры и сеттеры работать около 50-70 мс (для 5 полей * 100.000 объектов и комплектов).

Смежные вопросы