Skip to content

maps: make Equal O(1) for pointer-identical maps #71388

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

Open
dsnet opened this issue Jan 22, 2025 · 7 comments
Open

maps: make Equal O(1) for pointer-identical maps #71388

dsnet opened this issue Jan 22, 2025 · 7 comments
Labels
LibraryProposal Issues describing a requested change to the Go standard library or x/ libraries, but not to a tool NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance

Comments

@dsnet
Copy link
Member

dsnet commented Jan 22, 2025

Go version

go1.23

Output of go env in your module/workspace:

GOARCH=amd64
GOOS=linux

What did you do?

Ran this benchmark:

var m = func() map[int]string {
	m := make(map[int]string)
	for i := range 1000000 {
		m[i] = strconv.Itoa(i)
	}
	return m
}()

func Benchmark(b *testing.B) {
	for range b.N {
		maps.Equal(m, m)
	}
}

What did you see happen?

Benchmark-32    	      75	  15531818 ns/op

What did you expect to see?

Benchmark-32    	1000000000	         0.1882 ns/op

As a trivial test, Equal could use unsafe to check whether the map pointers are identical.
In which case, the maps are equal.

Unfortunately, we need to skip this optimization if the key and value types could possibly contain floating point values, which violate the reflexive property of equality for NaNs.

@gabyhelp
Copy link

Related Issues

Related Documentation

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

@gabyhelp gabyhelp added the LibraryProposal Issues describing a requested change to the Go standard library or x/ libraries, but not to a tool label Jan 22, 2025
@mateusz834
Copy link
Member

mateusz834 commented Jan 22, 2025

Unfortunately, we need to skip this optimization if the key and value types could possibly contain floating point values, which violate the reflexive property of equality for NaNs.

What if a map maintained a hasNaN bool ? Then it is just return !m.hasNaN && !m2.hasNaN && m == m2.

@adonovan
Copy link
Member

What if a map maintained a hasNaN bool ? Then it is just return !m.hasNaN && !m2.hasNaN && m == m2.

The NaN could be buried within the tree of aggregate (array/struct) values of the key, and the information that hashing encountered a NaN is not returned by the hash algorithm. Annoyingly, for Equal, one must also recursively search the values for NaNs.

Simpler to go by types. The rtype for the keys and values could record a statically computed hasFloat bit so that no recursion is required at runtime.

@ianlancetaylor
Copy link
Member

The old non-Swiss map type recorded ReflexiveKey as a flag on the map type. The new Swiss map type has a flags field. We could copy the old flag code to the new implementation, and use that.

But does this case come up a lot in practice?

@dsnet
Copy link
Member Author

dsnet commented Jan 23, 2025

But does this case come up a lot in practice?

In my use-case, I was doing a copy-on-write mechanism where I needed to detect if something changed. It is quite likely that I could be comparing a map to itself.

@zigo101
Copy link

zigo101 commented Jan 23, 2025

The same is for slices.Equal.

@dsnet
Copy link
Member Author

dsnet commented Jan 23, 2025

Fortunately for slices, it is possible for users to perform this optimization themselves with something like:

len(x) > 0 && len(x) == len(y) && &x[0] == &y[0]

This doesn't handle NaNs "correctly", but honestly most usages do not care.

@cagedmantis cagedmantis added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jan 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
LibraryProposal Issues describing a requested change to the Go standard library or x/ libraries, but not to a tool NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. Performance
Projects
None yet
Development

No branches or pull requests

7 participants