7

これが正しいかどうか、またはより良い方法があるかどうか、またはこの問題を既に解決している既存のライブラリがあるかどうかはわかりません。

特に、CAS がメモリ フェンスを必要とするかどうかはわかりません。

エージェントと変更可能な辞書も試してみましたが、遅くなるという私の直感が確認され、実装がより複雑になりました。

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
4

1 に答える 1

2

何をメモするかを文字列入力を受け取る関数に限定したい場合は、 の機能を再利用できますSystem.Runtime.Caching

これは、コア ライブラリの一部としてかなり堅牢である必要があります (希望はあります...) が、文字列の制限はかなり重いものであり、パフォーマンスを比較したい場合は、現在の実装に対してベンチマークする必要があります。

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)

使用法:

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

基礎となるキャッシュに直接アクセスすることにまったく興味がない場合は、明らかに古い関数と同じシグネチャを持つ新しい関数を返すことができますが、キャッシュが IDisposable であることを考えると、それは賢明ではないように思えました。

多くの点であなたのソリューションを好むと思いますが、同様の問題に直面したとき、できれば組み込みのものを実際に使用する必要があるというひねくれた考えがありました。

于 2014-06-23T16:01:06.490 に答える