4

クラスを派生させることで、ユーザーがパケットを簡単に定義できるように、高レベルのネットワーク ライブラリを (主に楽しみのために) 記述しようとしています。このように、メッセージの解析は非常に簡単です。ユーザー パケットには、基本的な値の型のみを含める必要があります。

これを行うには、すべてのユーザー定義パケットの各フィールドにアクセスする必要があります。この問題はリフレクションで簡単に解決できますが、リフレクションは非常に遅いので使用できません。これを迅速に行うために、実行時にユーザー定義のパケット フィールドごとにゲッターとセッターを挿入するクラスを作成しました (これは StackOverflow のどこかにあります)。ゲッターはFunc<UserDefinedPacket, fieldType>で、セッターはAction<UserDefinedPAcket, setValue>です。

ここに問題があります。ユーザー パケットが別のアセンブリで定義されているため、コンパイル時にゲッターまたはセッターの型を知ることができない可能性があります。ゲッターとセッターに使用できる最も派生したクラスはDelegate. これは、DynamicInvoke しか使用できないことを意味するので、再びリフレクションに進みます...しかし、メソッドが実際には Funcs と Action であることはわかっていますが、実際にはそれらをキャストできないため、動的型を使用して Invoke を呼び出すことができます。動的型はパフォーマンスを約 5 倍向上させましたが、値の設定と取得は通常のフィールド アクセス (約 100 倍高速) に比べて依然として遅く、このライブラリは使用できません。

最終的には、ユーザー定義のクラスをバイト配列に変換できるようにしたいと考えています。また、シリアライゼーションにはネットワーク経由で送信したくないジャンクデータが含まれており、シリアライゼーションは非常に遅いように見えるため、クラスをシリアライズしたくありません。

ここに質問があります: GetVaue と SetValue メソッドを実際にもっと速くすることはできますか? デリゲートを必要な関数/アクションにキャストしようとしました (ライブラリ内のカスタム パケット クラスを移動したため、これは適切ではありません)。セッターは 300 ミリ秒ではなく約 50 ミリ秒かかりました。一般的なパケットでそのパフォーマンスを得たいと思っていました。

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

リフレクションのゲッターとセッターは実際には動的呼び出しであり、通常は 300 ミリ秒かかります。

また、コードがかなり長いので、ここにも投稿しました。

4

2 に答える 2

2

少し異なるアプローチを検討しましたか?この場合のゲッターとセッターの主な目的はシリアライゼーションとデシリアライゼーションであるため、シリアライゼーション メソッドを動的に生成することに専念する必要があります。これにより、動的メソッドを使用してこれらのフィールドに 1 つずつアクセスするオーバーヘッドがなくなります。

たとえば、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();
    }

Linq 式を使用することで、ILGenerator よりも簡単にすることができます。

    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()もちろん、 to serializeの結果を保存できますStudentPacket。同じアプローチを逆シリアル化にも使用できます。

于 2013-11-10T01:53:12.890 に答える
2

完全を期すために、ゲッターとセッターのアクセス時間をなんとか下げることができました(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 オブジェクトのセットと取得の場合) と述べています。

于 2013-11-12T15:18:00.327 に答える