用自定义KeyValueCollection类代替Dictionary/Hastable,改善简化后的Entity性能

在上一篇《来一点反射,再来一点Emit —— 极度简化Entity!》中,Teddy运用反射和Emit极度简化了Entity的定义方式。本文将在上文的基础上,用自定义KeyValueCollection类代替原来的Dictionary类承载Entity的数据,从而改善Entity的读写性能,并保持Dictionary的方便的使用接口。

为什么Dictionary的性能不好?

当然,这里说的性能不好主要指相对于Private Field-Public Property方式的性能而言。我们知道,.Net2.0中的Dictionary类,实际上是一个泛型的HashTable实现。因此,性能不佳的原因主要就是HashTable算法。FantasySoft在他的文章《解读Hashtable》中为我们回顾了HashTable算法的原理。纵观整个算法,主要有以下几个地方的性能损失:一个是hashcode的计算,即计算key的hashcode的代码;第二个是hashcode重复的问题,此时,需要进行一个附加的链表查询。并且,Dictionary或HashTable都是通用类型,他们的太多代码,在这里完全没有用到,而白白浪费了实例化他们的内存开销。

为什么不直接使用Private Field-Public Property方式生成Entity?

那么,既然Private Field-Public Property方式的性能绝对是最好的,为什么Teddy又要定义自定义的KeyValueCollection呢?直接的原因是,对于我的已有代码,尤其是emit生成代码来讲,将Dictionary类承载数据改成Private Field-Public Property方式,代码的改动幅度过大,为了避免运行时反射的性能损失,IEntity接口中的方法可能都将不再适合定义在基类Enitity<>中,而必须直接动态emit,也因此,虽然明知Private Field-Public Property的性能最好,但是,我并不愿意冒然向他缴械。也因此,如本文标题,Teddy开始尝试使用自定义的KeyValueCollection来代替Dictionary,希望能够获得和Private Field-Public Property相似的性能,但是又有Dictionary的那样的简单使用接口。

自定义KeyValueCollection!

考虑到我的KeyValueCollection只是用于持久化内部的数据承载,我不需要太大的通用性,只需要必要的接口就好,我将KeyValueCollection类定义如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

namespace Ilungasoft.Helper.Data
{
    
public class KeyValueCollection
    
{
        
private string[] keys;
        
private object[] values;

        
private KeyValueCollection()
        
{
        }


        
public KeyValueCollection(params PropertyInfo[] pis)
        
{
            
if (pis != null)
            
{
                keys 
= new string[pis.Length];
                values 
= new object[pis.Length];
                
for (int i = 0; i < pis.Length; i++)
                
{
                    keys[i] 
= pis[i].Name;
                    values[i] 
= typeof(Util).GetMethod("DefaultValue", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(pis[i].PropertyType).Invoke(nullnull);
                }

            }

            
else
            
{
                keys 
= new string[0];
                values 
= new object[0];
            }

        }


        
public string[] GetKeys(params string[] exceptKeys)
        
{
            
if (exceptKeys != null && exceptKeys.Length > 0)
            
{
                
int retKeyCount = keys.Length - exceptKeys.Length;
                
if (retKeyCount <= 0)
                
{
                    
return new string[0];
                }

                List
<string> retKeys = new List<string>(retKeyCount);
                
foreach (string key in keys)
                
{
                    
bool isExcept = false;
                    
foreach (string exceptMember in exceptKeys)
                    
{
                        
if (key.Equals(exceptMember))
                        
{
                            isExcept 
= true;
                            
break;
                        }

                    }

                    
if (!isExcept)
                    
{
                        retKeys.Add(key);
                    }

                }

                
return retKeys.ToArray();
            }

            
else
            
{
                
return keys;
            }

        }


        
public object[] GetValues(params string[] exceptKeys)
        
{
            
if (exceptKeys != null && exceptKeys.Length > 0)
            
{
                
int retValueCount = keys.Length - exceptKeys.Length;
                
if (retValueCount <= 0)
                
{
                    
return new object[0];
                }

                List
<object> retValues = new List<object>(retValueCount);
                
for (int i = 0; i < keys.Length; i++)
                
{
                    
bool isExcept = false;
                    
foreach (string exceptMember in exceptKeys)
                    
{
                        
if (keys[i].Equals(exceptMember))
                        
{
                            isExcept 
= true;
                            
break;
                        }

                    }

                    
if (!isExcept)
                    
{
                        retValues.Add(values[i]);
                    }

                }

                
return retValues.ToArray();
            }

            
else
            
{
                
return values;
            }

        }


        
public KeyValueCollection Clone()
        
{
            KeyValueCollection retKeyValues 
= new KeyValueCollection();
            
string [] cloneKeys = new string[keys.Length];
            keys.CopyTo(cloneKeys, 
0);
            retKeyValues.keys 
= cloneKeys;
            
object[] cloneValues = new object[values.Length];
            values.CopyTo(cloneValues, 
0);
            retKeyValues.values 
= cloneValues;
            
return retKeyValues;
        }


        
public object this[int index]
        
{
            
get
            
{
                
return values[index];
            }

            
set
            
{
                values[index] 
= value;
            }

        }

    }

}

接着,还要将原来对Dictionary的调用全都改成对新的类的调用,基类Entity<>的改动不太大,我就不列举了,运行时生成的Entity改动就比较大了。使用新的KeyValueCollection类后,实际生成的Entity的代码的范例列举如下(IAbout为需要用户定义的接口,static EntityFactory()为修改后的生成运行时About类的emit代码,最后的About为,实际上emit出来的类的等价hard code代码):

using System;

namespace Ilungasoft.Helper.TestApp.DomainObject2
{
    
public interface About: Ilungasoft.Helper.Data.IEntity
    
{
        
int ID getset; }

        
string Title getset; }

        
string Content getset; }

        
bool Deletable getset; }

        
int Order getset; }
    }

}

        static EntityFactory()
        
{
            
//create dynamic IEntity Assembly & Type through Emit
            if (assBuilder == null)
            
{
                AssemblyName assName 
= new AssemblyName();
                assName.Name 
= DYNAMIC_ENTITY_NAMESPACE;
                assBuilder 
= AppDomain.CurrentDomain.DefineDynamicAssembly(assName, AssemblyBuilderAccess.Run);
            }


            
if (modBuilder == null)
            
{
                modBuilder 
= assBuilder.DefineDynamicModule(DYNAMIC_ENTITY_NAMESPACE);
            }


            
if (typeBuilder == null)
            
{
                typeBuilder 
= modBuilder.DefineType(DYNAMIC_ENTITY_NAMESPACE + "." + typeof(IEntityType).FullName, TypeAttributes.Public);
                typeBuilder.AddInterfaceImplementation(
typeof(IEntityType));
                Type keyValuesType 
= typeof(KeyValueCollection);
                Type baseType 
= typeof(Entity<IEntityType>);
                typeBuilder.SetParent(baseType);
                PropertyInfo[] pis 
= typeof(IEntityType).GetProperties();

                
//define SetPropertyValue(string key, object val)
                MethodInfo mi = typeof(IEntity).GetMethod("SetPropertyValue");
                ParameterInfo[] paramInfos 
= mi.GetParameters();
                
int paramlength = paramInfos.Length;
                Type[] paramTypes 
= new Type[paramlength];
                
for (int i = 0; i < paramlength; i++)
                
{
                    paramTypes[i] 
= paramInfos[i].ParameterType;
                }

                MethodBuilder setPropValMethodBuilder 
= typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
                
for (int i = 0; i < paramlength; i++)
                
{
                    ParameterInfo pi 
= paramInfos[i];
                    setPropValMethodBuilder.DefineParameter(i 
+ 1, pi.Attributes, pi.Name);
                }

                typeBuilder.DefineMethodOverride(setPropValMethodBuilder, mi);
                ILGenerator setPropValMethodIL 
= setPropValMethodBuilder.GetILGenerator();

                
int canWritePropertyCount = 0;
                
int firstCanWritePropertyIndex = -1;
                
int finalCanWritePropertyIndex = -1;
                
for (int k = 0; k < pis.Length; k++)
                
{
                    
if (pis[k].CanWrite)
                    
{
                        
if (firstCanWritePropertyIndex == -1)
                        
{
                            firstCanWritePropertyIndex 
= k;
                        }

                        finalCanWritePropertyIndex 
= k;
                        canWritePropertyCount
++;
                    }

                }


                
if (canWritePropertyCount == 0)
                
{
                    setPropValMethodIL.Emit(OpCodes.Ret);
                }

                
else if (canWritePropertyCount == 1)
                
{
                    setPropValMethodIL.Emit(OpCodes.Ldarg_0);
                    setPropValMethodIL.Emit(OpCodes.Ldfld, baseType.GetField(
"keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
                    EmitLoadInt32Value(setPropValMethodIL, firstCanWritePropertyIndex);
                    setPropValMethodIL.Emit(OpCodes.Ldarg_2);
                    setPropValMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod(
"set_Item"));
                    setPropValMethodIL.Emit(OpCodes.Ret);
                }

                
else
                
{
                    
int nextRetCodeIndex = 0;
                    
for (int k = 0; k < pis.Length; k++)
                    
{
                        
if (pis[k].CanWrite)
                        
{
                            
//work out nextRetCodeIndex
                            if (k == finalCanWritePropertyIndex)
                            
{
                                nextRetCodeIndex 
+= 0x1a;
                            }

                            
else
                            
{
                                nextRetCodeIndex 
+= 0x1b;
                            }

                            
                            setPropValMethodIL.Emit(OpCodes.Ldarg_1);
                            setPropValMethodIL.Emit(OpCodes.Ldstr, pis[k].Name);
                            setPropValMethodIL.Emit(OpCodes.Callvirt, 
typeof(string).GetMethod("Equals"new Type[] typeof(string) }));
                            setPropValMethodIL.Emit(OpCodes.Brfalse_S, nextRetCodeIndex);
                            setPropValMethodIL.Emit(OpCodes.Ldarg_0);
                            setPropValMethodIL.Emit(OpCodes.Ldfld, baseType.GetField(
"keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
                            EmitLoadInt32Value(setPropValMethodIL, k);
                            setPropValMethodIL.Emit(OpCodes.Ldarg_2);
                            setPropValMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod(
"set_Item"));
                            setPropValMethodIL.Emit(OpCodes.Ret);
                        }

                    }

                }


                
//define GetMemberNames(params string[] exceptMembers)
                mi = typeof(IEntity).GetMethod("GetMemberNames");
                paramInfos 
= mi.GetParameters();
                paramlength 
= paramInfos.Length;
                paramTypes 
= new Type[paramlength];
                
for (int i = 0; i < paramlength; i++)
                
{
                    paramTypes[i] 
= paramInfos[i].ParameterType;
                }

                MethodBuilder getMemberNamesMethodBuilder 
= typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
                
for (int i = 0; i < paramlength; i++)
                
{
                    ParameterInfo pi 
= paramInfos[i];
                    getMemberNamesMethodBuilder.DefineParameter(i 
+ 1, pi.Attributes, pi.Name);
                }

                typeBuilder.DefineMethodOverride(getMemberNamesMethodBuilder, mi);
                ILGenerator getMemberNamesMethodIL 
= getMemberNamesMethodBuilder.GetILGenerator();
                getMemberNamesMethodIL.Emit(OpCodes.Ldarg_1);
                getMemberNamesMethodIL.Emit(OpCodes.Call, baseType.GetMethod(
"GetMemberNames", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public));
                getMemberNamesMethodIL.Emit(OpCodes.Ret);

                
//define GetMemberValues(params string[] exceptMembers)
                mi = typeof(IEntity).GetMethod("GetMemberValues");
                paramInfos 
= mi.GetParameters();
                paramlength 
= paramInfos.Length;
                paramTypes 
= new Type[paramlength];
                
for (int i = 0; i < paramlength; i++)
                
{
                    paramTypes[i] 
= paramInfos[i].ParameterType;
                }

                MethodBuilder getMemberValuesMethodBuilder 
= typeBuilder.DefineMethod(mi.Name, mi.Attributes & (~MethodAttributes.Abstract) | MethodAttributes.Public, mi.CallingConvention, mi.ReturnType, paramTypes);
                
for (int i = 0; i < paramlength; i++)
                
{
                    ParameterInfo pi 
= paramInfos[i];
                    getMemberValuesMethodBuilder.DefineParameter(i 
+ 1, pi.Attributes, pi.Name);
                }

                typeBuilder.DefineMethodOverride(getMemberValuesMethodBuilder, mi);
                ILGenerator getMemberValuesMethodIL 
= getMemberValuesMethodBuilder.GetILGenerator();
                getMemberValuesMethodIL.Emit(OpCodes.Ldarg_0);
                getMemberValuesMethodIL.Emit(OpCodes.Ldarg_1);
                getMemberValuesMethodIL.Emit(OpCodes.Call, baseType.GetMethod(
"GetMemberValues"));
                getMemberValuesMethodIL.Emit(OpCodes.Ret);

                
//define default constructor
                ConstructorBuilder consBuilder = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
                ILGenerator ctorIL 
= consBuilder.GetILGenerator();
                ctorIL.Emit(OpCodes.Ldarg_0);
                ctorIL.Emit(OpCodes.Call, baseType.GetConstructor(
new Type[0]));
                ctorIL.Emit(OpCodes.Ret);

                
//define properties
                foreach (PropertyInfo pi in pis)
                
{
                    PropertyBuilder propBuilder 
= typeBuilder.DefineProperty(pi.Name, System.Reflection.PropertyAttributes.HasDefault, pi.PropertyType, null);
                    MethodAttributes getSetAttr 
= MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

                    
if (pi.CanRead)
                    
{
                        
//define getMethod
                        MethodBuilder getPropMethodBuilder = typeBuilder.DefineMethod("get_" + pi.Name, pi.GetGetMethod().Attributes & (~MethodAttributes.Abstract) | getSetAttr, pi.PropertyType, Type.EmptyTypes);
                        typeBuilder.DefineMethodOverride(getPropMethodBuilder, pi.GetGetMethod());
                        ILGenerator getPropMethodIL 
= getPropMethodBuilder.GetILGenerator();
                        getPropMethodIL.Emit(OpCodes.Ldarg_0);
                        getPropMethodIL.Emit(OpCodes.Ldfld, baseType.GetField(
"keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
                        getPropMethodIL.Emit(OpCodes.Ldstr, pi.Name);
                        getPropMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod(
"get_Item"));
                        
if (pi.PropertyType.IsValueType)
                        
{
                            getPropMethodIL.Emit(OpCodes.Unbox_Any, pi.PropertyType);
                        }

                        
else
                        
{
                            getPropMethodIL.Emit(OpCodes.Castclass, pi.PropertyType);
                        }

                        getPropMethodIL.Emit(OpCodes.Ret);
                        propBuilder.SetGetMethod(getPropMethodBuilder);
                    }


                    
if (pi.CanWrite)
                    
{
                        
//define setMethod
                        MethodBuilder setPropMethodBuilder = typeBuilder.DefineMethod("set_" + pi.PropertyType, pi.GetSetMethod().Attributes & (~MethodAttributes.Abstract) | getSetAttr, nullnew Type[] { pi.PropertyType });
                        typeBuilder.DefineMethodOverride(setPropMethodBuilder, pi.GetSetMethod());
                        ILGenerator setPropMethodIL 
= setPropMethodBuilder.GetILGenerator();
                        setPropMethodIL.Emit(OpCodes.Ldarg_0);
                        setPropMethodIL.Emit(OpCodes.Ldfld, baseType.GetField(
"keyValues", BindingFlags.Instance | BindingFlags.NonPublic));
                        setPropMethodIL.Emit(OpCodes.Ldstr, pi.Name);
                        setPropMethodIL.Emit(OpCodes.Ldarg_1);
                        
if (pi.PropertyType.IsValueType)
                        
{
                            setPropMethodIL.Emit(OpCodes.Box, pi.PropertyType);
                        }

                        setPropMethodIL.Emit(OpCodes.Callvirt, keyValuesType.GetMethod(
"set_Item"));
                        setPropMethodIL.Emit(OpCodes.Ret);
                        propBuilder.SetSetMethod(setPropMethodBuilder);
                    }

                }

            }

        }

using System;
using Ilungasoft.Helper.Data;

namespace Ilungasoft.Helper.TestApp.DomainObject
{
    
public class About : Entity<About>, IEntity
    
{
        
public About() : base()
        
{
        }


        
public int ID
        
{
            
get return (int)keyValues[0]; }
            
set { keyValues[0= value; }
        }


        
public string Title
        
{
            
get return (string)keyValues[1]; }
            
set { keyValues[1= value; }
        }


        
public string Content
        
{
            
get return (string)keyValues[2]; }
            
set { keyValues[2= value; }
        }


        
public bool Deletable
        
{
            
get return (bool)keyValues[3]; }
            
set { keyValues[3= value; }
        }


        
public int Order
        
{
            
get return (int)keyValues[4]; }
            
set { keyValues[4= value; }
        }


        
public void SetPropertyValue(string key, object val)
        
{
                
if (key.Equals("ID"))
                
{
                    keyValues[
0= val;
                }

                
else if (key.Equals("Title"))
                
{
                    keyValues[
1= val;
                }

                
else if (key.Equals("Content"))
                
{
                    keyValues[
2= val;
                }

                
else if (key.Equals("Deletable"))
                
{
                    keyValues[
3= val;
                }

                
else if (key.Equals("Order"))
                
{
                    keyValues[
4= val;
                }

            }


        
public new string[] GetMemberNames(params string[] exceptMembers)
        
{
            
return Entity<About>.GetMemberNames(exceptMembers);
        }


        
public new object[] GetMemberValues(params string[] exceptMembers)
        
{
            
return base.GetMemberValues(exceptMembers);
        }

    }

}

注意看最后一个About的Hard Code伪代码,SetPropertyValue是用于EntityFactory创建Entity实例和绑定Entity到DataSet/IDataReader的。这个函数的性能是O(n/2)。考虑到Entity的Property数量不会多至一百几千以上,这里的O(n/2)实际上基本等于O(1)。而set函数和get函数想比较与以前的Dictionary方式,用int索引代替了string型的key,从而避免了hashcode计算和字符串比较,再加上这里直接用一个数组代替,hashtable的存储结构,也就完全避免了hashtable可能的键值重复时的链表查询。

综上所述

采用自定义KeyValueCollection类代替Dictionary/HashTable来承载Entity的数据,获得了类似Dictionary的强类型key类型和简单的使用接口、使得原来的组件代码改动最少,并且,非常理想的获得了极其近似Private Field-Public Property方式的Entity性能。真可谓,一举数得!

示例下载(本示例与上一篇的示例代码区别仅在于用自定义KeyValueCollection代替了Dictionary类,Sample程序是一模一样的,但是引用的编译后的ILungasoft.Helper.Data.dll是修改后的新版本,请酌情下载)


posted @ 2006-01-09 12:23  Teddy's Knowledge Base  Views(5693)  Comments(16Edit  收藏  举报