Golang与JavaScript(v8/node.js)的映射性能比较

huangapple go评论102阅读模式
英文:

Golang vs JavaScript (v8/node.js) map performance

问题

出于好奇,我编写了一些简单的基准测试,比较了使用作为映射的golang映射和JavaScript(v8/node.js)对象的性能,并对它们的相对性能感到惊讶。JavaScript对象的性能似乎大约是go映射的两倍(即使包括一些go的轻微性能优势)!

这是go的实现:

// 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\n", elapsedMillis(t0, t1))
}

这是JavaScript的实现:

#!/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的实现在以下几个方面可能具有轻微的性能优势:

  1. Go直接将整数映射到整数,而JavaScript会将整数键转换为字符串属性名。
  2. Go的映射使用的初始容量等于基准大小,而JavaScript则从其默认容量开始增长。

然而,尽管上述列出了潜在的性能优势,但go映射的使用速度似乎只有JavaScript对象映射的一半!例如(代表性):

go: 128.318976ms
js: 48.18517ms

我是否在go映射方面犯了一些明显的错误,或者在比较时出现了不可比较的情况?

我本来期望go映射的性能至少与JavaScript对象映射一样好,甚至更好。这只是go的不成熟(在darwin/amd64上为1.4)的一个迹象,还是代表了我所忽略的两种语言数据结构之间的一些根本差异?

[更新]

请注意,如果显式使用字符串键(例如通过strconv.Itoa(i)和Go中的var s = ''+i),它们的性能大致相当。

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

我投票关闭,因为可能没有什么可看的...

英文:

Out of curiosity I wrote some trivial benchmarks comparing the performance of golang maps to JavaScript (v8/node.js) objects used as maps and am surprised at their relative performance. JavaScript objects appear to perform roughly twice as fast as go maps (even including some minor performance edges for go)!

Here is the go implementation:

<!-- language: lang-go -->

// map.go
package main
import &quot;fmt&quot;
import &quot;time&quot;
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 &lt; 1000000; i++ {
    m[i] = i     // Put.
    _ = m[i] + 1 // Get, use, discard.
  }
  t1 := time.Now()
  fmt.Printf(&quot;go: %fms\n&quot;, elapsedMillis(t0, t1))
}

And here is the JavaScript:

<!-- language: lang-javascript -->

#!/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&lt;1000000; i++) {
  m[i] = i; // Put.
  var _ = m[i] + 1; // Get, use, discard.
}
var t1 = process.hrtime();
console.log(&#39;js: &#39; + elapsedMillis(t0, t1) + &#39;ms&#39;);

Note that the go implementation has a couple of minor potential performance edges in that:

  1. Go is mapping integers to integers directly, whereas JavaScript will convert the integer keys to string property names.

  2. Go makes its map with initial capacity equal to the benchmark size, whereas JavaScript is growing from its default capacity).

However, despite the potential performance benefits listed above the go map usage seems to perform at about half the rate of the JavaScript object map! For example (representative):

go: 128.318976ms
js: 48.18517ms

Am I doing something obviously wrong with go maps or somehow comparing apples to oranges?

I would have expected go maps to perform at least as well - if not better than JavaScript objects as maps. Is this just a sign of go's immaturity (1.4 on darwin/amd64) or does it represent some fundamental difference between the two language data structures that I'm missing?

[Update]

Note that if you explicitly use string keys (e.g. via s := strconv.Itoa(i) and var s = &#39;&#39;+i in Go and JavaScript, respectively) then their performance is roughly equivalent.

My guess is that the very high performance from v8 is related to a specific optimization in that runtime for objects whose keys are consecutive integers (e.g. by substituting an array implementation instead of a hashtable).

I'm voting to close since there is likely nothing to see here...

答案1

得分: 4

你的基准测试有点合成,就像任何基准测试一样。只是为了好奇,尝试在Go实现中运行以下代码:

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

也许你会感到惊讶。

英文:

Your benchmark is synthetic a bit, just like any benchmarks are. Just for curious try

for i := 0; i &lt; 1000000; i += 9 {

in Go implementation. You may be surprised.

huangapple
  • 本文由 发表于 2015年4月21日 05:44:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/29758980.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定