Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Problems.ExternalEvaluation/3.3/EvaluationCache.cs @ 6345

Last change on this file since 6345 was 6291, checked in by epitzer, 14 years ago

fixed synchronization issue in evaluation cache and simplified event handling (#1516)

File size: 8.8 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2011 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 *
20 * The LRU cache is based on an idea by Robert Rossney see
21 * <http://csharp-lru-cache.googlecode.com>.
22 */
23#endregion
24
25using System;
26using System.Collections.Generic;
27using System.Linq;
28using System.Threading;
29using HeuristicLab.Common;
30using HeuristicLab.Common.Resources;
31using HeuristicLab.Core;
32using HeuristicLab.Data;
33using HeuristicLab.Parameters;
34using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
35using HeuristicLab.Analysis;
36using System.IO;
37using System.Globalization;
38using System.Text.RegularExpressions;
39namespace HeuristicLab.Problems.ExternalEvaluation {
40
41  [Item("EvaluationCache", "Cache for external evaluation values")]
42  [StorableClass]
43  public class EvaluationCache : ParameterizedNamedItem {
44
45    #region Types
46    private sealed class CacheEntry {
47
48      public readonly string Key;
49      public double Value;
50
51      public CacheEntry(string key, double value) {
52        Key = key;
53        Value = value;
54      }
55
56      public CacheEntry(string key) {
57        Key = key;
58      }
59
60      public override bool Equals(object obj) {
61        CacheEntry other = obj as CacheEntry;
62        if (other == null)
63          return false;
64        return Key.Equals(other.Key);
65      }
66
67      public override int GetHashCode() {
68        return Key.GetHashCode();
69      }
70
71      public override string ToString() {
72        return string.Format("{{{0} : {1}}}", Key, Value);
73      }
74    }
75
76    public delegate double Evaluator(SolutionMessage message);
77    #endregion
78
79    #region Fields
80    private LinkedList<CacheEntry> list;
81    private Dictionary<CacheEntry, LinkedListNode<CacheEntry>> index;
82
83    private HashSet<string> activeEvaluations = new HashSet<string>();
84    private object cacheLock = new object();   
85    #endregion
86
87    #region Properties
88    public override System.Drawing.Image ItemImage {
89      get { return VSImageLibrary.Database; }
90    }
91    public int Size { get { lock (cacheLock) return index.Count; } }
92    public int ActiveEvaluations { get { lock (cacheLock) return activeEvaluations.Count; } }
93
94    [Storable]
95    public int Hits { get; private set; }
96    #endregion
97
98    #region events
99    public event EventHandler Changed;
100
101    protected virtual void OnChanged() {
102      EventHandler handler = Changed;
103      if (handler != null)
104        handler(this, EventArgs.Empty);
105    }
106    #endregion
107
108    #region Parameters
109    public FixedValueParameter<IntValue> CapacityParameter {
110      get { return (FixedValueParameter<IntValue>)Parameters["Capacity"]; }
111    }
112    public FixedValueParameter<BoolValue> PersistentCacheParameter {
113      get { return (FixedValueParameter<BoolValue>)Parameters["PersistentCache"]; }
114    }
115    #endregion
116
117    #region Parameter Values
118    public int Capacity {
119      get { return CapacityParameter.Value.Value; }
120      set { CapacityParameter.Value.Value = value; }
121    }
122    public bool IsPersistent {
123      get { return PersistentCacheParameter.Value.Value; }
124    }
125    #endregion
126
127    #region Persistence
128    [Storable(Name="Cache")]
129    private IEnumerable<KeyValuePair<string, double>> Cache_Persistence {
130      get {
131        if (IsPersistent) {
132          return GetCacheValues();
133        } else {
134          return Enumerable.Empty<KeyValuePair<string, double>>();
135        }
136      }
137      set {
138        SetCacheValues(value);
139      }
140    }
141    [StorableHook(HookType.AfterDeserialization)]
142    private void AfterDeserialization() {
143      RegisterEvents();
144    }
145    #endregion
146
147    #region Construction & Cloning
148    [StorableConstructor]
149    protected EvaluationCache(bool deserializing) : base(deserializing) { }
150    protected EvaluationCache(EvaluationCache original, Cloner cloner)
151      : base(original, cloner) {
152      SetCacheValues(original.GetCacheValues());
153      RegisterEvents();
154    }
155    public EvaluationCache() {
156      list = new LinkedList<CacheEntry>();
157      index = new Dictionary<CacheEntry, LinkedListNode<CacheEntry>>();
158      Parameters.Add(new FixedValueParameter<IntValue>("Capacity", "Maximum number of cache entries.", new IntValue(10000)));
159      Parameters.Add(new FixedValueParameter<BoolValue>("PersistentCache", "Save cache when serializing object graph?", new BoolValue(false)));
160      RegisterEvents();
161    }
162    public override IDeepCloneable Clone(Cloner cloner) {
163      return new EvaluationCache(this, cloner);
164    }
165    #endregion
166
167    #region Event Handling
168    private void RegisterEvents() {
169      CapacityParameter.Value.ValueChanged += new EventHandler(Value_ValueChanged);
170    }
171
172    void Value_ValueChanged(object sender, EventArgs e) {
173      if (Capacity < 0)
174        throw new ArgumentOutOfRangeException("Cache capacity cannot be less than zero");
175      lock (cacheLock)
176        Trim();
177      OnChanged();
178    }
179    #endregion
180
181    #region Methods
182    public void Reset() {
183      lock (cacheLock) {
184        list = new LinkedList<CacheEntry>();
185        index = new Dictionary<CacheEntry, LinkedListNode<CacheEntry>>();
186        Hits = 0;
187      }
188      OnChanged();
189    }
190
191    public double GetValue(SolutionMessage message, Evaluator evaluate) {
192      CacheEntry entry = new CacheEntry(message.ToString());
193      LinkedListNode<CacheEntry> node;
194      bool lockTaken = false;
195      bool waited = false;
196      try {       
197        Monitor.Enter(cacheLock, ref lockTaken);
198        while (true) {
199          if (index.TryGetValue(entry, out node)) {
200            list.Remove(node);
201            list.AddLast(node);
202            Hits++;
203            lockTaken = false;
204            Monitor.Exit(cacheLock);
205            OnChanged();
206            return node.Value.Value;
207          } else {
208            if (!waited && activeEvaluations.Contains(entry.Key)) {
209              while (activeEvaluations.Contains(entry.Key))
210                Monitor.Wait(cacheLock);
211              waited = true;
212            } else {
213              activeEvaluations.Add(entry.Key);
214              lockTaken = false;
215              Monitor.Exit(cacheLock);
216              OnChanged();
217              try {
218                entry.Value = evaluate(message);
219                Monitor.Enter(cacheLock, ref lockTaken);
220                index[entry] = list.AddLast(entry);
221                Trim();
222              } finally {
223                if (!lockTaken)
224                  Monitor.Enter(cacheLock, ref lockTaken);
225                activeEvaluations.Remove(entry.Key);
226                Monitor.PulseAll(cacheLock);
227                lockTaken = false;
228                Monitor.Exit(cacheLock);
229              }
230              OnChanged();
231              return entry.Value;
232            }
233          }
234        }
235      } finally {
236        if (lockTaken)
237          Monitor.Exit(cacheLock);
238      }
239    }
240
241    private void Trim() {
242      while (list.Count > Capacity) {
243        LinkedListNode<CacheEntry> item = list.First;
244        list.Remove(item);
245        index.Remove(item.Value);
246      }
247    }
248
249    private IEnumerable<KeyValuePair<string, double>> GetCacheValues() {
250      lock (cacheLock) {
251        return index.ToDictionary(kvp => kvp.Key.Key, kvp => kvp.Key.Value);
252      }
253    }
254
255    private void SetCacheValues(IEnumerable<KeyValuePair<string, double>> value) {
256      lock (cacheLock) {
257        list = new LinkedList<CacheEntry>();
258        index = new Dictionary<CacheEntry, LinkedListNode<CacheEntry>>();
259        foreach (var kvp in value) {
260          var entry = new CacheEntry(kvp.Key);
261          entry.Value = kvp.Value;
262          index[entry] = list.AddLast(entry);
263        }
264      }
265    }
266
267    public void Save(string filename) {
268      using (var writer = new StreamWriter(filename)) {
269        lock (cacheLock) {
270          foreach (var entry in list) {
271            writer.WriteLine(string.Format(CultureInfo.InvariantCulture,
272              "\"{0}\", {1}",
273              Regex.Replace(entry.Key, "\\s", "").Replace("\"", "\"\""),
274              entry.Value));
275          }
276        }
277        writer.Close();
278      }
279    }
280    #endregion
281  }
282}
Note: See TracBrowser for help on using the repository browser.