出于好奇,我写了一些琐碎的基准测试,将golang地图的性能与用作地图的JavaScript(v8 / node.js)对象进行比较,并对其相对性能感到惊讶。 JavaScript对象的执行速度大约是go map的两倍(甚至包括一些较小的性能边缘)!

这是go实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// map.go
package main
import"fmt"
import"time"
func elapsedMillis(t0, t1 time.Time) float64 {
  n0, n1 := float64(t0.UnixNano()), float64(t1.UnixNano())
  return (n1 - n0) / 1e6
}
func main() {
  m := make(map[int]int, 1000000)
  t0 := time.Now()
  for i := 0; i < 1000000; i++ {
    m[i] = i     // Put.
    _ = m[i] + 1 // Get, use, discard.
  }
  t1 := time.Now()
  fmt.Printf("go: %fms
", elapsedMillis(t0, t1))
}

这是JavaScript:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env node
// map.js
function elapsedMillis(hrtime0, hrtime1) {
  var n0 = hrtime0[0] * 1e9 + hrtime0[1];
  var n1 = hrtime1[0] * 1e9 + hrtime1[1];
  return (n1 - n0) / 1e6;
}
var m = {};
var t0 = process.hrtime();
for (var i=0; i<1000000; i++) {
  m[i] = i; // Put.
  var _ = m[i] + 1; // Get, use, discard.
}
var t1 = process.hrtime();
console.log('js: ' + elapsedMillis(t0, t1) + 'ms');

请注意,go实现具有以下几个潜在的性能优势:

  • Go将整数直接映射到整数,而JavaScript会将整数键转换为字符串属性名称。

  • Go使其地图的初始容量等于基准大小,而JavaScript正从其默认容量增长)。

  • 但是,尽管上面列出了潜在的性能优势,但是go map使用似乎只能执行JavaScript对象映射的一半!例如(代表):

    1
    2
    go: 128.318976ms
    js: 48.18517ms

    我在做地图或以某种方式比较苹果和橙子时做了一些明显错误的事情吗?

    我本来希望go map至少也能执行 - 如果不比JavaScript对象好,那就像map一样。这只是go的不成熟的标志(darwin / amd64上的1.4)还是它代表了我缺少的两种语言数据结构之间的一些根本区别?

    [更新]

    请注意,如果您明确使用字符串键(例如分别通过Go和JavaScript中的s := strconv.Itoa(i)var s = ''+i),那么它们的性能大致相当。

    我的猜测是,v8的非常高的性能与该运行时针对其键为连续整数的对象的特定优化有关(例如,通过替换数组实现而不是哈希表)。

    我投票结束,因为这里可能没有什么可看的......

    • 我不确定Go是不是对数字进行散列,我不确定javascript是否也这样。 0 ... 1M之间的连续整数的映射基本上是一个数组。将m初始化为1M int的数组,将执行时间减少到2ms。现在用字符串做这个基准测试,这是一个真正的比较。如果v8的速度也会更快,我也不会感到惊讶。
    • @Not_a_Golfer:ya我有点猜测go可能会直接使用int键而不是散列它们;我想我可以阅读哈希表实现来找出答案。足够有趣 - 如果我在两种语言中使用字符串键(使用s:=strconv.Itoa(i)var s=''+i),那么它们的表现大致相同;虽然在js中使用数字键仍然快得多。这里有许多谜团......
    • "JavaScript会将整数键转换为字符串属性名称。" - 不,不。即使它们是等价的,也不是发动机的作用。它很快意识到你正在滥用数组的对象,并将使用下面的数组。
    • @Bergi:是的,这可以解释很多(参见我对@Not_a_Golfer的评论)。看起来这个愚蠢的基准可能会弯曲v8引擎的优化部分......
    • @ Bergi的解释是有道理的。你的Go是惯用的,我高度怀疑任何脚本语言实际上都可以执行它。
    • @maerics当我们将两者都与字符串进行比较时,我并不感到惊讶,两者的地图实现大致相同。 V8是高度优化的,你知道,你基本上是在编译语言中对两个哈希表实现进行基准测试。
    • 顺便说一下,使用testing软件包进行基准测试更容易,更好。
    • 是的,看起来这里没什么可看的 - 请投票结束......

    您的基准测试是合成的,就像任何基准测试一样。 只是为了好奇的尝试

    1
    for i := 0; i < 1000000; i += 9 {

    在Go实施中。 你可能会感到惊讶。

    • 嗯,是的,有趣。 通过一个以上的增量似乎打败了我怀疑逐个增加遇到的v8优化(例如使用数组实现而不是哈希表)。
    • Go map实际上是作为哈希表(没有任何数组)实现的,并且只需将一个键递增一个,你只需要尝试对碰撞很多的病理数据集进行建模。