我注意到map[int]int
变量的以下两个增量方法的速度因子是3倍:
快速:myMap[key]++
慢:myMap[key]=myMap[key]+1
这可能不足为奇,因为至少在第二种情况下,我天真地指示Go两次访问myMap。我很好奇:熟悉Go编译器的人可以帮助我了解这些地图操作之间的区别吗?并且了解了编译器的工作原理之后,有没有更快的技巧来增加映射?
编辑:在本地运行该差异不明显,但仍然存在:
package main
import (
"fmt"
"math"
"time"
)
func main() {
x, y := make(map[int]int), make(map[int]int)
x[0], y[0] = 0, 0
steps := int(math.Pow(10, 9))
start1 := time.Now()
for i := 0; i < steps; i++ {
x[0]++
}
elapsed1 := time.Since(start1)
fmt.Println("++ took", elapsed1)
start2 := time.Now()
for i := 0; i < steps; i++ {
y[0] = y[0] + 1
}
elapsed2 := time.Since(start2)
fmt.Println("y=y+1 took", elapsed2)
}
输出:
++ took 8.1739809s
y=y+1 took 17.9079386s
Edit2:按照建议,我转储了机器代码。这是相关的片段
对于x [0] ++
0x4981e3 488d05b6830100 LEAQ runtime.types+95648(SB), AX
0x4981ea 48890424 MOVQ AX, 0(SP)
0x4981ee 488d8c2400020000 LEAQ 0x200(SP), CX
0x4981f6 48894c2408 MOVQ CX, 0x8(SP)
0x4981fb 48c744241000000000 MOVQ $0x0, 0x10(SP)
0x498204 e8976df7ff CALL runtime.mapassign_fast64(SB)
0x498209 488b442418 MOVQ 0x18(SP), AX
0x49820e 48ff00 INCQ 0(AX)
对于y [0] = y [0] +1
0x498302 488d0597820100 LEAQ runtime.types+95648(SB), AX
0x498309 48890424 MOVQ AX, 0(SP)
0x49830d 488d8c24d0010000 LEAQ 0x1d0(SP), CX
0x498315 48894c2408 MOVQ CX, 0x8(SP)
0x49831a 48c744241000000000 MOVQ $0x0, 0x10(SP)
0x498323 e80869f7ff CALL runtime.mapaccess1_fast64(SB)
0x498328 488b442418 MOVQ 0x18(SP), AX
0x49832d 488b00 MOVQ 0(AX), AX
0x498330 4889442448 MOVQ AX, 0x48(SP)
0x498335 488d0d64820100 LEAQ runtime.types+95648(SB), CX
0x49833c 48890c24 MOVQ CX, 0(SP)
0x498340 488d9424d0010000 LEAQ 0x1d0(SP), DX
0x498348 4889542408 MOVQ DX, 0x8(SP)
0x49834d 48c744241000000000 MOVQ $0x0, 0x10(SP)
0x498356 e8456cf7ff CALL runtime.mapassign_fast64(SB)
0x49835b 488b442418 MOVQ 0x18(SP), AX
0x498360 488b4c2448 MOVQ 0x48(SP), CX
0x498365 48ffc1 INCQ CX
0x498368 488908 MOVQ CX, 0(AX)
奇怪的是,++甚至没有调用地图访问权限! ++显然是一个简单的操作(大约2或3)。我解析机器的能力到此就结束了,因此,如果有人对正在发生的事情有深刻的了解,我很想听听
答案 0 :(得分:4)
Go gc编译器是一种优化的编译器。它正在不断地得到改善。例如,对于Go1.11,
开始问题:cmd/compile: We can avoid extra mapaccess in "m[k] op= r" #23661
开始提交:7395083136539331537d46875ab9d196797a2173
cmd/compile: avoid extra mapaccess in "m[k] op= r" Currently, order desugars map assignment operations like m[k] op= r into m[k] = m[k] op r which in turn is transformed during walk into: tmp := *mapaccess(m, k) tmp = tmp op r *mapassign(m, k) = tmp However, this is suboptimal, as we could instead produce just: *mapassign(m, k) op= r One complication though is if "r == 0", then "m[k] /= r" and "m[k] %= r" will panic, and they need to do so *before* calling mapassign, otherwise we may insert a new zero-value element into the map. It would be spec compliant to just emit the "r != 0" check before calling mapassign (see #23735), but currently these checks aren't generated until SSA construction. For now, it's simpler to continue desugaring /= and %= into two map indexing operations. Fixes #23661.
代码结果:
go1.10
:
++ took 10.258130907s
y=y+1 took 10.233823639s
go1.11
:
++ took 7.995184419s
y=y+1 took 10.259916484s
您的问题的一般答案是在代码中简单,明确且显而易见。这样,编译器就可以轻松完成识别常见的可优化模式的任务。