Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nsf/eabc9018a4662c3f3ffc1a9209eacb6b to your computer and use it in GitHub Desktop.
Save nsf/eabc9018a4662c3f3ffc1a9209eacb6b to your computer and use it in GitHub Desktop.
Methods for fetching structure fields in Go (golang)
package main
import "log"
import "time"
import "reflect"
import "unsafe"
// suggested via http://stackoverflow.com/a/8363629/87207
func trace(s string) (string, time.Time) {
log.Println("START:", s)
return s, time.Now()
}
// suggested via http://stackoverflow.com/a/8363629/87207
func un(s string, startTime time.Time) {
endTime := time.Now()
log.Println(" END:", s, "ElapsedTime in seconds:", endTime.Sub(startTime))
}
type S struct {
A int
B int
C int
D int
E int
F int
G int
H int
X int
Y int
}
type Getter func(*S, string) int
// get a field by static offset
func getField(s *S, _ string) int {
return s.X
}
// dynamically lookup a field
// 40x slower than getField()
func reflectField(s *S, field string) int {
r := reflect.ValueOf(s)
f := reflect.Indirect(r).FieldByName(field)
return int(f.Int())
}
// create a function that knows what slot to fetch given a name.
// 10x slower than getField()
func makeFieldGetter(v reflect.Value, field string) Getter {
t := v.Type()
for i := 0; i < v.NumField(); i++ {
if t.Field(i).Name == field {
return func(s *S, _ string) int {
av := reflect.ValueOf(s).Elem()
i, _ := av.Field(i).Interface().(int)
return i
}
}
}
return nil
}
func makeFieldGetterUnsafe(t reflect.Type, field string) Getter {
for i := 0; i < t.NumField(); i++ {
if t.Field(i).Name == field {
offset := t.Field(i).Offset
return func(s *S, _ string) int {
p := unsafe.Pointer(uintptr(unsafe.Pointer(s)) + offset)
return *(*int)(p)
}
}
}
return nil
}
func testMethod(name string, s *S, f Getter) {
defer un(trace(name))
var x int
for i := 0; i < 10 * 1000 * 1000; i++ {
x = f(s, "X")
}
println(x)
}
func main() {
s := S{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
testMethod("direct", &s, getField)
testMethod("getter", &s, makeFieldGetter(reflect.ValueOf(&s).Elem(), "X"))
testMethod("getter_unsafe", &s, makeFieldGetterUnsafe(reflect.TypeOf(&s).Elem(), "X"))
testMethod("reflect", &s, reflectField)
}
@nsf
Copy link
Author

nsf commented May 19, 2019

Added unsafe getter function, it gives same perf as "direct", but it's "unsafe":

2019/05/19 11:09:46 START: direct
2019/05/19 11:09:46   END: direct ElapsedTime in seconds: 40.745804ms
2019/05/19 11:09:46 START: getter
2019/05/19 11:09:46   END: getter ElapsedTime in seconds: 712.222534ms
2019/05/19 11:09:46 START: getter_unsafe
2019/05/19 11:09:46   END: getter_unsafe ElapsedTime in seconds: 38.501966ms
2019/05/19 11:09:46 START: reflect
2019/05/19 11:09:49   END: reflect ElapsedTime in seconds: 2.337854547s

@nsf
Copy link
Author

nsf commented May 19, 2019

Also let's print the result, so that we know it's correct:

2019/05/19 11:12:45 START: direct
9
2019/05/19 11:12:45   END: direct ElapsedTime in seconds: 38.518838ms
2019/05/19 11:12:45 START: getter
9
2019/05/19 11:12:46   END: getter ElapsedTime in seconds: 698.960467ms
2019/05/19 11:12:46 START: getter_unsafe
9
2019/05/19 11:12:46   END: getter_unsafe ElapsedTime in seconds: 40.986655ms
2019/05/19 11:12:46 START: reflect
9
2019/05/19 11:12:48   END: reflect ElapsedTime in seconds: 2.155271329s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment