Skip to content

proposal: reflect: improve the speed of getting field name from struct and elements from slice #47099

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

Closed
karminski opened this issue Jul 8, 2021 · 2 comments

Comments

@karminski
Copy link

I'm writing a GraphQL implementation by Go, In some case, I need to get field by name from a struct:

e.g.

 v := reflect.ValueOf(TargetStruct)
 f := reflect.Indirect(v).FieldByName(field)

or get all elements from a slice:

e.g.

v := reflect.ValueOf(TargetSlice)
for i := 0; i < v.Len(); i++ {
    allElem[i] = s.Index(i)
}

But the FieldByName() and Index() method are design for the reflection interface "type Value". And the costs are not necessary in this case.

I wrote two methods for that:

// StructFieldByName returns the struct field with the given name.
// It returns the nil if no field was found.
// It panics if input data Kind is not struct.
func StructFieldByName(structData interface{}, name string) interface{} {
    // unpack Struct
    e := (*emptyInterface)(unsafe.Pointer(&structData))
    startPointer := e.word

    // check flag
    valueMetadataFlag := e.typ.kind
    if valueMetadataFlag == 0 {
        panic("invalied reflect.Value.Type")
    }
    
    // please input a struct type data 
    if valueMetadataFlag != structKind {
        panic("Field of non-struct type ")
    }

    // pick up target field
    var targetStructField *structField
    tt := (*structType)(unsafe.Pointer(e.typ))
    for i := range tt.fields {
        tf := &tt.fields[i]
        if tf.name.name() == name {
            targetStructField = tf
        }
    }
    if targetStructField == nil {
        return nil
    }

    // repack target field
    var packed interface{}
    pe := (*emptyInterface)(unsafe.Pointer(&packed))
    pe.typ  = targetStructField.typ
    pe.word = add(startPointer, targetStructField.offset(), "same as non-reflect &v.field")
    return packed
}
// Get all elements from an interface (slice type)
// SliceAllElements returns the all elements from an interface (slice type).
// It returns the nil if no field was found.
// It panics if input data Kind is not slice.
func SliceAllElements(sliceData interface{}) []interface{} {
    // unpack Struct
    e := (*emptyInterface)(unsafe.Pointer(&sliceData))

    // check flag
    valueMetadataFlag := e.typ.kind
    if valueMetadataFlag == 0 {
        panic("invalied reflect.Value.Type")
    }

    // please input a slice type data 
    if valueMetadataFlag != sliceKind {
        panic("non-slice type ")
    }
    
    s := (*UnsafeSlice)(e.word)

    // check slice len and init a container for return 
    if s.Len < 1 { // an empty slice
        return nil
    }

    // pickup and return
    allElements := make([]interface{}, s.Len)
    tt := (*sliceType)(unsafe.Pointer(e.typ))
    typ := tt.elem
    for i := 0; i < s.Len; i++ {
        element := arrayAt(s.Data, i, typ.size, "i < s.Len")
        var packed interface{}
        pe := (*emptyInterface)(unsafe.Pointer(&packed))
        pe.typ  = typ
        pe.word = element
        allElements[i] = packed
    }
    return allElements
}

And the benchmark result are:

BenchmarkStructFieldByName-8     	14530963	        79.68 ns/op	       0 B/op	       0 allocs/op
BenchmarkOriginalFieldByName-8   	 3430908	       355.1 ns/op	      32 B/op	       4 allocs/op
BenchmarkSliceAllElements-8      	 9998232	       125.7 ns/op	     144 B/op	       1 allocs/op
BenchmarkOriginalIndex-8         	 2914161	       417.5 ns/op	     216 B/op	       9 allocs/op

But these methods depend on the private method from the reflect lib. and if the lib changed, these methods will crash.

So can we do some performance improvements like this, Or maybe there's a fast way to do this already?

Thanks.

@gopherbot gopherbot added this to the Proposal milestone Jul 8, 2021
@ianlancetaylor
Copy link
Member

I expect that the field lookup will be faster if you first decide which field names you are going to look up, use reflect.Type.FieldByName to fetch the reflect.StructField, save the Index value, and then every time you want to look up the field value use reflect.Value.FieldByIndex. Or in the normal case that the fields are not embedded so the length of the Index value is 1, use reflect.Value.Field.

Your all-elements code will be faster if you don't call v.Len on each loop iteration. But it will probably still be slower than your unsafe code.

I think it's unlkely that we will support unpacking the reflect package in the ways that you suggest. That will make it impossible to ever change the reflection data structures.

@rsc
Copy link
Contributor

rsc commented Aug 18, 2021

This proposal has been declined as retracted.
— rsc for the proposal review group

@rsc rsc moved this to Declined in Proposals Aug 10, 2022
@rsc rsc added this to Proposals Aug 10, 2022
@golang golang locked and limited conversation to collaborators Aug 18, 2022
@rsc rsc removed this from Proposals Oct 19, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

5 participants