-
Notifications
You must be signed in to change notification settings - Fork 18k
proposal: spec: make byte array types ordered #61004
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
Comments
The standard library already has Would adding a new operator allow anything that can't already be achieved using that function with full-coverage slices of both arrays? Would the answer change if there were also a (I understand that these functions take byte slices rather than byte arrays. If that is the crucial difference that you're motivated by then I'd love to hear more about why that distinction is important!) |
Looks like a dup of #39355. |
A (very specific) subset, as the proposal seems held down by what about |
“B-tree implementations now need redundant code/types to make fixed-width keys work efficiently, i.e., |
If I understand that quote correctly, I don't agree with it. The answer is that a B-tree implementation should always use a comparison function. If you want to use a type that is already ordered, then the comparison function is |
The problem is that we want to use a type that is not already ordered @ianlancetaylor. Is there any reason why this needs to be v2? |
We generally mark all language changes as v2. |
Sorry, I made a mistake. I should have said that for a byte slice the comparison function you should use is func(a, b [N]byte) int { return bytes.Compare(a[:], b[:]) } |
I understand that we can make a comparison function for arrays, and another one for ordered types, yet we can't do one for both now, which results in duplicate code. The alternative is an interface, or a lambda. Both kill the performance. |
Sorry @pascaldekloe .... I did read the writeup several times but for some reason I had a mental block on the statement about the B-tree use-case. If you have a specific example of some code that is currently inconvenient to write that would be improved by this proposal, it would help to include a source snippet in the proposal to help make the need clearer. It sounds like you are trying to make something that is generic over type Map[K, V any] struct {
less func (i, j K) bool
// (etc)
}
func NewMapOrdered[K cmp.Ordered, V any]() Map[K, V] {
return Map[K, V]{
less: cmp.Less[K],
}
}
func NewMapByteSlice[V any]() Map[[]byte, V] {
return Map[K, V]{
less: bytes.Less, // (assuming there were a bytes.Less function)
}
} However, I would worry that the indirect call overhead for calling the That aside, I think the main question left in my mind is whether comparing byte arrays is common enough to warrant the additional language complexity of a weird exception for one particular array element type. Most Go programmers don't start by reading the specification, and so I wonder how they would discover that byte arrays in particular are ordered while no other array type is, and if they were to find that out via experimentation would they correctly deduce what the rule is? I think it's fair to say that most Go programmers aren't regularly writing b-tree implementations, and so it might help to explore what other use-cases this could potentially meet that might be more general. |
Pick a piece of code which compares generics @apparentlymart, say this map, and then try to make it work for both |
I don't see why that would be. The inliner gets better with every release, and seems fully capable of handling the lambda case. If performance is the main argument in favor of this language change, then we should invest effort into improving performance. |
Out of curiosity I took the I'm not familiar enough with this code to know where the hot paths are but from casual inspection of the |
var tokens = []string{"the", "quick", "brown", "fox", "jumps", "over", "the", "…"}
func BenchmarkSlicesEqual(b *testing.B) {
same := make([]string, len(tokens))
copy(same, tokens)
b.Run("generics", func(b *testing.B) {
for i := 0; i < b.N; i++ {
slices.Equal(tokens, same)
}
})
b.Run("func", func(b *testing.B) {
for i := 0; i < b.N; i++ {
slices.EqualFunc(tokens, same, func(a, b string) bool {
return a == b
})
}
})
} Looking at the assembler is the way to go @apparentlymart. 👍 |
We aren't going to specialize the language for byte arrays (or byte slices; it's not immediately clear what this proposal is after). Any language extension we make here should apply to all arrays (or slices) with ordered element types. If this is about slices, note that we've always decided that slices are not comparable, meaning that you can't use Generic containers should always use an explicit comparison function, so we don't need ordered arrays in order to use containers. If it's important to make Therefore, this is a likely decline. Leaving open for three weeks for final comments. |
We do already do this. (Not by using |
Thanks for the clear expectation management Ian. That helps. 🙂 I have pointed out 3 reasons in this issue: (1) performance, (2) code duplication, and (3) consistency.
“ The logic for comparing byte arrays is fully intuitive.” The irony (3) is that a string conversion does compare—with an underlaying array. That slice reasoning is pretty lame anyway. Go can absolutely declare how it compares/orders slices and get it done so. Channels are comparable now, and their comparison is way more "ambiguous" than any slices version could.
I have clearly demonstrated how they are too slow (1). More generally, all If the Go team puts its priority somewhere else then that is OK of course. Just own it. All those tricks to delay, confuse and obscure are not doing anyone a favour. |
An argument based on performance is only useful if we think that the performance can't be improved. We certainly think that the performance here can be improved and in fact there is an effort right now to improve the inliner. |
No change in consensus. |
For two byte arrays with the same size,
a
andb
,string(a[:]) < string(b[:])
works, yeta < b
does not.The logic for comparing byte arrays is fully intuitive.
Use-Case
B-tree implementations now need redundant code/types to make fixed-width keys work efficiently, i.e.,
type Map[K Orderable, V any]
andtype ArrayKeyMap[K Arrays, V any]
. Interface overhead would kill the performance.Workaround
Suboptimal instructions remain even with the risks of
unsafe.String(&array[0], len(array))
as the size context gets lost.History
Bytes only, rather than the full type range, prevents quite a few implementation issues.
Extra
Array inclusion may play nicely in the new
cmp
package. Generics does not support matching on any-size arrays yet, so it may start with a long list of~[2]byte | ~[3]byte | …
instead.The text was updated successfully, but these errors were encountered: