Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

runtime: reduce duplicate work by merging map operations #70837

Open
prattmic opened this issue Dec 13, 2024 · 4 comments
Open

runtime: reduce duplicate work by merging map operations #70837

prattmic opened this issue Dec 13, 2024 · 4 comments
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance
Milestone

Comments

@prattmic
Copy link
Member

default := ...
v, ok := m[k]
if !ok {
  m[k] = default
  v = default
}
// Use v

This is a mapaccess2 followed (conditionally) by a mapassign. The beginning of mapassign is identical to mapaccess2: hashing the key and looking for an existing entry (which won't be found).

In theory, the compiler could detect this pattern and use a special call for the map assignment that avoids the duplicate work (this would effectively be internal/runtime/maps.(*table).uncheckedPutSlot in today's implementation).

Another pattern is iteration plus delete:

for k := range m {
  if iDontLikeIt {
    delete(m, k)
  }
}

It could be optimized in a similar way. This is also maps.DeleteFunc which could be directly specialized more easily than adding new compiler optimizations.

@prattmic prattmic added Performance NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. compiler/runtime Issues related to the Go compiler and/or runtime. labels Dec 13, 2024
@prattmic prattmic added this to the Backlog milestone Dec 13, 2024
@prattmic
Copy link
Member Author

The paper Representing Data Collections in an SSA Form describes adding high-level collection operations to compiler SSA to aid in optimizations, which I find a fascinating direction.

@prattmic prattmic mentioned this issue Dec 13, 2024
5 tasks
@mvdan
Copy link
Member

mvdan commented Dec 14, 2024

I would be very happy if this were implemented! Previously, see #5147, and in particular, #5147 (comment) which describes how this would speed up declaring flags.

@mateusz834
Copy link
Member

mateusz834 commented Dec 14, 2024

In theory, the compiler could detect this pattern and use a special call for the map assignment that avoids the duplicate work (this would effectively be internal/runtime/maps.(*table).uncheckedPutSlot in today's implementation).

It does not have to be internal/runtime/maps.(*table).uncheckedPutSlot. It can be even smarter. Want to point to Zig std lib hash map implementation, zig is a low level language (we don't want obviously to expose this kind of low level stuff), but the optimizations can be made even further, see:

https://ziglang.org/documentation/master/std/#std.hash_map.HashMap.getOrPut

If key exists this function cannot fail. If there is an existing item with key, then the result's Entry pointers point to it, and found_existing is true. Otherwise, puts a new item with undefined value, and the Entry pointers point to it. Caller should then initialize the value (but not the key).

It returns a struct https://ziglang.org/documentation/master/std/#std.hash_map.HashMapUnmanaged.GetOrPutResult

In Go land it would look something like:

type GetOrPutResult[K comparable, V any] struct {
	Key   *K
	Val   *V
	Found bool
}

func GetOrPut[K comparable, V any](m map[K]V, key K) GetOrPutResult[K, V] {
	return GetOrPutResult[K, V]{ /*....*/ }
}

func main() {
	m := make(map[string]string)
	r := GetOrPut(m, "somekey")
	if !r.Found {
		*r.Val = "val"
	}
}

With GetOrPut the caller is responsible to set the value in case r.Found == false.

This works because while scanning for a given key in a swisstable, we also know the place where we would have inserted the element with the same key (at least in Zig, not sure whether this would be possible in Go), thus we can return a pointer to it and there is no need to do that work again in uncheckedPutSlot.

@mknyszek
Copy link
Contributor

Related: #5147

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler/runtime Issues related to the Go compiler and/or runtime. NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance
Projects
Development

No branches or pull requests

4 participants