internal/impl: optimize reflect methods
This change performs two optimizations:
* It uses a pre-constructed rangeInfos slice to iterate over
all the fields. This is more performant since iterating over a slice
is faster than iterating over a map. Furthermore, this slice
does not contain fields that are part of a oneof. If a oneof has
N fields, the time to check presence on the oneof is now O(1)
instead of O(N).
* It uses a dense field info slice that is optmized for the common
case where the field number is relatively low and close in value
to the index itself.
We also fix a minor bug in the construction of oneofInfo where
it wasn't treating a typed nil pointer to a wrapper struct as if
it were unset. This ensures WhichOneof and Has always agree.
name old time/op new time/op delta
Reflect/Has-4 7.81µs ± 3% 6.74µs ± 3% -13.61% (p=0.000 n=9+9)
Reflect/Get-4 12.7µs ± 1% 11.3µs ± 4% -10.85% (p=0.000 n=8+10)
Reflect/Set-4 19.5µs ± 5% 17.8µs ± 2% -8.99% (p=0.000 n=10+10)
Reflect/Clear-4 12.0µs ± 4% 10.2µs ± 3% -14.86% (p=0.000 n=9+10)
Reflect/Range-4 6.58µs ± 1% 4.17µs ± 2% -36.65% (p=0.000 n=8+9)
Change-Id: I2c48b4d3fb6103ab238924950529ded0d37f8c8a
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/196358
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/internal/impl/message_reflect_test.go b/internal/impl/message_reflect_test.go
index a836c89..6148d4d 100644
--- a/internal/impl/message_reflect_test.go
+++ b/internal/impl/message_reflect_test.go
@@ -1485,3 +1485,53 @@
})
runtime.KeepAlive(sink)
}
+
+func BenchmarkReflect(b *testing.B) {
+ m := new(testpb.TestAllTypes).ProtoReflect()
+ fds := m.Descriptor().Fields()
+ vs := make([]pref.Value, fds.Len())
+ for i := range vs {
+ vs[i] = m.NewField(fds.Get(i))
+ }
+
+ b.Run("Has", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ for j := 0; j < fds.Len(); j++ {
+ m.Has(fds.Get(j))
+ }
+ }
+ })
+ b.Run("Get", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ for j := 0; j < fds.Len(); j++ {
+ m.Get(fds.Get(j))
+ }
+ }
+ })
+ b.Run("Set", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ for j := 0; j < fds.Len(); j++ {
+ m.Set(fds.Get(j), vs[j])
+ }
+ }
+ })
+ b.Run("Clear", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ for j := 0; j < fds.Len(); j++ {
+ m.Clear(fds.Get(j))
+ }
+ }
+ })
+ b.Run("Range", func(b *testing.B) {
+ b.ReportAllocs()
+ for i := 0; i < b.N; i++ {
+ m.Range(func(pref.FieldDescriptor, pref.Value) bool {
+ return true
+ })
+ }
+ })
+}