2013-08-13 15 views
7

Nie jestem pewien, czy mam to prawo, czy jest lepszy sposób lub istniejąca biblioteka rozwiązująca ten problem.Czas na żywo notowania w F #

W szczególności nie jestem pewien, czy CAS będzie potrzebował ogrodzenia pamięci ... Myślę, że nie, ale lepiej zapytaj.

Próbowałem również z agentem i zmiennym słownikiem, ale moja intuicja, że ​​byłaby wolniejsza, została potwierdzona, a implementacja bardziej zaangażowana.

module CAS = 
    open System.Threading 

    let create (value: 'T) = 
     let cell = ref value 

     let get() = !cell 

     let rec swap f = 
      let before = get() 
      let newValue = f before 
      match Interlocked.CompareExchange<'T>(cell, newValue, before) with 
      | result when obj.ReferenceEquals(before, result) -> 
       newValue 
      | _ -> 
       swap f 

     get, swap 

module Memoization = 
    let timeToLive milis f = 
     let get, swap = CAS.create Map.empty 

     let evict key = 
      async { 
       do! Async.Sleep milis 
       swap (Map.remove key) |> ignore 
      } |> Async.Start 

     fun key -> 
      let data = get() 
      match data.TryFind key with 
      | Some v -> v 
      | None -> 
       let v = f key 
       swap (Map.add key v) |> ignore 
       evict key 
       v 
+0

Testowałeś to? Czy to działa? – Daniel

+0

Wydaje się działać naprawdę dobrze. –

+0

Wydaje mi się, że to w porządku. Nie sądzę, żebyś potrzebował bariery pamięci z 'PorównajExchange'. Nitpick: powinno być 'millis'. Możesz porównać perf 'ConcurrentDictionary'. – Daniel

Odpowiedz

1

Jeśli są chętni do ograniczenia co do memoize do funkcji, które mają wejście ciąg, można ponownie wykorzystać funkcjonalność z System.Runtime.Caching.

To powinno być dość solidne jako część biblioteki głównej (możesz mieć nadzieję ...), ale ograniczenie łańcucha znaków jest dość ciężkie i musisz porównać z bieżącą implementacją, jeśli chcesz porównać na wydajność.

open System 
open System.Runtime.Caching 

type Cached<'a>(func : string -> 'a, cache : IDisposable) = 
    member x.Func : string -> 'a = func 

    interface IDisposable with 
     member x.Dispose() = 
      cache.Dispose() 

let cache timespan (func : string -> 'a) = 
    let cache = new MemoryCache(typeof<'a>.FullName) 
    let newFunc parameter = 
     match cache.Get(parameter) with 
     | null -> 
      let result = func parameter 
      let ci = CacheItem(parameter, result :> obj) 
      let cip = CacheItemPolicy() 
      cip.AbsoluteExpiration <- DateTimeOffset(DateTime.UtcNow + timespan) 
      cip.SlidingExpiration <- TimeSpan.Zero 
      cache.Add(ci, cip) |> ignore 
      result 
     | result -> 
      (result :?> 'a) 
    new Cached<'a>(newFunc, cache) 

let cacheAsync timespan (func : string -> Async<'a>) = 
    let cache = new MemoryCache(typeof<'a>.FullName) 
    let newFunc parameter = 
     match cache.Get(parameter) with 
     | null -> 
      async { 
       let! result = func parameter 
       let ci = CacheItem(parameter, result :> obj) 
       let cip = CacheItemPolicy() 
       cip.AbsoluteExpiration <- DateTimeOffset(DateTime.UtcNow + timespan) 
       cip.SlidingExpiration <- TimeSpan.Zero 
       cache.Add(ci, cip) |> ignore 
       return result 
      } 
     | result -> 
      async { return (result :?> 'a) } 
    new Cached<Async<'a>>(newFunc, cache) 

Zastosowanie:

let getStuff = 
    let cached = cacheAsync (TimeSpan(0, 0, 5)) uncachedGetStuff 
    // deal with the fact that the cache is IDisposable here 
    // however is appropriate... 
    cached.Func 

Jeśli nie jesteś zainteresowany w dostępie do podstawowej pamięci podręcznej bezpośrednio można oczywiście po prostu wrócić nową funkcję o takiej samej sygnaturze starego - ale ze względu na cache jest IDisposable wydawało się to nierozsądne.

Myślę, że pod wieloma względami wolę twoje rozwiązanie, ale gdy stanąłem przed podobnym problemem, miałem perwersyjną myśl, że powinienem naprawdę użyć wbudowanych rzeczy, jeśli bym mógł.