英文:
Do Go implementations store the length of a map?
问题
map
的长度是存储在某个地方还是每次调用len(my_map)
时都会计算?
语言规范对于map
的说明并不是很清楚:
map
元素的数量被称为其长度。对于一个map
m
,可以使用内置函数len
来获取其长度,并且在执行过程中可能会发生变化。可以使用赋值语句添加元素,并使用索引表达式检索元素;可以使用内置函数delete
删除元素。
在“长度和容量”部分,我们看到了这样的说明:
如果
s
是一个string
常量,表达式len(s)
是常量。如果s
的类型是array
或pointer
到array
,并且表达式s
不包含通道接收或(非常量)函数调用,则表达式len(s)
和cap(s)
是常量;在这种情况下,s
不会被求值。否则,len
和cap
的调用不是常量,s
会被求值。
因此,它告诉我们s
不是常量并且会被求值,但它没有说明它是否像slice
类型那样作为存储的值进行查找。
英文:
Does a map
have its length stored somewhere or is it calculated each time we call len(my_map)
?
The language spec shows this for maps, which doesn't really help:
>The number of map
elements is called its length. For a map m
, it can be discovered using the built-in function len
and may change during execution. Elements may be added during execution using assignments and retrieved with index expressions; they may be removed with the delete built-in function.
Under the "Length and capacity" section we see this:
> The expression len(s)
is constant if s
is a string
constant. The expressions len(s)
and cap(s)
are constants if the type of s
is an array
or pointer
to an array
and the expression s
does not contain channel receives or (non-constant) function calls; in this case s
is not evaluated. Otherwise, invocations of len
and cap
are not constant and s
is evaluated.
So it tells us that s
isn't constant and is evaluated but it doesn't state if it's looked up as a stored value like they do with the slice
type.
答案1
得分: 19
我还没有检查过这些来源,但我写了一个快速的基准测试。
它测试了4个映射,其中2个键是int
,2个键是string
。
var m1 = make(map[int]int) // 键的类型为int,len(m1) = 10
var m2 = make(map[int]int) // 键的类型为int,len(m2) = 1000000
var s1 = make(map[string]int) // 键的类型为string,len(s1) = 10
var s2 = make(map[string]int) // 键的类型为string,len(s2) = 1000000
"Small"映射有10个元素,"large"映射有一百万个元素。映射的填充方式如下:
func init() {
for i := 0; i < 10; i++ {
m1[i] = i
s1[strconv.Itoa(i)] = i
}
for i := 0; i < 1000000; i++ {
m2[i] = i
s2[strconv.Itoa(i)] = i
}
}
基准测试函数的样子如下:
func BenchmarkSmallIntMap(b *testing.B) {
for i := 0; i < b.N; i++ {
for j := 0; j < 1000; j++ {
_ = len(m1) + len(m1) + len(m1) + len(m1) + len(m1) + len(m1)
}
}
}
其他的测试函数类似,只是使用了m2
、s1
和s2
。以下是测试结果:
BenchmarkSmallIntMap 1000000 2085 ns/op
BenchmarkLargeIntMap 1000000 2087 ns/op
BenchmarkSmallStringMap 1000000 2087 ns/op
BenchmarkLargeStringMap 1000000 2086 ns/op
所有的结果都相同,这基本上说明len(m)
的执行时间与映射的大小(键值对的数量)无关,这表明映射的长度是被存储而不是在调用时进行"计数"。
如果感兴趣,这里是完整的测试源代码:Go Playground。
twotwotwo检查了源代码,长度是被存储的。
英文:
I haven't checked the sources, but I wrote a quick benchmark.
It tests 4 maps, 2 where keys are int
, 2 where keys are string
.
var m1 = make(map[int]int) // key is of type int, len(m1) = 10
var m2 = make(map[int]int) // key is of type int, len(m2) = 1000000
var s1 = make(map[string]int) // key is of type string, len(s1) = 10
var s2 = make(map[string]int) // key is of type string, len(s2) = 1000000
"Small" maps have 10 elements, "large" maps have a million elements. Maps are populated like this:
func init() {
for i := 0; i < 10; i++ {
m1[i] = i
s1[strconv.Itoa(i)] = i
}
for i := 0; i < 1000000; i++ {
m2[i] = i
s2[strconv.Itoa(i)] = i
}
}
A benchmark function looks like this:
func BenchmarkSmallIntMap(b *testing.B) {
for i := 0; i < b.N; i++ {
for j := 0; j < 1000; j++ {
_ = len(m1) + len(m1) + len(m1) + len(m1) + len(m1) + len(m1)
}
}
}
All the others are similar, but using m2
, s1
and s2
obviously. Here are the results:
BenchmarkSmallIntMap 1000000 2085 ns/op
BenchmarkLargeIntMap 1000000 2087 ns/op
BenchmarkSmallStringMap 1000000 2087 ns/op
BenchmarkLargeStringMap 1000000 2086 ns/op
All are the same which pretty much tells that execution time of len(m)
does not depend on the map size (number of key-value pairs), which suggests that map length is stored and not "counted" when called.
If interested, here is the complete test source: Go Playground.
And twotwotwo checked the sources, the length is stored.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论