小优化之UInt64双哈希

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

问题描述:

首先从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 on 2011-04-24 19:39  老哈  阅读(590)  评论(1编辑  收藏  举报

导航