6

To know a key k exist in a map M1[k]v is very straightforward in Go.

if v, ok := M1[k]; ok {
    // key exist
}

'v': a value of a non-pointer type.

If v is large, To just check if a particular key exists using the above method is not efficient as it will load the value v in the memory(even if I use a blank identifier _ in the place of v as per my understanding and please correct me if my understanding is wrong here).

Is there an efficient way in which we can check if a key is present in a Map(without reading/or the value is allocated in the memory)?

I am thinking to create a new map M2[k]bool to store the information and make an entry in M2 each time I insert something in M1.

4
  • 2
    Use if _, ok := M1[k]; ok { }. If you use the blank identifier, the value will not be "loaded". Commented Jan 2, 2022 at 17:03
  • 1
    if a particular key exists using the above method is not efficient as it will load the value v in the memory [...] What makes you believe that? Commented Jan 2, 2022 at 17:04
  • 1
    See mapaccess2_fat and friends. The runtime returns a pointer to the map value. The pointed to value is only copied if used by the application. Commented Jan 2, 2022 at 17:08
  • @PenélopeStevens Thanks for the above link. Commented Jan 2, 2022 at 17:26

1 Answer 1

8

Use if _, ok := M1[k]; ok { }. If you use the blank identifier, the value will not be "loaded".

Let's write benchmarks to test it:

var m = map[int][1_000_000]int64{
    1: {},
}

func BenchmarkNonBlank(b *testing.B) {
    for i := 0; i < b.N; i++ {
        if v, ok := m[1]; ok {
            if false {
                _ = v
            }
        }
    }
}

func BenchmarkBlank(b *testing.B) {
    for i := 0; i < b.N; i++ {
        if _, ok := m[1]; ok {
            if false {
                _ = ok
            }
        }
    }
}

Running go test -bench ., the output is:

BenchmarkNonBlank-8         1497            763278 ns/op
BenchmarkBlank-8        97802791                12.09 ns/op

As you can see, using the blank identifier, the operation takes about 10 ns. When we assign the value to a non-blank identifier, it's almost 1 ms (almost a hundred thousand times slower) when the value type has a size of around 8 MB.

Sign up to request clarification or add additional context in comments.

5 Comments

The behaviour is same for sync.Map.Load() as well?. I went through the official docs pkg.go.dev/sync#Map.Load and it says nothing about allocation in case we use a blank identifier.
@PrakashPandey That's a completely different story, as sync.Map is not generic (like the builtin map type), and hence stored values must be wrapped in interface{}. Also Map.Load() is a function call, it must return the values regardless if you use them or not. My answer shows how to benchmark it, modify my benchmark to test it yourself.
I think I got my answer. as map is built in and sync. Map is just a struct + Map.Load() is a normal func call so it will always allocative the value in the memory. And thanks for sharing the above benchmark code.
@PrakashPandey The call sync.Map.Load() copies an interface{}. An interface{} value is two machine words.
@PenélopeStevens Thanks. If someone wants to know more about interface{} value two machine words, here is a nice explanation in another StackOverflow post: stackoverflow.com/a/23148998/4408364

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.