[英]Injecting generic getters and setters to get better performance than reflection
我試圖編寫一個高級網絡庫(主要是為了好玩),用戶可以通過派生類輕松定義他們的數據包。 這樣解析消息很簡單。 用戶數據包應僅包含基本值類型 。
為了做到這一點,我需要訪問每個用戶定義的數據包的每個字段。 這個問題可以通過反射很容易地完成,但由於反射很慢,我無法使用它。 為了快速做到這一點,我創建了一個類,在運行時為每個用戶定義的數據包字段注入getter和setter(在StackOverflow上找到它)。 getter是Func<UserDefinedPacket, fieldType>
,setter是Action<UserDefinedPAcket, setValue>
。
這是問題所在:因為用戶數據包是在另一個程序集中定義的,所以在編譯時我不可能知道getter或setter的類型。 我可以用於getter和setter的派生類最多的是Delegate
。 這意味着我只能使用DynamicInvoke,所以我再次進行反射...但是因為我知道這些方法是實際的Funcs和Actions但實際上無法投射它們我可以使用動態類型並調用Invoke。 動態類型將性能提高了大約5倍,但與正常的字段訪問(大約快100倍)相比,設置和獲取值仍然很慢,使得此庫無法使用。
最終,我希望能夠將用戶定義的類轉換為字節數組。 此外,我不想序列化類,因為序列化包括一些我真的不想通過網絡發送的垃圾數據,序列化似乎很慢。
這是一個問題 :我可以更快地實現GetVaue和SetValue方法嗎? 我嘗試將代理轉換為所需的函數/操作(移動庫中的自定義數據包類,因此這不好),並且setter大約需要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
反射吸氣劑和設定器實際上是動態呼叫,它們通常最終需要300毫秒。
此外,由於代碼很長,我也在這里發布。
您是否考慮過略有不同的方法? 在這種情況下,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();
}
使用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()
的結果來序列化StudentPacket
。 同樣的方法也可用於反序列化。
為了完整起見,我設法降低了getter和setter的訪問時間( kkokosa
確實更好地解決了我的問題,但問題是我可以為getter和setter獲得更好的性能)。
我做了這樣的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注入它們。 現在我知道運行時委托的類型,我只能通過裝箱和拆箱開銷來逃避。 這個說法讓我讓吸氣劑和制定者以大約50-70ms的速度運行(對於5場* 100.000個對象設置並獲得)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.