谈表达式树的缓存(5):引入散列值

到目前为止,我们已经实现了三种缓存方式:首先我们设法构建唯一字符串,但是由于它的代价较高 ,于是我们使用了前缀树进行存储;又由于前缀树在实际操作中所花的时间和空间都有不令人满意之处, 我们又引入了二叉搜索树。那么二叉搜索树又有什么缺点呢?其实前文已经谈到过了,那就是从理论上来 说,它的时间复杂度相对前两个要高,在最坏情况下将会出现O(m * log(n))的时间复杂度——每次比较 两个前缀树需要耗费O(m),共比较O(log(n))次。

很显然,与最理想的时间复杂度O(m)相比,其差距就在于n,也就是缓存空间中已有的元素数量。如果 元素越多,则n越大,log(n)也会随之增大,则耗费O(m)的“次数”也就越多。换句话说,如果我们要改 进性能,就要想办法减少比较次数。一个比较容易想到的做法便是对缓存空间中的n个元素进行“分组” ,在每次查询时首先使用很小的时间复杂度,确定被查询的表达式树处于哪个组中,然后只需要与这个组 中为数不多的几个元素进行比较便可。这样耗费O(m)的操作次数就少了,性能随之提高。

既然要进行分组,那么我们其实就是要从每个表达式树中提取“特征值”,再根据这个“特征值”进 行分组——不过这是有条件的,例如:

特征值计算要尽可能的快,否则光计算一次特征值就消耗大量时间,得不偿失。

根据特征值要能够快速确定分组,原因如第1点。

特征值要可以将元素尽可能地分散在不同组中,这样每个组里的元素会变得更少,更节省比较次数。

想到这里,您应该已经得出结论了……这不就是散列值吗?在.NET Framework中,一个对象的散列值 为一个32位整型数值,然后便可以从一个以Int32类型为键的字典中快速地获取一个“分组”,这就已经 满足了上述第2点要求。因此,问题的关键在于如何“快速”地求出一个表达式树的散列值,并且要使这 个散列值能够尽可能地分布均匀。一个表达式树的散列值,显然是由它内部的元素组成,因此我们只需要 遍历它的每个元素,将这些元素散列值结合为一个单一的散列值即可——这是一个O(m)的操作,非常高效 。

为此,老赵实现了一个ExpressionHasher类,用于计算一个表达式树的散列值,如下:

public class ExpressionHasher : ExpressionVisitor
{
   public int Hash(Expression exp)
   { 
     this.HashCode = 0;
     this.Visit(exp);
     return this.HashCode;
   }

   public int HashCode { get; protected set; }

   protected virtual ExpressionHasher Hash(int value)
   {
     unchecked { this.HashCode += value; }
     return this;
   }

   protected virtual ExpressionHasher Hash(bool value)
   {
     unchecked { this.HashCode += value ? 1 : 0; }
     return this;
   }

   private static readonly object s_nullValue = new object();

   protected virtual ExpressionHasher Hash(object value)
   {
     value = value ?? s_nullValue;
     unchecked { this.HashCode += value.GetHashCode(); }
     return this;
   }

   ...
}

时间: 2024-10-27 00:30:50

谈表达式树的缓存(5):引入散列值的相关文章

谈表达式树的缓存(1):引言

表达式树(Expression Tree)是.NET 3.5中引入的一种表达方式.表达式树的运用十分广泛,可以直 观地表现出各种"数据",甚至"逻辑"和"行为".再者,表达式树是强类型的,因此合理地使用这个 新特性可以让代码编写变得优雅,方便.一个最简单而常见的例子便是,某些朋友目前就已经喜欢使用表 达式树来代替传统的ByXxx方法,尤其是在访问一些直接支持表达式树的数据源时(例如IEnumerable或 LINQ to SQL).如下: pub

谈表达式树的缓存(6):五种缓存方式的性能比较

开始还债,因为还有至少两个可写的重要话题依赖在这个系列上,不解决就难以前进. 目前我们已经涉及了五种不同的缓存实现,它们分别是: SimpleKeyCache:构造字符串作为Key,使用字典作为存储. PrefixTreeCache:使用前缀树进行存储. SortedListCache:使用排序列表或二叉搜索树进行存储. HashedListCache:先对表达式树取散列值,再从字典中取出二叉搜索树. DictionaryCache:实现了散列值和表达式树的比较方法,直接使用字典进行存储. 如果

谈表达式树的缓存(3):使用前缀树

在上一篇文章里我们设法将前缀树构造为一个唯一的字符串,然后使用字符串作为key缓存在字典中. 这个想法非常直接,做法也不困难(在遍历时记录详细信息便可).不过事实上,老赵在思考表达式树的 缓存问题时,这种字符串拼接的方式只存在于脑海当中,而上文的实现是为了这一系列文章的完整性而特 地编写的.这是因为它的缺点较为明显,正如上文所述,字符串拼接操作较为耗时耗资源,且很容易生成 一个长度可观的字符串(并非不能优化,不过实现就复杂了).于是我们现在设法选择另一个解决方案来 处理这个问题. 不过现在,我们

谈表达式树的缓存(4):使用二叉搜索树(AVL树)

上一篇文章中谈到的前缀树实现方式,时间复杂度从理论上来讲已经达到了最优,而空间复杂度理论 上也可以做到较优.但是理论和实际是有差别的,而对于上文前缀树的实现来说,这两方面并不是非常理 想: 时间:前缀树时间复杂度为O(m)的前提是每次哈希表查找操作的时间复杂度为O(1),不过这个O(1)与 一次数值比较相比,从性能上来说还是有比较明显的差距. 空间:前缀树空间复杂度较优的前提是"精细"地实现该数据结构,如果像上文般粗枝大叶,那么会 形成大量稀疏的哈希表,反而造成空间浪费. 因此,虽然事

谈表达式树的缓存(2):由表达式树生成字符串

谈到使用表达式树作为key进行缓存,您脑海中最早浮现出来的解决方案是什么?老赵看来,大部分朋 友的第一反应自然就是将作为key的表达式树,使用一定规则生成一个字符串.简而言之,这个生成字符 串的规则F需要能够保证: 在同一个缓存空间内,同样的表达式树能够生成相同的字符串. 在同一个缓存空间内,不同的表达式树生成不同的字符串. 似乎有些罗嗦,朋友们明白便是.其中"在同一个缓存空间"的前提,其实只是放宽了后续要求的条 件.因为在不同的缓存空间内,即使不同的表达式树生成了同样的字符串,它们也

谈表达式树的缓存(7):五种缓存方式的总体分析及改进方案

终于到了这个系列的最后一篇文章了,这个系列的文章本是许多话题的基础,却拖了那么长时间还没 有完结.这篇文章主要讨论五种缓存方式各自的优劣,以及他们的性能关键在什么地方,如果要进行改进 又有什么可选方案.在这个问题上,老赵的思考可能会有遗漏,如果您有任何补充,也请不吝指出. SimpleKeyCache SimpleKeyCache是最直接的缓存方案:将表达式树进行完整编码,将其转化为一个独一无二的字符串 ,然后作为Key存放到字典中. 从上一篇文章的实验来看,无论从耗时还是对GC造成的压力上来说

《解读NoSQL》——2.4 使用一致性散列算法维护当前的缓存

2.4 使用一致性散列算法维护当前的缓存 我们已经知道了将经常使用的数据保存在RAM缓存中的重要性,以及如何通过减少非必要的磁盘访问来提升数据库性能.NoSQL系统将基于这个概念进行深入探讨,并采用了一致性散列(consistent hashing)的算法使访问最频繁的数据保存在缓存中. 在评估NoSQL系统如何工作时,一致性散列算法是一种有效的通用流程.一致性散列算法能很快判断出一个新的查询或者文档是否和缓存中的某一个对象是相同的.了解这些将有助于减少不必要的磁盘访问并且使数据库保持高速运行的

Android数据加密之SHA安全散列算法_Android

前言: 对于SHA安全散列算法,以前没怎么使用过,仅仅是停留在听说过的阶段,今天在看图片缓存框架Glide源码时发现其缓存的Key采用的不是MD5加密算法,而是SHA-256加密算法,这才勾起了我的好奇心,所以趁着晚上没啥事,来学习一下. 其他几种加密方式:  •Android数据加密之Rsa加密  •Android数据加密之Aes加密  •Android数据加密之Des加密  •Android数据加密之MD5加密  •Android数据加密之Base64编码算法 SHA加密算法      SH

Android数据加密之SHA安全散列算法

前言: 对于SHA安全散列算法,以前没怎么使用过,仅仅是停留在听说过的阶段,今天在看图片缓存框架Glide源码时发现其缓存的Key采用的不是MD5加密算法,而是SHA-256加密算法,这才勾起了我的好奇心,所以趁着晚上没啥事,来学习一下. 其他几种加密方式: •Android数据加密之Rsa加密  •Android数据加密之Aes加密  •Android数据加密之Des加密  •Android数据加密之MD5加密  •Android数据加密之Base64编码算法 SHA加密算法 SHA(Secu