46

The "Map types" section of the go language specification describes the interface and general usage of map types and the "Go maps in action" post on The Go Blog casually mentions hash tables and "fast lookups, adds, and deletes".

The current runtime/map.go source code describes its implementation as a hashtable (which are typically amortized O(1)); however, I don't see any guarantee of performance characteristics (such as Big O performance) in the language specification or other materials.

Does the go language make any performance guarantees (e.g. constant-time insertion/lookup/deletion) for map types or only interface guarantees? (Compare to the Java language where interfaces and implementations are clearly separate.)

kubanczyk
  • 5,184
  • 1
  • 41
  • 52
maerics
  • 151,642
  • 46
  • 269
  • 291
  • Relevant, check out this page: [Issue 3885: profile and tune map code](https://github.com/golang/go/issues/3885) ([old link](https://code.google.com/p/go/issues/detail?id=3885)) – icza Apr 16 '15 at 14:56
  • Hashing isn't O(1), eg. for strings. – Paul Hankin Apr 16 '15 at 22:57

3 Answers3

46

The language reference doesn't make explicit guarantees about the performance of maps. There's an implicit expectation that maps perform like you expect hash-tables to perform. I don't see how a performance guarantee would avoid being either vaguely specified or inaccurate.

Big-O complexity is a poor way to describe run-times for maps: practically speaking the actual clock time is relevant and complexity isn't. Theoretically, maps with keys from finite domains (such as ints) are trivially O(1) in space and time, and maps with keys with infinite domains (such as strings) require hashing and the specifics of equality testing to be included in the cost, which makes inserts and lookups best case O(N log N) on average (since the keys have to be at least O(log N) in size on average to construct a hash table with N entries. Unless you get these details right in the specification it's going to be inaccurate, and the benefit of getting it right isn't clearly worth it.

To provide guarantees about actual run-time rather than complexity it'd be also be difficult: there's a wide range of target machines, as well as the confounding problems of caching and garbage collection.

Paul Hankin
  • 54,811
  • 11
  • 92
  • 118
  • "lookups O(N log N) on average" this is completely utterly wrong. lookups are O(1) on a properly loaded hashmap. If somehow a hash function is so terrible and unlucky that all the keys would be in one bucket, it'd become O(n) which never happens practically in go. Please remember N is the number of elements. – Laurent Demailly Feb 26 '23 at 18:59
  • @LaurentDemailly You are using a statement about the average number of comparisons of a hashtable lookup (which is O(1) - I agree), but my answer is suggesting that "number of comparisons" is a choice of performance measure, which is difficult or at least murky to apply to actual (clock) runtime of hashmaps. If you have N things in a hashmap, they each need to have ~log(n) bits for them all to be different, so hashing and comparisons times should grow at least like log(N). Technicalities maybe, but they make accurate statements suitable for a lang reference difficult. – Paul Hankin Feb 27 '23 at 07:41
  • Even if the constant part of k O(1) would somewhat grow by log(n), that's still O(1) / negligible compared to N. And in any case would have nothing to do with O(N log N). Please correct your answer as people are repeating what they see here without understanding. – Laurent Demailly Feb 27 '23 at 17:39
10

To be precise hash tables performance is O(1 + n/k) to resolve collisions, where n/k refer to load-factor. Go spec declare maps as non-restrictive in keys quantity. So they need dynamic resizing with partly rehashing to keep load-factor when growing or shrinking. This means near O(1) can be easily achieved for lookup in constant size maps, but can't even theoretically be guaranteed for massive insertion or deletions. Such operations want reallocation, partial rehashing and possible garbage collections to keep load-factor and reasonable memory usage.

Uvelichitel
  • 8,220
  • 1
  • 19
  • 36
  • Thanks for the detailed information. I suppose what I'm looking for is documented guarantees about even general characteristics, e.g. logarithmic-time vs constant-time for various operations. – maerics Apr 16 '15 at 22:32
  • As you can read in source // When the hashtable grows, we allocate a new array // of buckets TWICE AS BIG – Uvelichitel Apr 16 '15 at 22:41
  • So you can assume logarithmic-time when sequentially insert N items in newly map, to my mind. And then near O(1) for lookup – Uvelichitel Apr 16 '15 at 22:48
  • And of course, don't forget that you can provide a sizing hint to `make` for maps to skip some/all of those re-allocations when you know the size (or approximate size). – Dave C Apr 16 '15 at 23:24
8

From what I can see, the Go Programming Language Specification does not make any performance guarantees for map types. Hash tables in general are O(1) for inserting, looking up and deleting data; we can assume that this is also true for Go's maps.

If you are worried about map performance, you can always benchmark your code on different loads and decide, whether to use Go's maps or some other data structure.

Ainar-G
  • 34,563
  • 13
  • 93
  • 119
  • 10
    +1, nitpick: [_amortized_](http://www.cs.cornell.edu/courses/cs3110/2011sp/lectures/lec20-amortized/amortized.htm) O(1) – thwd Apr 16 '15 at 15:09
  • Thanks for the answer! I guess I'm trying to see if we can only *assume* that we'll get amortized `O(1)` performance for map operations or if there is (or will be) any *documented guarantees*. – maerics Apr 16 '15 at 15:22