Go map vs C# Dictionary

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

Go map vs C# Dictionary

问题

我写了一个简单的测试来比较Go和C#在并发查找访问方面的性能,并对结果感到惊讶。

这只是一个非常简单的例子,我并不是Go的专家,但测试的目的是在一个map上执行1,000,000次的锁定/检查/添加/解锁操作,因为我只是检查这些函数:

package main

import (
	"fmt"
	"sync"
	"time"
)

var mu sync.Mutex

func main() {
	cache := make(map[int]int, 1000000)
	start := time.Now()

	for i := 0; i < 1000000; i++ {
		mu.Lock()
		if _, ok := cache[i]; ok == false {
			cache[i] = i
		}
		mu.Unlock()
	}

	end := time.Since(start)
	fmt.Println(end)

	var sum int64
	for _, v := range cache {
		sum += int64(v)
	}

	fmt.Println(sum)
}

而在C#中的相同操作(通过LINQPad)如下:

void Main()
{
	var cache = new Dictionary<int, int>(1000000);
	var sw = Stopwatch.StartNew();

	for (var i = 0; i < 1000000; i++)
	{
		lock (cache)
		{
			int d;
			if (cache.TryGetValue(i, out d) == false)
			{
				cache.Add(i, i);
			}
		}
	}

	$"{sw.ElapsedMilliseconds:N0}ms".Dump();

	var sum = 0L;
	foreach (var kvp in cache)
	{
		sum += kvp.Value;
	}
	sum.Dump();
}

我对两个集合的元素求和以确保它们匹配(499,999,500,000),并打印所花费的时间。以下是结果:

  • C#: 56ms
  • Go: 327ms

我已经确认无法初始化map的大小,只能初始化其容量,所以我想知道是否有什么方法可以提高Go map的性能?

在没有map访问的情况下,Go执行1,000,000次锁定/解锁操作需要32ms。

英文:

I wrote a quick and dirty test to check the performance of Go vs C# in the area of concurrent lookup access and was surprised by the results.

It's a very trivial example and I'm no Go expert but the test is simply to perform 1,000,000 lock/check/add/unlock operations on a map, it's only single-threaded because I'm checking just these functions:

package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

var mu sync.Mutex

func main() {
	cache := make(map[int]int, 1000000)
	start := time.Now()

	for i := 0; i &lt; 1000000; i++ {
		mu.Lock()
		if _, ok := cache[i]; ok == false {
			cache[i] = i
		}
		mu.Unlock()
	}

	end := time.Since(start)
	fmt.Println(end)

	var sum int64
	for _, v := range cache {
		sum += int64(v)
	}

	fmt.Println(sum)
}

And the same thing in C# (via LINQPad):

void Main()
{
	var cache = new Dictionary&lt;int, int&gt;(1000000);
	var sw = Stopwatch.StartNew();

	for (var i = 0; i &lt; 1000000; i++)
	{
		lock (cache)
		{
			int d;
			if (cache.TryGetValue(i, out d) == false)
			{
				cache.Add(i, i);
			}
		}
	}

	$&quot;{sw.ElapsedMilliseconds:N0}ms&quot;.Dump();

	var sum = 0L;
	foreach (var kvp in cache)
	{
		sum += kvp.Value;
	}
	sum.Dump();
}

I sum the elements of both collections to ensure they match up (499,999,500,000) and print the time taken. Here are the results:

  • C#: 56ms
  • Go: 327ms

I've checked that it's not possible to initialise the size of a map, just the capacity, so I'm wondering if there's anything I could do to improve the performance of the Go map?

It takes Go 32ms to perform 1,000,000 lock/unlock operations without the map access.

答案1

得分: 6

所以,我想知道是否有任何方法可以改善Go map的性能?

没有,Go基本上没有性能调节选项。

(请注意,Go的map类型是一个非常通用和强大的哈希映射,它使用强密码哈希(如果可能的话)来防止攻击,并强制随机键/迭代顺序。它是“完全通用的”而不仅仅是“一个快速的字典”。)

只是为了完全正确:有一个环境变量GOGC可以“调整”垃圾回收。

英文:

> [S]o I'm wondering if there's anything I could do to improve the performance of the Go map?

No there is not. Go has basically no performance knobs.

(Note that Go's map type is a very general and robust hash map which uses strong cryptographic hashing (if possible) to prevent attacks and forces random key/iteration order. It is "totally general purpose" and not just "a fast dictionary".)

Just to be totally correct: There is the environmental variable GOGC to "tune" GC.

答案2

得分: 4

可能有一件被忽视的事情,将整个练习变成了不可比较的情况:同步。在Go方面,你使用的是在每次访问时都会降到内核级别的Mutex。而在C#方面,你使用的是lock(){},它使用了SpinLock的组合,在需要时才会回退到内核调用。由于你的测试都是在单线程中进行的,C#甚至不会进入内核。

在Go中,不鼓励使用Mutex,应该使用通道进行同步。

几个建议:

  1. 如果你想要对map/dictionary进行基准测试,请移除同步。
  2. 如果你想要对并发性能进行基准测试,请使用正确的结构和范例编写你的测试。

祝好!

英文:

There may be one thing that is overlooked and converts the whole exercise into apples and oranges: synchronization. On Go side you use Mutex which goes down to the kernel on every access. On C# side you use lock(){} that uses a combination of SpinLock and only falls back to kernel calls when needed. Since your tests are performed in a single thread anyways, C# never even goes to kernel.

Use of Mutex is discouraged in Go and channels should be used for synchronization instead.

Couple of suggestions:

  1. Remove synchronization if you want to benchmark map/dictionary by themselves.
  2. Write your tests using correct constructs and paradigms if you like to benchmark concurrent performance.

Cheers!

答案3

得分: 1

我使用Mono编译了你的C#示例,并在OS X上运行它,以消除微软在其Windows实现的Dictionary中可能添加的任何"魔法"。

除非我们忽略了Go的性能技巧,否则C#在这个特定的测试中似乎确实比Go更快:

dict.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;

public class DictionaryTest
{
    public static void Main()
    {
        var cache = new Dictionary<int, int>(1000000);
        var sw = Stopwatch.StartNew();

        for (var i = 0; i < 1000000; i++)
        {
            lock (cache)
            {
                int d;
                if (cache.TryGetValue(i, out d) == false)
                {
                    cache.Add(i, i);
                }
            }
        }

        sw.Stop();

        Console.WriteLine(string.Format("{0}ms", sw.ElapsedMilliseconds));

        var sum = 0L;
        foreach (var kvp in cache)
        {
            sum += kvp.Value;
        }

        Console.WriteLine("Sum: " + sum);
    }
}

如果你已经安装了Mono SDK,你可以使用mcs dict.cs进行编译,并使用mono dict.exe执行。

我运行了几次,平均需要47毫秒,而Go版本平均需要149毫秒。

英文:

I compiled your C# example using Mono, and ran it on OS X, just to neutralize any "magic" Microsoft might have added to its Windows implementation of Dictionary.

It appears that C# is indeed faster than Go for this particular test, unless there is some Go performance trick we are overlooking:

dict.cs

using System;
using System.Collections.Generic;
using System.Diagnostics;

public class DictionaryTest
{
    public static void Main()
    {
        var cache = new Dictionary&lt;int, int&gt;(1000000);
        var sw = Stopwatch.StartNew();

        for (var i = 0; i &lt; 1000000; i++)
        {
            lock (cache)
            {
                int d;
                if (cache.TryGetValue(i, out d) == false)
                {
                    cache.Add(i, i);
                }
            }
        }

        sw.Stop();

        Console.WriteLine(string.Format(&quot;{0}ms&quot;, sw.ElapsedMilliseconds));

        var sum = 0L;
        foreach (var kvp in cache)
        {
            sum += kvp.Value;
        }

        Console.WriteLine(&quot;Sum: &quot; + sum);
    }
}

If you have the Mono SDK installed, you can compile the above with mcs dict.cs and execute with mono dict.exe.

I ran it several times, and it takes an average of 47ms compared to my average 149ms for the Go version.

答案4

得分: 1

我发现,如果我将1000000缩小到100000,Golang的速度将从151.0087毫秒变为10.0005毫秒(乘以15.1),而C#版本的速度将从65毫秒变为9毫秒(乘以7.22),这是否意味着Golang的哈希映射在处理大型映射时存在困难?

我编写了一个简单的Go基准程序,如下所示:

func BenchmarkIntMapGet100(b *testing.B) {
	count := 100
	setupIntMap(b, count)

   	b.ResetTimer()
	for i:=0; i<b.N; i++{
		_, _ = intMap[i%count]
	}
}

我得到了以下结果:

BenchmarkIntMapGet10-4         	100000000	        15.6 ns/op
BenchmarkIntMapGet100-4        	100000000	        17.1 ns/op
BenchmarkIntMapGet1000-4       	50000000	        25.7 ns/op
BenchmarkIntMapGet10000-4      	50000000	        32.3 ns/op
BenchmarkIntMapGet100000-4     	30000000	        39.2 ns/op
BenchmarkIntMapGet1000000-4    	20000000	        67.2 ns/op
BenchmarkIntMapGet10000000-4   	20000000	        82.3 ns/op
英文:

I found if I shink 1000000 to 100000, golang speed would change from 151.0087ms to 10.0005ms (15.1 multiply), while csharp version change from 65ms to 9ms (7.22 multiply) , so it means golang's hashmap has difficulty to handle large map ?

I wrote a simple go benchmark program like this

func BenchmarkIntMapGet100(b *testing.B) {
	count := 100
	setupIntMap(b, count)

   	b.ResetTimer()
	for i:=0; i&lt;b.N; i++{
		_, _ = intMap[i%count]
	}
}

and I got the result

BenchmarkIntMapGet10-4         	100000000	        15.6 ns/op
BenchmarkIntMapGet100-4        	100000000	        17.1 ns/op
BenchmarkIntMapGet1000-4       	50000000	        25.7 ns/op
BenchmarkIntMapGet10000-4      	50000000	        32.3 ns/op
BenchmarkIntMapGet100000-4     	30000000	        39.2 ns/op
BenchmarkIntMapGet1000000-4    	20000000	        67.2 ns/op
BenchmarkIntMapGet10000000-4   	20000000	        82.3 ns/op

huangapple
  • 本文由 发表于 2016年1月28日 15:14:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/35055027.html
匿名

发表评论

匿名网友

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

确定