Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/sources/HeuristicLab.Problems.ExternalEvaluation/3.4/EvaluationCache.cs @ 13365

Last change on this file since 13365 was 13203, checked in by pfleck, 9 years ago

#1674 Made CacheEntry thread-safe. Renamed the persistence properties for more clarity.

File size: 11.3 KB
RevLine 
[6140]1#region License Information
2/* HeuristicLab
[12012]3 * Copyright (C) 2002-2015 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
[6140]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/>.
[6169]19 *
20 * The LRU cache is based on an idea by Robert Rossney see
21 * <http://csharp-lru-cache.googlecode.com>.
[6140]22 */
23#endregion
24
25using System;
26using System.Collections.Generic;
[6519]27using System.Globalization;
28using System.IO;
[6140]29using System.Linq;
[6519]30using System.Text.RegularExpressions;
[6183]31using System.Threading;
[13180]32using Google.ProtocolBuffers;
[6140]33using HeuristicLab.Common;
34using HeuristicLab.Common.Resources;
35using HeuristicLab.Core;
[6169]36using HeuristicLab.Data;
37using HeuristicLab.Parameters;
[6140]38using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
[6519]39
[6140]40namespace HeuristicLab.Problems.ExternalEvaluation {
41
42  [Item("EvaluationCache", "Cache for external evaluation values")]
43  [StorableClass]
[6169]44  public class EvaluationCache : ParameterizedNamedItem {
[6140]45
[6169]46    #region Types
[6291]47    private sealed class CacheEntry {
[6169]48
[6291]49      public readonly string Key;
[6169]50
[13180]51      private QualityMessage message;
52      private byte[] rawMessage;
53
[13203]54      private object lockObject = new object();
55
[13180]56      public byte[] RawMessage {
57        get { return rawMessage; }
58        set {
[13203]59          lock (lockObject) {
60            rawMessage = value;
61            message = null;
62          }
[13180]63        }
[6169]64      }
65
[6291]66      public CacheEntry(string key) {
67        Key = key;
68      }
[6169]69
[13180]70      public QualityMessage GetMessage(ExtensionRegistry extensions) {
[13203]71        lock (lockObject) {
72          if (message == null && rawMessage != null)
73            message = QualityMessage.ParseFrom(ByteString.CopyFrom(rawMessage), extensions);
74        }
[13180]75        return message;
76      }
77      public void SetMessage(QualityMessage value) {
[13203]78        lock (lockObject) {
79          message = value;
80          rawMessage = value.ToByteArray();
81        }
[13180]82      }
83
[6169]84      public override bool Equals(object obj) {
85        CacheEntry other = obj as CacheEntry;
86        if (other == null)
87          return false;
88        return Key.Equals(other.Key);
89      }
90
91      public override int GetHashCode() {
92        return Key.GetHashCode();
93      }
94
[13180]95      public string QualityString(IFormatProvider formatProvider = null) {
96        if (formatProvider == null) formatProvider = CultureInfo.CurrentCulture;
97        if (RawMessage == null) return "-";
98        var msg = message ?? CreateBasicQualityMessage();
99        switch (msg.Type) {
100          case QualityMessage.Types.Type.SingleObjectiveQualityMessage:
101            return msg.GetExtension(SingleObjectiveQualityMessage.QualityMessage_).Quality.ToString(formatProvider);
102          case QualityMessage.Types.Type.MultiObjectiveQualityMessage:
103            var qualities = msg.GetExtension(MultiObjectiveQualityMessage.QualityMessage_).QualitiesList;
104            return string.Format("[{0}]", string.Join(",", qualities.Select(q => q.ToString(formatProvider))));
105          default:
106            return "-";
107        }
108      }
109      private QualityMessage CreateBasicQualityMessage() {
110        var extensions = ExtensionRegistry.CreateInstance();
111        ExternalEvaluationMessages.RegisterAllExtensions(extensions);
112        return QualityMessage.ParseFrom(ByteString.CopyFrom(rawMessage), extensions);
113      }
114
[6169]115      public override string ToString() {
[13180]116        return string.Format("{{{0} : {1}}}", Key, QualityString());
[6169]117      }
118    }
119
[13180]120    public delegate QualityMessage Evaluator(SolutionMessage message);
[6169]121    #endregion
[6140]122
[6169]123    #region Fields
124    private LinkedList<CacheEntry> list;
125    private Dictionary<CacheEntry, LinkedListNode<CacheEntry>> index;
[6291]126
[6188]127    private HashSet<string> activeEvaluations = new HashSet<string>();
[6519]128    private object cacheLock = new object();
[6169]129    #endregion
130
131    #region Properties
[7201]132    public static new System.Drawing.Image StaticItemImage {
[6140]133      get { return VSImageLibrary.Database; }
134    }
[6291]135    public int Size { get { lock (cacheLock) return index.Count; } }
136    public int ActiveEvaluations { get { lock (cacheLock) return activeEvaluations.Count; } }
137
[6140]138    [Storable]
[6169]139    public int Hits { get; private set; }
[6140]140    #endregion
141
[6169]142    #region events
[6291]143    public event EventHandler Changed;
[6140]144
[6291]145    protected virtual void OnChanged() {
146      EventHandler handler = Changed;
[6140]147      if (handler != null)
148        handler(this, EventArgs.Empty);
149    }
150    #endregion
151
[6169]152    #region Parameters
153    public FixedValueParameter<IntValue> CapacityParameter {
154      get { return (FixedValueParameter<IntValue>)Parameters["Capacity"]; }
155    }
[6183]156    public FixedValueParameter<BoolValue> PersistentCacheParameter {
157      get { return (FixedValueParameter<BoolValue>)Parameters["PersistentCache"]; }
158    }
[6169]159    #endregion
[6140]160
[6169]161    #region Parameter Values
162    public int Capacity {
163      get { return CapacityParameter.Value.Value; }
164      set { CapacityParameter.Value.Value = value; }
165    }
[6183]166    public bool IsPersistent {
167      get { return PersistentCacheParameter.Value.Value; }
168    }
[6169]169    #endregion
[6140]170
[6169]171    #region Persistence
[13180]172    #region BackwardsCompatibility3.4
[6519]173    [Storable(Name = "Cache")]
[13203]174    private IEnumerable<KeyValuePair<string, double>> Cache_Persistence_backwardscompatability {
[13180]175      get { return Enumerable.Empty<KeyValuePair<string, double>>(); }
[6169]176      set {
[13180]177        var rawMessages = value.ToDictionary(kvp => kvp.Key,
178          kvp => QualityMessage.CreateBuilder()
179            .SetSolutionId(0)
180            .SetExtension(
181              SingleObjectiveQualityMessage.QualityMessage_,
182              SingleObjectiveQualityMessage.CreateBuilder().SetQuality(kvp.Value).Build())
183            .Build().ToByteArray());
184        SetCacheValues(rawMessages);
[6169]185      }
186    }
[13180]187    #endregion
188    [Storable(Name = "CacheNew")]
[13203]189    private IEnumerable<KeyValuePair<string, byte[]>> Cache_Persistence {
[13180]190      get { return IsPersistent ? GetCacheValues() : Enumerable.Empty<KeyValuePair<string, byte[]>>(); }
191      set { SetCacheValues(value); }
192    }
[6169]193    [StorableHook(HookType.AfterDeserialization)]
194    private void AfterDeserialization() {
195      RegisterEvents();
196    }
197    #endregion
198
[6140]199    #region Construction & Cloning
200    [StorableConstructor]
201    protected EvaluationCache(bool deserializing) : base(deserializing) { }
202    protected EvaluationCache(EvaluationCache original, Cloner cloner)
[13180]203        : base(original, cloner) {
[6183]204      SetCacheValues(original.GetCacheValues());
[7737]205      Hits = original.Hits;
[6169]206      RegisterEvents();
[6140]207    }
208    public EvaluationCache() {
[6169]209      list = new LinkedList<CacheEntry>();
210      index = new Dictionary<CacheEntry, LinkedListNode<CacheEntry>>();
211      Parameters.Add(new FixedValueParameter<IntValue>("Capacity", "Maximum number of cache entries.", new IntValue(10000)));
[6183]212      Parameters.Add(new FixedValueParameter<BoolValue>("PersistentCache", "Save cache when serializing object graph?", new BoolValue(false)));
[6169]213      RegisterEvents();
[6140]214    }
215    public override IDeepCloneable Clone(Cloner cloner) {
216      return new EvaluationCache(this, cloner);
217    }
218    #endregion
219
[6169]220    #region Event Handling
221    private void RegisterEvents() {
[7737]222      CapacityParameter.Value.ValueChanged += new EventHandler(CapacityChanged);
[6169]223    }
224
[7737]225    void CapacityChanged(object sender, EventArgs e) {
[6169]226      if (Capacity < 0)
227        throw new ArgumentOutOfRangeException("Cache capacity cannot be less than zero");
[6291]228      lock (cacheLock)
229        Trim();
230      OnChanged();
[6169]231    }
232    #endregion
233
234    #region Methods
235    public void Reset() {
[6183]236      lock (cacheLock) {
237        list = new LinkedList<CacheEntry>();
238        index = new Dictionary<CacheEntry, LinkedListNode<CacheEntry>>();
239        Hits = 0;
240      }
[6291]241      OnChanged();
[6169]242    }
243
[13180]244    public QualityMessage GetValue(SolutionMessage message, Evaluator evaluate, ExtensionRegistry extensions) {
[7737]245      var entry = new CacheEntry(message.ToString());
[6183]246      bool lockTaken = false;
[6291]247      bool waited = false;
[6519]248      try {
[6183]249        Monitor.Enter(cacheLock, ref lockTaken);
[6291]250        while (true) {
[7737]251          LinkedListNode<CacheEntry> node;
[6291]252          if (index.TryGetValue(entry, out node)) {
253            list.Remove(node);
254            list.AddLast(node);
255            Hits++;
[6183]256            lockTaken = false;
[6291]257            Monitor.Exit(cacheLock);
258            OnChanged();
[13180]259            return node.Value.GetMessage(extensions);
[6291]260          } else {
261            if (!waited && activeEvaluations.Contains(entry.Key)) {
262              while (activeEvaluations.Contains(entry.Key))
263                Monitor.Wait(cacheLock);
264              waited = true;
265            } else {
266              activeEvaluations.Add(entry.Key);
267              lockTaken = false;
268              Monitor.Exit(cacheLock);
269              OnChanged();
270              try {
[13180]271                entry.SetMessage(evaluate(message));
[6291]272                Monitor.Enter(cacheLock, ref lockTaken);
273                index[entry] = list.AddLast(entry);
274                Trim();
[13180]275              } finally {
[6291]276                if (!lockTaken)
277                  Monitor.Enter(cacheLock, ref lockTaken);
278                activeEvaluations.Remove(entry.Key);
279                Monitor.PulseAll(cacheLock);
280                lockTaken = false;
281                Monitor.Exit(cacheLock);
282              }
283              OnChanged();
[13180]284              return entry.GetMessage(extensions);
[6188]285            }
[6183]286          }
287        }
[13180]288      } finally {
[6183]289        if (lockTaken)
[6291]290          Monitor.Exit(cacheLock);
[6183]291      }
292    }
293
[6169]294    private void Trim() {
[6291]295      while (list.Count > Capacity) {
[7737]296        var item = list.First;
[6291]297        list.Remove(item);
298        index.Remove(item.Value);
[6169]299      }
[6140]300    }
[6265]301
[13180]302    private IEnumerable<KeyValuePair<string, byte[]>> GetCacheValues() {
[6183]303      lock (cacheLock) {
[13180]304        return index.ToDictionary(kvp => kvp.Key.Key, kvp => kvp.Key.RawMessage);
[6183]305      }
306    }
[6265]307
[13180]308    private void SetCacheValues(IEnumerable<KeyValuePair<string, byte[]>> value) {
[6183]309      lock (cacheLock) {
[13180]310        if (list == null) list = new LinkedList<CacheEntry>();
311        if (index == null) index = new Dictionary<CacheEntry, LinkedListNode<CacheEntry>>();
[6183]312        foreach (var kvp in value) {
[13180]313          var entry = new CacheEntry(kvp.Key) { RawMessage = kvp.Value };
[6183]314          index[entry] = list.AddLast(entry);
315        }
316      }
317    }
[6265]318
319    public void Save(string filename) {
320      using (var writer = new StreamWriter(filename)) {
321        lock (cacheLock) {
322          foreach (var entry in list) {
323            writer.WriteLine(string.Format(CultureInfo.InvariantCulture,
324              "\"{0}\", {1}",
325              Regex.Replace(entry.Key, "\\s", "").Replace("\"", "\"\""),
[13180]326              entry.QualityString(CultureInfo.InvariantCulture)));
[6265]327          }
328        }
329        writer.Close();
330      }
331    }
[6169]332    #endregion
[6291]333  }
[6140]334}
Note: See TracBrowser for help on using the repository browser.