<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      代碼改變世界

      談表達式樹的緩存(5):引入散列值

      2009-03-20 01:40  Jeffrey Zhao  閱讀(13689)  評論(16)    收藏  舉報

      到目前為止,我們已經(jīng)實現(xiàn)了三種緩存方式:首先我們設(shè)法構(gòu)建唯一字符串,但是由于它的代價較高,于是我們使用了前綴樹進行存儲;又由于前綴樹在實際操作中所花的時間和空間都有不令人滿意之處,我們又引入了二叉搜索樹。那么二叉搜索樹又有什么缺點呢?其實前文已經(jīng)談到過了,那就是從理論上來說,它的時間復(fù)雜度相對前兩個要高,在最壞情況下將會出現(xiàn)O(m * log(n))的時間復(fù)雜度——每次比較兩個前綴樹需要耗費O(m),共比較O(log(n))次。

      很顯然,與最理想的時間復(fù)雜度O(m)相比,其差距就在于n,也就是緩存空間中已有的元素數(shù)量。如果元素越多,則n越大,log(n)也會隨之增大,則耗費O(m)的“次數(shù)”也就越多。換句話說,如果我們要改進性能,就要想辦法減少比較次數(shù)。一個比較容易想到的做法便是對緩存空間中的n個元素進行“分組”,在每次查詢時首先使用很小的時間復(fù)雜度,確定被查詢的表達式樹處于哪個組中,然后只需要與這個組中為數(shù)不多的幾個元素進行比較便可。這樣耗費O(m)的操作次數(shù)就少了,性能隨之提高。

      既然要進行分組,那么我們其實就是要從每個表達式樹中提取“特征值”,再根據(jù)這個“特征值”進行分組——不過這是有條件的,例如:

      1. 特征值計算要盡可能的快,否則光計算一次特征值就消耗大量時間,得不償失。
      2. 根據(jù)特征值要能夠快速確定分組,原因如第1點。
      3. 特征值要可以將元素盡可能地分散在不同組中,這樣每個組里的元素會變得更少,更節(jié)省比較次數(shù)。

      想到這里,您應(yīng)該已經(jīng)得出結(jié)論了……這不就是散列值嗎?在.NET Framework中,一個對象的散列值為一個32位整型數(shù)值,然后便可以從一個以Int32類型為鍵的字典中快速地獲取一個“分組”,這就已經(jīng)滿足了上述第2點要求。因此,問題的關(guān)鍵在于如何“快速”地求出一個表達式樹的散列值,并且要使這個散列值能夠盡可能地分布均勻。一個表達式樹的散列值,顯然是由它內(nèi)部的元素組成,因此我們只需要遍歷它的每個元素,將這些元素散列值結(jié)合為一個單一的散列值即可——這是一個O(m)的操作,非常高效。

      為此,老趙實現(xiàn)了一個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;
          }
      
          ...
      }
      

      ExpressionHasher有個Hash方法,可用于計算一個表達式數(shù)的散列值。與之前的幾種ExpressionVisitor實現(xiàn)類似,ExpressionHasher也準備一些輔助方法供其他方法調(diào)用。這些輔助方法接受不同類型的參數(shù),完全避免了數(shù)據(jù)的裝箱/拆箱,盡可能保持算法的高效。從上面的代碼看出,我們不斷地向這些輔助方法內(nèi)傳入對象時,它們會被累加到HashCode屬性中——這就是老趙在這里使用的“組合方式”,將表達式樹中每個元素的散列值進行組合,最終成為整個表達式數(shù)的散列值。老趙無法證明這是一種優(yōu)秀的散列組合算法,但是從測試上來看,這么做的效果還是不錯的(事實上,老趙隨機生成了大量表達式還沒有出現(xiàn)碰撞)。更關(guān)鍵的一點是,這么做非常高效,如果將這些元素拼接起來,并得到最終字符串的散列值可能會有更好的結(jié)果,但是其性能就比整數(shù)的相加要差許多了。

      現(xiàn)在,我們只需要在Visit每個節(jié)點的時候,把節(jié)點的屬性作為表達式樹的每個元素傳入對應(yīng)的輔助方法便可,以下為部分代碼:

      protected override Expression Visit(Expression exp)
      {
          if (exp == null) return exp;
      
          this.Hash((int)exp.NodeType).Hash(exp.Type);
          return base.Visit(exp);
      }
      
      protected override Expression VisitBinary(BinaryExpression b)
      {
          this.Hash(b.IsLifted).Hash(b.IsLiftedToNull).Hash(b.Method);
          return base.VisitBinary(b);
      }
      
      protected override Expression VisitConstant(ConstantExpression c)
      {
          this.Hash(c.Value);
          return base.VisitConstant(c);
      }
      
      protected override Expression VisitMemberAccess(MemberExpression m)
      {
          this.Hash(m.Member);
          return base.VisitMemberAccess(m);
      }
      
      protected override Expression VisitMethodCall(MethodCallExpression m)
      {
          this.Hash(m.Method);
          return base.VisitMethodCall(m);
      }
      
      ...
      

      按照我們剛才的設(shè)想,首先計算出一個表達式樹的散列值,然后從字典中獲取具體的一個分組,再從這個分組中進行查找。使用這個方法則得到了HashedListCache:

      public class HashedListCache<T> : IExpressionCache<T> where T : class
      {
          private ReaderWriterLockSlim m_rwLock = new ReaderWriterLockSlim();
          private Dictionary<int, SortedList<Expression, T>> m_storage =
              new Dictionary<int, SortedList<Expression, T>>();
      
          public T Get(Expression key, Func<Expression, T> creator)
          {
              SortedList<Expression, T> sortedList;
              T value;
      
              int hash = new ExpressionHasher().Hash(key);
              this.m_rwLock.EnterReadLock();
              try
              {
                  if (this.m_storage.TryGetValue(hash, out sortedList) &&
                      sortedList.TryGetValue(key, out value))
                  {
                      return value;
                  }
              }
              finally
              {
                  this.m_rwLock.ExitReadLock();
              }
      
              this.m_rwLock.EnterWriteLock();
              try
              {
                  if (!this.m_storage.TryGetValue(hash, out sortedList))
                  {
                      sortedList = new SortedList<Expression, T>(new ExpressionComparer());
                      this.m_storage.Add(hash, sortedList);
                  }
      
                  if (!sortedList.TryGetValue(key, out value))
                  {
                      value = creator(key);
                      sortedList.Add(key, value);
                  }
                  
                  return value;
              }
              finally
              {
                  this.m_rwLock.ExitWriteLock();
              }
          }
      }
      

      計算一個表達式樹的散列值需要耗費O(m)的時間復(fù)雜度,從字典中查找分組需要O(1),如果散列值夠好的話,每個分組中的表達式樹數(shù)量(k)應(yīng)該非常少,這樣從中進行查詢的時間復(fù)雜度(O(log(k)))就非常接近于常數(shù)了。因此,HashedListCache的查找操作,其時間復(fù)雜度為O(m),這也達到了最為理想的時間復(fù)雜度。

      到目前為止,我們?yōu)榱私鉀Q表達式樹的緩存問題,已經(jīng)提出了4種不同的處理方式,并且編寫了多個操作表達式樹的輔助類:

      1. SimpleKeyBuilder:將表達式樹構(gòu)造成唯一的字符串。
      2. PrefixTreeVisitor:根據(jù)表達式樹構(gòu)造一顆前綴樹。
      3. ExpressionComparer:比較兩個表達式樹的“大小”關(guān)系。
      4. ExpressionHasher:計算一個表達式樹的散列值。

      回想起第一種做法,我們使用最原始的方式,使用字典來存儲對象,不過我們需要拼接出一個龐大的字符串,因為它具有“唯一性”。但是其實從那時開始,我們就已經(jīng)走了一條彎路。在.NET Framework中,一個對象如果要作為字典的“鍵”,難道一定要是字符串嗎?很顯然,答案是否定的。事實上,任何類型的對象都可以作為字典的鍵,而字典認為兩個“鍵”對象相同依靠的是對象的GetHashCode方法和Equals方法。字典的整個查詢分兩步走:

      1. 首先根據(jù)GetHashCode獲取對象散列值,用于確定需要查找的對象在那個分組(或者說是“桶”,在數(shù)據(jù)結(jié)構(gòu)中稱為散列表的“buckets”)中。
      2. 每個分組的對象數(shù)量很少,然后在使用Equals方法依次進行比較,最終得到相同的那個值。

      因為有了ExpressionComparer和ExpressionHasher,我們已經(jīng)可以非常輕松地實現(xiàn)那個作為“鍵”的對象了:

      private class CacheKey
      {
          private IComparer<Expression> m_comparer = new ExpressionComparer();
      
          public Expression Expression { get; private set; }
      
          public CacheKey(Expression exp)
          {
              this.Expression = exp;
          }
      
          private int m_hashCode;
          private bool m_hashCodeInitialized = false;
      
          public override int GetHashCode()
          {
              if (!this.m_hashCodeInitialized)
              {
                  this.m_hashCode = new ExpressionHasher().Hash(this.Expression);
                  this.m_hashCodeInitialized = true;
              }
      
              return this.m_hashCode;
          }
      
          public override bool Equals(object obj)
          {
              if (obj == null) return false;
              if (obj.GetType() != this.GetType()) return false;
      
              CacheKey other = (CacheKey)obj;
              return this.m_comparer.Compare(this.Expression, other.Expression) == 0;
          }
      }
      

      最后再實現(xiàn)一個DictionaryCache:

      public class DictionaryCache<T> : IExpressionCache<T> where T : class
      {
          private ReaderWriterLockSlim m_rwLock = new ReaderWriterLockSlim();
          private Dictionary<CacheKey, T> m_storage = new Dictionary<CacheKey, T>();
      
          public T Get(Expression key, Func<Expression, T> creator)
          {
              T value;
              CacheKey cacheKey = new CacheKey(key);
      
              this.m_rwLock.EnterReadLock();
              try
              {
                  if (this.m_storage.TryGetValue(cacheKey, out value))
                  {
                      return value;
                  }
              }
              finally
              {
                  this.m_rwLock.ExitReadLock();
              }
      
              this.m_rwLock.EnterWriteLock();
              try
              {
                  if (this.m_storage.TryGetValue(cacheKey, out value))
                  {
                      return value;
                  }
      
                  value = creator(key);
                  this.m_storage.Add(cacheKey, value);
                  return value;
              }
              finally
              {
                  this.m_rwLock.ExitWriteLock();
              }
          }
      }
      

      DictionaryCache的實現(xiàn)其實和HashedListCache比較接近,不過從理論上說,DictionaryCache的性能不如HashedListCache。因為同樣在根據(jù)散列值獲取到分組后,DictionaryCache中的分組元素數(shù)量可能會比HashedListCache要多(因為字典中多個散列值也可以在同一個分組中);同時,字典在同組的k個元素中找到指定元素使用O(k)的遍歷算法,而二叉搜索樹只要O(log(k))的時間復(fù)雜度——此消彼長,DictionaryCache的性能自然就要略差一些了。

      至此,老趙在這個話題中計劃談起的5種解決方案都已經(jīng)講述完畢了,您覺得哪種做法的效果最好呢?在今后的文章中,老趙將會對5種解決方案進行性能上的比較并進行分析,同時給出每種方案的優(yōu)點、缺點和改進余地——其實這些才是最重要的,朋友們千萬不要錯過,也歡迎大家和我一起討論。

      最后再學(xué)著打一個廣告:如果朋友們喜歡老趙的文章,可以通過RSS訂閱進行持續(xù)關(guān)注(訂閱地址為http://www.rzrgm.cn/JeffreyZhao/rss)。

       

      完整代碼下載:http://code.msdn.microsoft.com/ExpressionCache

      相關(guān)文章:

      主站蜘蛛池模板: 女人香蕉久久毛毛片精品| 六十路熟妇乱子伦| 国产乱码精品一区二三区| 资源新版在线天堂偷自拍| 特黄aaaaaaaaa毛片免费视频| 亚洲国产精品一区二区久| 日韩人妻系列无码专区| 亚洲AV无码不卡在线播放| 在线日韩日本国产亚洲| 中文字幕无码av不卡一区| 亚洲高清WWW色好看美女| 2021亚洲va在线va天堂va国产| 999国产精品999久久久久久| 五月丁香啪啪| 中文字幕亚洲精品人妻| 国产精品会所一区二区三区| 激情无码人妻又粗又大| 亚洲日产韩国一二三四区| 日韩成人精品一区二区三区| 亚洲爆乳少妇无码激情| 最新中文乱码字字幕在线| 国产福利深夜在线播放| 高清精品视频一区二区三区 | 亚洲男女羞羞无遮挡久久丫| 极品少妇无套内射视频| 阿荣旗| 无码人妻一区二区三区精品视频 | 国产中文字幕精品在线| 亚洲精品成人片在线观看精品字幕| 熟妇人妻久久精品一区二区| 伊人蕉影院久亚洲高清| 亚洲无人区码二码三码区| 国产免费午夜福利在线播放| 精品亚洲精品日韩精品| 日韩亚洲中文图片小说| 国内精品久久毛片一区二区| 给我中国免费播放片在线| 久久精品熟女亚洲av艳妇| 国产亚洲精品AA片在线爽| 国产中文字幕精品免费| 国产精品午夜福利资源|