Skip to content

proposal: make array types ordered when their element type is ordered #39355

Closed
@martisch

Description

@martisch

According to the Go spec array types are comparable but not ordered. Which is odd for e.g. [...]byte array values since strings are ordered and comparable.

As arrays values dont have a capacity and the same number of elements for the same type the problems of having multiple ways to consistently define an ordering or comparable with or without taking capacity into account such as for slices is not the case for arrays types. (#38377, #23725)

I propose to make array types ordered if their element types are ordered so it is possible to use comparison operators like < on two array values.

Semantics

Array values are ordered if values of the array element type are ordered.
An array a is less than an array b if there exists and index i with a[i] < b[i] and there is no index j with j < i and a[i] != b[i].

Note: This does not require the array element values to be sorted. Just for the type of the elements having a defined sort ordering according to the Go specification. e.g. int, uint8, float64... . Arrays of type [2]bool or [3]interface{}wont be orderable.

Examples

func lessVector(a,b [16]byte) bool {
  return a < b
}

Or to avoid a copy:

func lessVector(a,b *[16]byte) bool {
  return *a < *b
}

For the above the compiler could then optimise this to an inlined 128bit comparison on e.g. amd64.

What are the current alternatives that work and have the same semantics?

For byte arrays we have:

func lessVector(a,b [16]byte) bool {
  return string(a[:]) < string(b[:])
}
func lessVector(a,b [16]byte) bool {
  return bytes.Compare(a[:], b[:]) < 0
}

Others array types need a manually crafted function looping over the elements for each type.

Motivation

This came up when writing a function that finds a minimum from a slice of array values efficiently. For [16]byte array values one can use string conversions to tap into the compilers optimisation for comparing known length byte sequences. The Go Gc compiler inlines known length short string comparisons. This currently doesn't work for string(a[:]) < string(b[:]) but could be detected by the compiler. It however instead be better if the code was more concise and had the same optimisations applied to e.g. [8]uint which cant use the string conversion pattern.

Complexity and Overhead

Go code that doesn't use array ordering wont be affected as this proposal is backwards compatible and doesn't add runtime overhead for other array operations. A new spec compatible Go Compiler would need an adjustment of the type checker and compiler.

In contrast this can avoid complexity by adding a concise expressible way to order two large memory regions in form of arrays thats easy to detect and optimise vectorised by the compiler instead of trying to detect a for loop that does element wise comparison.

The computational work done for array comparison is similar to string comparisons or to already allowed array equality checks. Therefore the work involved in a simple operator such as < should not be surprisingly large for a Go programer already using strings and arrays.

A minimal implementation could implement one generic array compare runtime function similar to the generic runtime hashing function that is used with compiler generated calls:

func typehash(t *_type, p unsafe.Pointer, h uintptr) uintptr {

A compiler could then specialise functions for some types and inline and optimise small array compares to avoid using a generic function as an optimisation.

Alternatives

Go users can write out their compare function as currently and the go compiler can detect such a loop and optimise matching ones for some types like uint/byte/... to inlined memory compares.

This would be a special case of a potential general future autovectorisation optimization.
Upsides:

  • does not require a language change
  • keeps the language consistent in that neither arrays, slices or structs support ordering
  • no need to support e.g. floats or complex that have NaNs in the implementation
  • leaves the possibility open for a future language change when more evidence can be collected based on vectorisation optimization if this case is used often enough

Other considerations

In context of the addition of generics this likely also allow array types to be used in containers/functions that expect the passed in type to be ordered. On the otherside generics will
even make it easier to write a generic array compare function (based on converting to slices first).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions