Last active
May 10, 2024 18:30
-
-
Save davecheney/3be245c92b61e5045f75 to your computer and use it in GitHub Desktop.
Which is faster ? map[string]bool or map[string]struct{} ?
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package bm | |
import ( | |
"testing" | |
) | |
var mb = map[string]bool{ | |
"alpha": true, | |
"beta": true, | |
"gamma": true, | |
"delta": true, | |
"epsilon": true, | |
"zeta": true, | |
"eta": true, | |
"theta": true, | |
"iota": true, | |
"kappa": true, | |
"lambda": true, | |
"mu": true, | |
"nu": true, | |
"xi": true, | |
"omicron": true, | |
"pi": true, | |
"rho": true, | |
"sigma": true, | |
"tau": true, | |
"upsilon": true, | |
"phi": true, | |
"chi": true, | |
"psi": true, | |
"omega": true, | |
} | |
var ms = map[string]struct{}{ | |
"alpha": struct{}{}, | |
"beta": struct{}{}, | |
"gamma": struct{}{}, | |
"delta": struct{}{}, | |
"epsilon": struct{}{}, | |
"zeta": struct{}{}, | |
"eta": struct{}{}, | |
"theta": struct{}{}, | |
"iota": struct{}{}, | |
"kappa": struct{}{}, | |
"lambda": struct{}{}, | |
"mu": struct{}{}, | |
"nu": struct{}{}, | |
"xi": struct{}{}, | |
"omicron": struct{}{}, | |
"pi": struct{}{}, | |
"rho": struct{}{}, | |
"sigma": struct{}{}, | |
"tau": struct{}{}, | |
"upsilon": struct{}{}, | |
"phi": struct{}{}, | |
"chi": struct{}{}, | |
"psi": struct{}{}, | |
"omega": struct{}{}, | |
} | |
var bb bool | |
func BenchmarkMapBool(B *testing.B) { | |
var b bool | |
for i := 0; i < B.N; i++ { | |
b = mb["alpha"] | |
b = mb["gamma"] | |
b = mb["mu"] | |
b = mb["pi"] | |
b = mb["omega"] | |
} | |
bb = b | |
} | |
func BenchmarkMapStruct(B *testing.B) { | |
var b bool | |
for i := 0; i < B.N; i++ { | |
_, b = ms["alpha"] | |
_, b = ms["gamma"] | |
_, b = ms["mu"] | |
_, b = ms["pi"] | |
_, b = ms["omega"] | |
} | |
bb = b | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
### update ### Cyrill Schumacher pointed out that my first attempt | |
was incorrect as I was using the mb not the ms map in the second | |
test. The updated results are below. | |
% go test -bench=. -benchtime=10s | |
testing: warning: no tests to run | |
PASS | |
BenchmarkMapBool 100000000 127 ns/op | |
BenchmarkMapStruct 100000000 138 ns/op | |
ok bm 26.802s | |
% go test -bench=. -benchtime=10s | |
testing: warning: no tests to run | |
PASS | |
BenchmarkMapBool 100000000 127 ns/op | |
BenchmarkMapStruct 100000000 133 ns/op | |
ok bm 26.300s | |
% go test -bench=. -benchtime=10s | |
testing: warning: no tests to run | |
PASS | |
BenchmarkMapBool 100000000 128 ns/op | |
BenchmarkMapStruct 100000000 142 ns/op | |
ok bm 27.383s | |
% go test -bench=. -benchtime=10s | |
testing: warning: no tests to run | |
PASS | |
BenchmarkMapBool 100000000 110 ns/op | |
BenchmarkMapStruct 100000000 128 ns/op | |
Conclusion: they are effectively the same, use whichever one | |
makes your code clearer. There is a small advantage to the | |
map[string]bool type, but the difference is below the variance | |
in the test runs. It is also worth noting that the map[string]struct{} | |
version is considered to consume less memory* but this was not | |
tested in this benchmark. | |
*you're welcome to extend this benchmark to prove/disprove this. |
Hm, nope, it's not that clever: https://gist.github.com/mipearson/79226b266ab901fa74e4
To the memory discussion:
package main
import (
"fmt"
"unsafe"
)
func main() {
variant1 := make(map[string]bool)
variant2 := make(map[string]struct{})
for i := 0; i < 1<<16; i++ {
key := fmt.Sprintf("%v", i)
variant1[key] = true
variant2[key] = struct{}{}
}
size1 := unsafe.Sizeof(variant1)
size2 := unsafe.Sizeof(variant2)
for k, v := range variant1 {
size1 += unsafe.Sizeof(k)
size1 += unsafe.Sizeof(v)
}
for k, v := range variant2 {
size2 += unsafe.Sizeof(k)
size2 += unsafe.Sizeof(v)
}
fmt.Printf("bool variant : %v bytes\n", size1)
fmt.Printf("struct variant: %v bytes\n", size2)
// bool variant : 1114120 bytes
// struct variant: 1048584 bytes
}
@trusch thank you for this. size2 += unsafe.Sizeof(v)
will always evaluate to size2 += 0
. If you subtract the key sizes it will show that the struct variant allocates 0 extra bytes for the values.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I would have thought that every
b =
assignment except the last would have been optimised out by the compiler. I see you've added thebb = b
to ensure that the whole thing doesn't just get optimised out.