Optimization 记忆还是不记忆

Optimization 记忆还是不记忆,optimization,f#,memoization,Optimization,F#,Memoization,。。。这就是问题所在。我一直在研究一种算法,它以向量数组作为输入,算法的一部分反复选取向量对,并计算这两个向量的函数,这两个向量不会随时间变化。在研究优化算法的方法时,我认为这是一个很好的记忆案例:与其一遍又一遍地重新计算相同的函数值,不如将其延迟缓存并命中缓存 在开始编写代码之前,我的问题的要点是:我从记忆中获得的好处取决于向量的数量,我认为向量的数量与重复调用的数量成反比,在某些情况下,记忆会完全降低性能。那么,我的情况是否不足以记录下来?我做错了什么,有没有更聪明的方法来优化我的处境 下面

。。。这就是问题所在。我一直在研究一种算法,它以向量数组作为输入,算法的一部分反复选取向量对,并计算这两个向量的函数,这两个向量不会随时间变化。在研究优化算法的方法时,我认为这是一个很好的记忆案例:与其一遍又一遍地重新计算相同的函数值,不如将其延迟缓存并命中缓存

在开始编写代码之前,我的问题的要点是:我从记忆中获得的好处取决于向量的数量,我认为向量的数量与重复调用的数量成反比,在某些情况下,记忆会完全降低性能。那么,我的情况是否不足以记录下来?我做错了什么,有没有更聪明的方法来优化我的处境

下面是一个简化的测试脚本,它非常接近真实情况:

open System
open System.Diagnostics
open System.Collections.Generic

let size = 10 // observations
let dim = 10 // features per observation
let runs = 10000000 // number of function calls

let rng = new Random()
let clock = new Stopwatch()

let data =
    [| for i in 1 .. size ->
        [ for j in 1 .. dim -> rng.NextDouble() ] |]    
let testPairs = [| for i in 1 .. runs -> rng.Next(size), rng.Next(size) |]

let f v1 v2 = List.fold2 (fun acc x y -> acc + (x-y) * (x-y)) 0.0 v1 v2

printfn "Raw"
clock.Restart()
testPairs |> Array.averageBy (fun (i, j) -> f data.[i] data.[j]) |> printfn "Check: %f"
printfn "Raw: %i" clock.ElapsedMilliseconds
我创建了一个随机向量(数据)列表,一个随机索引集合(testPairs),并在每个索引对上运行f

以下是备忘录版本:

let memoized =
    let cache = new Dictionary<(int*int),float>(HashIdentity.Structural)
    fun key ->
        match cache.TryGetValue(key) with
        | true, v  -> v
        | false, _ ->
            let v = f data.[fst key] data.[snd key]
            cache.Add(key, v)
            v

printfn "Memoized"
clock.Restart()
testPairs |> Array.averageBy (fun (i, j) -> memoized (i, j)) |> printfn "Check: %f"
printfn "Memoized: %i" clock.ElapsedMilliseconds
让我们记住=
let cache=新字典(HashIdentity.Structural)
趣味键->
将cache.TryGetValue(键)与
|对,v->v
|错误,->
设v=f数据。[fst键]数据。[snd键]
cache.Add(键,v)
v
打印fn“备忘录化”
时钟重新启动()
testPairs |>Array.averageBy(fun(i,j)->记忆化(i,j))|>printfn“检查:%f”
打印fn“已记忆:%i”时钟。延时百万秒
以下是我观察到的情况: *当尺寸较小(10)时,记忆的速度大约是原始版本的两倍, *当尺寸较大(1000)时,记忆所需时间比原始版本多15倍, *当f是昂贵的时,记忆可以改善事情

我的解释是,当大小很小时,我们会有更多的重复计算,缓存会得到回报

令我惊讶的是,更大尺寸的手机在性能上受到了巨大的冲击,我不确定是什么原因造成的。我知道我可以用一个结构键来改进字典的访问,但我没想到“天真”版本会表现得如此糟糕


那么,我所做的有什么明显的错误吗?对于我的情况来说,记忆是错误的方法吗?如果是,还有更好的方法吗?

我认为记忆是一种有用的技术,但它不是一颗银弹。它在降低算法(理论)复杂度方面非常有用。作为一种优化,它可以(正如您可能期望的)产生不同的结果

在您的例子中,当观察数较少时(并且
f
的计算成本更高),缓存肯定更有用。您可以向备忘录中添加简单的统计信息:

let stats = ref (0, 0) // Count number of cache misses & hits
let memoized =
    let cache = new Dictionary<(int*int),float>(HashIdentity.Structural)
    fun key ->
        let (mis, hit) = !stats
        match cache.TryGetValue(key) with
        | true, v  -> stats := (mis, hit + 1); v // Increment hit count
        | false, _ ->
            stats := (mis + 1, hit); // Increment miss count
            let v = f data.[fst key] data.[snd key]
            cache.Add(key, v)
            v
let stats=ref(0,0)//计算缓存未命中和命中的数量
让我们回忆一下=
let cache=新字典(HashIdentity.Structural)
趣味键->
让(失误,击中)=!统计数据
将cache.TryGetValue(键)与
|正确,v->stats:=(错误,命中+1);v//增加命中次数
|错误,->
统计:=(失误+1,命中);//增量未命中计数
设v=f数据。[fst键]数据。[snd键]
cache.Add(键,v)
v
  • 对于较小的
    大小
    ,我得到的数字类似于
    (100999900)
    ,因此记忆有很大的好处-函数
    f
    被计算100倍,然后每个结果被重复使用99999倍

  • 对于big
    size
    ,我得到了类似于
    (632331137669)
    的结果,因此
    f
    被多次调用,每个结果只被重用两次。在这种情况下,(大)哈希表中的分配和查找开销要大得多

作为一个小优化,您可以预先分配
字典
并编写
新字典(10000,HashIdentity.Structural)
,但在这种情况下,这似乎没有多大帮助


为了提高优化效率,我认为您需要了解有关记忆函数的更多信息。在您的示例中,输入是非常规则的,因此记忆几乎没有意义,但是如果您知道函数更经常地使用一些参数值调用,那么您可能只能对这些常见参数进行记忆。

Tomas的答案非常适合于何时使用记忆。这就是为什么在你的案例中,记忆化进展如此缓慢

听起来像是在调试模式下进行测试。在发行版中再次运行您的测试,您应该会得到更快的结果以便于记忆。元组在调试模式下可能会造成很大的性能损失。我添加了一个哈希版本以进行比较,并进行了一些微优化

释放

Raw
Check: 1.441687
Raw: 894

Memoized
Check: 1.441687
Memoized: 733

memoizedHash
Check: 1.441687
memoizedHash: 552

memoizedHashInline
Check: 1.441687
memoizedHashInline: 493

memoizedHashInline2
Check: 1.441687
memoizedHashInline2: 385
调试

来源

open System
open System.Diagnostics
open System.Collections.Generic

let size = 10 // observations
let dim = 10 // features per observation
let runs = 10000000 // number of function calls

let rng = new Random()
let clock = new Stopwatch()

let data =
    [| for i in 1 .. size ->
        [ for j in 1 .. dim -> rng.NextDouble() ] |]    
let testPairs = [| for i in 1 .. runs -> rng.Next(size), rng.Next(size) |]

let f v1 v2 = List.fold2 (fun acc x y -> acc + (x-y) * (x-y)) 0.0 v1 v2

printfn "Raw"
clock.Restart()
testPairs |> Array.averageBy (fun (i, j) -> f data.[i] data.[j]) |> printfn "Check: %f"
printfn "Raw: %i\n" clock.ElapsedMilliseconds


let memoized =
    let cache = new Dictionary<(int*int),float>(HashIdentity.Structural)
    fun key ->
        match cache.TryGetValue(key) with
        | true, v  -> v
        | false, _ ->
            let v = f data.[fst key] data.[snd key]
            cache.Add(key, v)
            v

printfn "Memoized"
clock.Restart()
testPairs |> Array.averageBy (fun (i, j) -> memoized (i, j)) |> printfn "Check: %f"
printfn "Memoized: %i\n" clock.ElapsedMilliseconds


let memoizedHash =
    let cache = new Dictionary<int,float>(HashIdentity.Structural)
    fun key ->
        match cache.TryGetValue(key) with
        | true, v  -> v
        | false, _ ->
            let i = key / size
            let j = key % size
            let v = f data.[i] data.[j]
            cache.Add(key, v)
            v

printfn "memoizedHash"
clock.Restart()
testPairs |> Array.averageBy (fun (i, j) -> memoizedHash (i * size + j)) |> printfn "Check: %f"
printfn "memoizedHash: %i\n" clock.ElapsedMilliseconds



let memoizedHashInline =
    let cache = new Dictionary<int,float>(HashIdentity.Structural)
    fun key ->
        match cache.TryGetValue(key) with
        | true, v  -> v
        | false, _ ->
            let i = key / size
            let j = key % size
            let v = f data.[i] data.[j]
            cache.Add(key, v)
            v

printfn "memoizedHashInline"
clock.Restart()
let mutable total = 0.0
for i, j in testPairs do
    total <- total + memoizedHashInline (i * size + j)
printfn "Check: %f" (total / float testPairs.Length)
printfn "memoizedHashInline: %i\n" clock.ElapsedMilliseconds


printfn "memoizedHashInline2"
clock.Restart()
let mutable total2 = 0.0
let cache = new Dictionary<int,float>(HashIdentity.Structural)
for i, j in testPairs do
    let key = (i * size + j)
    match cache.TryGetValue(key) with
    | true, v  -> total2 <- total2 + v
    | false, _ ->
        let i = key / size
        let j = key % size
        let v = f data.[i] data.[j]
        cache.Add(key, v)
        total2 <- total2 + v

printfn "Check: %f" (total2 / float testPairs.Length)
printfn "memoizedHashInline2: %i\n" clock.ElapsedMilliseconds



Console.ReadLine() |> ignore
开放系统
开放系统诊断
open System.Collections.Generic
设大小=10//观察值
设dim=10//每个观测值的特征
let runs=10000000//函数调用数
设rng=new Random()
让时钟=新秒表()
让数据=
[|对于1.尺寸中的i->
[用于1..dim->rng.NextDouble()]中的j]
让testPairs=[| for i in 1..runs->rng.Next(size),rng.Next(size)|]
设f v1 v2=List.fold2(趣味acc x y->acc+(x-y)*(x-y))0.0 v1 v2
printfn“原始”
时钟重新启动()
testPairs |>Array.averageBy(fun(i,j)->f data[i]data[j])|>printfn“检查:%f”
printfn“原始:%i\n”clock.ElapsedMilliseconds
让我们回忆一下=
let cache=新字典(HashIdentity.Structural)
趣味键->
将cache.TryGetValue(键)与
|对,v->v
|错误,->
设v=f数据。[fst键]数据。[snd键]
cache.Add(键,v)
v
打印fn“备忘录化”
时钟重新启动()
testPairs |>Array.averageBy(fun(i,j)->记忆化(i,j))|>printfn“检查:%f”
printfn“已记忆:%i\n”clock.elapsedmillyses
让记忆散列=
let cache=新字典(HashIdentity.Structural)
傅
open System
open System.Diagnostics
open System.Collections.Generic

let size = 10 // observations
let dim = 10 // features per observation
let runs = 10000000 // number of function calls

let rng = new Random()
let clock = new Stopwatch()

let data =
    [| for i in 1 .. size ->
        [ for j in 1 .. dim -> rng.NextDouble() ] |]    
let testPairs = [| for i in 1 .. runs -> rng.Next(size), rng.Next(size) |]

let f v1 v2 = List.fold2 (fun acc x y -> acc + (x-y) * (x-y)) 0.0 v1 v2

printfn "Raw"
clock.Restart()
testPairs |> Array.averageBy (fun (i, j) -> f data.[i] data.[j]) |> printfn "Check: %f"
printfn "Raw: %i\n" clock.ElapsedMilliseconds


let memoized =
    let cache = new Dictionary<(int*int),float>(HashIdentity.Structural)
    fun key ->
        match cache.TryGetValue(key) with
        | true, v  -> v
        | false, _ ->
            let v = f data.[fst key] data.[snd key]
            cache.Add(key, v)
            v

printfn "Memoized"
clock.Restart()
testPairs |> Array.averageBy (fun (i, j) -> memoized (i, j)) |> printfn "Check: %f"
printfn "Memoized: %i\n" clock.ElapsedMilliseconds


let memoizedHash =
    let cache = new Dictionary<int,float>(HashIdentity.Structural)
    fun key ->
        match cache.TryGetValue(key) with
        | true, v  -> v
        | false, _ ->
            let i = key / size
            let j = key % size
            let v = f data.[i] data.[j]
            cache.Add(key, v)
            v

printfn "memoizedHash"
clock.Restart()
testPairs |> Array.averageBy (fun (i, j) -> memoizedHash (i * size + j)) |> printfn "Check: %f"
printfn "memoizedHash: %i\n" clock.ElapsedMilliseconds



let memoizedHashInline =
    let cache = new Dictionary<int,float>(HashIdentity.Structural)
    fun key ->
        match cache.TryGetValue(key) with
        | true, v  -> v
        | false, _ ->
            let i = key / size
            let j = key % size
            let v = f data.[i] data.[j]
            cache.Add(key, v)
            v

printfn "memoizedHashInline"
clock.Restart()
let mutable total = 0.0
for i, j in testPairs do
    total <- total + memoizedHashInline (i * size + j)
printfn "Check: %f" (total / float testPairs.Length)
printfn "memoizedHashInline: %i\n" clock.ElapsedMilliseconds


printfn "memoizedHashInline2"
clock.Restart()
let mutable total2 = 0.0
let cache = new Dictionary<int,float>(HashIdentity.Structural)
for i, j in testPairs do
    let key = (i * size + j)
    match cache.TryGetValue(key) with
    | true, v  -> total2 <- total2 + v
    | false, _ ->
        let i = key / size
        let j = key % size
        let v = f data.[i] data.[j]
        cache.Add(key, v)
        total2 <- total2 + v

printfn "Check: %f" (total2 / float testPairs.Length)
printfn "memoizedHashInline2: %i\n" clock.ElapsedMilliseconds



Console.ReadLine() |> ignore