zoukankan      html  css  js  c++  java
  • 用自定义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是修改后的新版本,请酌情下载)


  • 相关阅读:
    循环顺序队列
    iscsi与multipath
    MySQL 数据库设计 笔记与总结(4)维护优化
    [Swift]LeetCode6. Z字形变换 | ZigZag Conversion
    [Swift]八大排序算法(八):基数排序
    [Swift]八大排序算法(七):归并排序
    [Swift]八大排序算法(六):希尔排序
    [Swift]八大排序算法(五):插入排序
    [Swift]八大排序算法(四):堆排序
    [Swift]八大排序算法(三):选择排序 和 简单选择排序
  • 原文地址:https://www.cnblogs.com/teddyma/p/313737.html
Copyright © 2011-2022 走看看