解决Go语言中的map指针问题的方法

huangapple go评论87阅读模式
英文:

Workaround Go's map pointer issue

问题

我正在翻译你提供的内容,请稍等片刻。

我正在解决这个 Project Euler 问题。首先我尝试了蛮力法,用了0.5秒,然后我尝试了动态规划来利用记忆化,期望有很大的改进,但我惊讶地发现结果只用了0.36秒。

经过一点搜索,我发现你不能在一个函数(find_collatz_len)中使用指向外部映射数据(memo)的指针。所以每次运行下面的函数时,它都会复制整个字典。这听起来像是对处理器性能的巨大浪费。

我的问题是有没有一种解决方法,可以使用指向函数外部映射的指针,以避免复制。

这是我的糟糕代码:

package main
//project euler 014 - longest collatz sequence

import (
    "fmt"
    "time"
)

func find_collatz_len(n int, memo map[int]int) int {
    counter := 1
    initital_value := n
    for n != 1 {
        counter++
        if n < initital_value {
            counter = counter + memo[n]
            break
        }
        if n%2 == 0 {
            n = int(float64(n)/2)
        } else {
            n = n*3+1
        }
    }
    memo[initital_value] = counter
    return counter
}

func main() {
    start := time.Now()
    max_length := 0
    number := 0
    current_length := 0
    memo := make(map[int]int)
    for i:=1; i<1_000_000; i++ {
        current_length = find_collatz_len(i, memo)
        if current_length > max_length {
            max_length = current_length 
            number = i
        }
    }
    fmt.Println(max_length, number)
    fmt.Println("Time:", time.Since(start).Seconds())
}
英文:

I was solving this Project Euler question. First I tried brute force and it took 0.5 seconds and then I tried the dynamic programming to utilize memoization expecting a huge improvement but I surprised that the result was 0.36 seconds.

After a little bit of googling I found out you can not use a pointer in a function (find_collatz_len) to an outside map data (memo). So each time the function below runs it copies over the entire dictionary. That sounds like a huge waste of processor power.

My question is what is a workaround so that I can use a pointer to a map outside the function to avoid the copying.

Here is my ugly code:

package main
//project euler 014 - longest collatz sequence

import (
    &quot;fmt&quot;
    &quot;time&quot;
)

func find_collatz_len(n int, memo map[int]int) int {
    counter := 1
    initital_value := n
    for n != 1 {
        counter++
        if n &lt; initital_value {
            counter = counter + memo[n]
            break
        }
        if n%2 == 0 {
            n = int(float64(n)/2)
        } else {
            n = n*3+1
        }
    }
    memo[initital_value] = counter
    return counter
}

func main() {
    start := time.Now()
    max_length := 0
    number := 0
    current_length := 0
    memo := make(map[int]int)
    for i:=1; i&lt;1_000_000; i++ {
        current_length = find_collatz_len(i, memo)
        if current_length &gt; max_length {
            max_length = current_length 
            number = i
        }
    }
    fmt.Println(max_length, number)
    fmt.Println(&quot;Time:&quot;, time.Since(start).Seconds())
}

答案1

得分: 5

地图在底层已经是指针。传递地图值将传递一个单一的指针。有关详细信息,请参阅https://stackoverflow.com/questions/55520624/why-slice-values-can-sometimes-go-stale-but-never-map-values/55521138#55521138

当创建一个没有容量提示的地图时,地图会分配具有足够内部结构来存储相对较少条目(大约7条)的内存。随着地图的增长,实现有时需要分配更多的内存并重构(重新散列)地图以容纳更多的元素。如果按照@mkopriva的建议使用预期的最终容量初始化地图,可以避免这种情况:

memo := make(map[int]int, 1_000_000).

因此,将分配足够的空间来存储所有条目(在您的示例中为1_000_000),因此在应用程序的生命周期内不会发生重新散列。这将将运行时间从0.3秒减少到0.2秒。

您还可以将int(float64(n)/2)替换为n/2,因为在您使用的整数范围内,它们会给出相同的结果。这将进一步提高5%的性能(在我的机器上为0.19秒)。

英文:

Maps are already pointers under the hood. Passing a map value will pass a single pointer. For details, see https://stackoverflow.com/questions/55520624/why-slice-values-can-sometimes-go-stale-but-never-map-values/55521138#55521138

When creating a map without a capacity hint, a map is allocated with internal structure enough to store a relatively small number of entries (around 7). As the map grows, the implementation sometimes needs to allocate more memory and restructure (rehash) the map to accommodate more elements. This can be avoided if you initialize the map with the expected final capacity as suggested by @mkopriva:

memo := make(map[int]int, 1_000_000).

As a result, enough room will be allocated to store all entries (1_000_000 in your example), so a rehash will not happen during the lifetime of your app. This will reduce the runtime from 0.3 sec to 0.2 sec.

You can also replace int(float64(n)/2) with n/2, as in the integer range you use, they give the same result. This will give you further 5% boost (0.19 sec on my machine).

huangapple
  • 本文由 发表于 2021年10月6日 18:31:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/69464063.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定