摘要: 一直以为只有metadatatoken是用来查找元数据的,而token只能在module内部使用,那么当两个module同时被一个客户端引用,要求module1的代码要创建module2中的类型的对象,而又没有引用module2的时候发生了什么呢? 用Windbg尝试跟踪的结果。阅读全文
posted @ 2011-09-13 09:45 老哈 阅读(85) 评论(0) 编辑
摘要: 一直以为只有metadatatoken是用来查找元数据的,而token只能在module内部使用,那么当两个module同时被一个客户端引用,要求module1的代码要创建module2中的类型的对象,而又没有引用module2的时候发生了什么呢?阅读全文
posted @ 2011-09-13 09:44 老哈 阅读(69) 评论(0) 编辑

最近接到个任务,一直在思考最有效率的方法是怎么样的。

问题描述:

首先从App.Config或者Web.Config(第一个xml文件)中取得一个key-value对,value表明了另一个xml文件的路径,然后根据value把该xml(第二个xml文件)加载进来做缓存。在实际应用中,第二个xml文件大多也都是一组key-value对,暂且不需要考虑更复杂的情形(深度更深的树形结构或者某种自定义结构)。然后这个缓存可能只存在60s(60s刷新一次)。而且xml文件里的内容可能会非常频繁的被使用。

最后这个缓存的客户端目前需要的只是XElement类型。

问题规模的假设(高峰时段):

假设有50个用户同时使用这个产品,对应一个应用程序,每个用户每分钟需要读取缓存内容21次。同时显然这种频度的访问下,缓存每分钟都需要及时刷新。

也就是每分钟需要读取该缓存1050次,构造1次。

key-value对中key的长度假设为10个UTF-8字符的字符串,也就是占用10byte。

然后假设第一个xml文件共有3个key-value对,对应的每个第二个xml也有3个key-value对。每个key-value被访问的概率都相同。为了能被9整除把读取操作设为1080次。

解题思路:

这里考察2个思路,

第一个思路是比较直接的思路,每个文件对应存储为一个XElement,然后用第一个xml(config)中的key作为键,存入一个Dictionary<string, XElement>类型的缓存中去。

优点:容易理解,容易应对变化,构造速度快

缺点:整个儿缓存的结构成为一个深度为2的森林,对于许多频繁使用的值都需要一个深度为2的查找过程。第一个查找过程为hash,第二个则为顺序查找,因此当第二个xml文件的规模扩大的时候,消耗会大量增加。(尤其是如果第二个xml文件也用<add key="" value="">这种形式的话,单从ElementName无法判断,需要取Attribute的值,用linq,消耗更加大,但是本实验不采用这种形式),

然后第二个思路,是为了减少深度的,由于大部分查找都是深度为2的查找,因此可以考虑将深度为2的查找简化为1,具体做法是用第一个xml文件的key和第二个xml文件的key的两个hash值拼凑为一个大hash值,然后缓存从而减少访问深度。

最开始考虑把这两个key连接为一个字符串作为键,但是这样的话就要设定一个分割字符(串),会比较丑陋。最后决定用hash值的方法,由于hash值是一个UINT32,所以可以用一个UINT64结构完美的装下2个Hash值。而一个UINT64作为键,在运算上空间和时间复杂度都好于两个长度为10的字串的连接。

优点:减少了查找深度,读取速度上应该好于方法1。

缺点:两个不同字符串的hash值仍然有冲突的可能,构造速度差于方法1。

分析:

从略,这么简单的小问题,就没必要计算了。

代码:

方法1的构造操作

                foreach (string key in keys)
                {
                    string filePath = System.Configuration.ConfigurationManager.AppSettings[key];
                    cache.Add(key, XDocument.Load(filePath).Element("root"));
                }

方法1的读取操作

                for (int i = 0; i < 120; i++)
                    foreach (string key in keys)
                    {
                        string str1 = cache[key].Element("testtest01").Attribute("value1").Value;
                        string str2 = cache[key].Element("testtest02").Attribute("value1").Value;
                        string str3 = cache[key].Element("testtest03").Attribute("value1").Value;
                    }

方法2的构造操作

                foreach (string key in keys)
                {
                    string filePath = System.Configuration.ConfigurationManager.AppSettings[key];
                    cache.Add((UInt64)key.GetHashCode() << 32 | (UInt32)"testtest01".GetHashCode(),
                        XDocument.Load(filePath).Element("root").Element("testtest01"));
                    cache.Add((UInt64)key.GetHashCode() << 32 | (UInt32)"testtest02".GetHashCode(),
                        XDocument.Load(filePath).Element("root").Element("testtest02"));
                    cache.Add((UInt64)key.GetHashCode() << 32 | (UInt32)"testtest03".GetHashCode(),
                        XDocument.Load(filePath).Element("root").Element("testtest03"));
                }

方法2的读取操作:

                for (int i = 0; i < 120; i++)
                    foreach (string key in keys)
                    {
                        string str1 =
                            cache[(UInt64)key.GetHashCode() << 32 | (UInt32)"testtest01".GetHashCode()].Attribute("value1").Value;
                        string str2 =
                          cache[(UInt64)key.GetHashCode() << 32 | (UInt32)"testtest02".GetHashCode()].Attribute("value1").Value;
                        string str3 =
                            cache[(UInt64)key.GetHashCode() << 32 | (UInt32)"testtest03".GetHashCode()].Attribute("value1").Value;
                    }

实验结果:

(从上到下分别是方法1执行2回(每回10000次)所花时间,与方法2执行2回(每回10000次)所花时间)

然后把读取的循环改为1200次(10倍),再次测量需要的时间

结论:的确方法2可以简化读取时间,不过在这个规模下其构造过程反而成为瓶颈。

唉,C#中你可控的优化真是很少。

posted @ 2011-04-24 19:39 老哈 阅读(198) 评论(1) 编辑

在看公司代码的时候发现这么一个东西,就是遍历表达式树,然后把调用Where方法的(MethodCallExpression)表达式下面的条件记录在一个Collection里面。具体细节还没有看的太仔细,但是它大体上是这样干的:

用一个Stack<bool>,记录当前状态,在遍历开始Push(false),然后遍历表达式树的时候,如果该节点是调用Where方法的表达式,则Push(true),其他则Push(false),访问完毕一个表达式以后Pop()。在需要判断的时候用Peek()来判断。

我写了一个大概的模拟代码如下:(如果是直接在Where方法下面的表达式,则放入一个collection)

首先模拟的表达式是一个string数组:

            string[] expressionTree = new string[]
            {
                "A",
                "B",
                "C",
                "D",
                "WHERE",
                "Condition1",
                "Condition1A",
                "Condition1B",
                "Condition1C"
            };
然后是模拟的遍历部分:(index为模拟表达式树的数组的当前索引)
        static List<string> collection;
        static Stack<bool> stack;
        static int index;
        static string[] expressionTree;
 
        static void test1Main(string[] iexpressionTree)
        {
            index = 0;            
            collection = new List<string>();
            stack = new Stack<bool>();
            stack.Push(false);
            expressionTree = iexpressionTree;
 
            test1(expressionTree[index]);
        }
 
 
        static void test1(string expression)
        {
            if (stack.Peek())
                collection.Add(expression);
 
            switch (expression)
            {
                case "WHERE":
                    stack.Push(true);
                    //DOSomeThing
                    if (++index == expressionTree.Length)
                        break;
                    else
                        test1(expressionTree[++index]);
 
                    stack.Pop();
                    break;
                default:
                    stack.Push(false);
                    if (++index == expressionTree.Length)
                        break;
                    else
                        test1(expressionTree[++index]);
                    stack.Pop();
                    break;
            }            
        }
程序最后记录的条件应该是“Condition1”。

那么有没有比这个更加有效率的解决方案呢,暂时想到了2个。

第一个由于这个要处理的表达式中至多有一次Where方法的调用,我可以用一个int来做这个事情,最开始将int设置为1,如果当前遍历的是Where方法的调用节点,则将int设置为0,否则在对应Push的部分把这个int加1,再对应Pop的部分把这个int-1。如果当前该int的值为0则认为当前是在Where方法下面的条件。在只有一个Where的时候,这个方法可以正常运转。

但是这样做显然与原来堆栈的做法分歧较大,可能产生扩展上的不方便。于是又想到了另外一个模拟堆栈的方法,就是用一个uint(姑且称为stackuint),初始设置为0,然后设置一个uint的flag为0x00000001 ,如果当前是Where,则通过stackuint与flag取或把最低位设置为1

stackbyte |= flag

然后对应以前Push(false)的部分把该uint左移一位,对应Pop的部分把该uint右移1位。

在判断的时候用

(stackuint&flag) != 0
如果不为0,则认为当前语境是在Where条件下。这个方法与Stack的方法基本上是等效的,只是要求遍历
表达式的深度不能超过UInt的位数。
而这个条件应该是可以满足的(刨除某些变态的情况)。
最后附上三种方法执行1000万次所花费时间的测试结果:
posted @ 2011-04-07 12:03 老哈 阅读(156) 评论(0) 编辑