英文:
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 (
"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())
}
答案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).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论