-
Notifications
You must be signed in to change notification settings - Fork 106
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
cleanup(ygot): render
cleanups and minor improvements for reflect
uses
#831
base: master
Are you sure you want to change the base?
Conversation
ygot/render.go
Outdated
// CopyReserve performs normal Copy and reserves extra space for the slices. | ||
func (g *gnmiPath) CopyReserve(reservedSize int) *gnmiPath { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also note that for PathElem paths it performs a shallow copy.
ygot/struct_validation_map.go
Outdated
// structTagToLibPaths takes an input struct field as a reflect.Type, and determines | ||
// the set of validation library paths that it maps to. Returns the paths as a slice of | ||
// empty interface slices, or an error. | ||
// the set of validation library paths that it maps to. | ||
// | ||
// Note: the returned paths use a shallow copy of the parentPath. | ||
func structTagToLibPaths(f reflect.StructField, parentPath *gnmiPath, preferShadowPath bool) ([]*gnmiPath, error) { | ||
// Note: the mapPaths input is modified in-place. | ||
func structTagToLibPaths(mapPaths []*gnmiPath, f reflect.StructField, parentPath *gnmiPath, preferShadowPath bool) (int, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update comment. Where does the mapPaths input come from? What does it return? Is mapPaths modified?
ygot/struct_validation_map.go
Outdated
// pathAnnotationToLibPaths takes the already looked-up pathAnnotation as input and determines | ||
// the set of validation library paths that it describes. | ||
// | ||
// Note: the mapPaths input is modified in-place. | ||
func pathAnnotationToLibPaths(mapPaths []*gnmiPath, pathAnnotation string, parentPath *gnmiPath) (int, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explain the other inputs and output of the function.
ygot/render.go
Outdated
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32: | ||
value = field.Elem().Int() | ||
case reflect.Int64: | ||
if args.jType == RFC7951 { | ||
val := field.Elem().Int() | ||
value = strconv.FormatInt(val, 10) | ||
} else { | ||
value = field.Elem().Int() | ||
} | ||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: | ||
value = field.Elem().Uint() | ||
case reflect.Uint64: | ||
if args.jType == RFC7951 { | ||
val := field.Elem().Uint() | ||
value = strconv.FormatUint(val, 10) | ||
} else { | ||
value = field.Elem().Uint() | ||
} | ||
case reflect.Float32: | ||
value = field.Elem().Float() | ||
case reflect.Float64: | ||
if args.jType == RFC7951 { | ||
value = fmt.Sprintf("%g", field.Elem().Float()) | ||
} else { | ||
value = field.Elem().Float() | ||
} | ||
case reflect.String: | ||
value = field.Elem().String() | ||
case reflect.Bool: | ||
value = field.Elem().Bool() | ||
default: | ||
value = field.Elem().Interface() | ||
if args.jType == RFC7951 { | ||
value = writeIETFScalarJSON(value) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like it's generally useful for writeIETFScalarJSON
. I think it makes sense to change the implementation of writeIETFScalarJSON
to have these case statements. You can create a helper that writeIETFScalarJSON
calls that takes in reflect.Value and reflect.Kind. This way we also shorten this function for readers.
ygot/render.go
Outdated
// When jType == RFC7951, per the IETF RFC7951 specificatioin, | ||
// uint64, int64 and float64 values are represented as strings. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So if you incorporate the logic below into a helper for writeIETFScalarJSON
then this can be removed.
ygot/render.go
Outdated
errs.Add(fmt.Errorf("%v->%s: %v", parent, ftype.Name, err)) | ||
continue | ||
} | ||
func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for putting this in a function? Did you mean to extract this out into a helper?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this was for convenience of putting back to the sync.Pool
using defer
. I personally don't prefer this approach as well, and the use of sync.Pool
is not giving much performance gain in this case. I'm inclined to revert the use of sync.Pool
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wenovus Sorry for the delay. June was a very busy month on multiple tasks for me. I'll get back to this soon.
ygot/render.go
Outdated
if prependmods != nil && p.Len() != len(prependmods[i]) { | ||
errs.Add(fmt.Errorf("number of paths and modules elements not the same: (paths: %v, modules: %v)", p, prependmods[i])) | ||
if !strings.Contains(pathAnnotation, "/") { | ||
parent[pathAnnotation] = value | ||
continue | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ygot was not designed for very high performance situations in mind, so I think it might not be appropriate for micro-optimizations like this to be here. I'm ok with more generic optimizations like the node caching, but it's unlikely that we won't introduce performance regressions in the future since it is highly unintuitive to write code like this if the writer doesn't care about performance to the same level, and we're unlikely to enforce any stringent performance metrics.
ygot/render.go
Outdated
|
||
var kn string | ||
switch keyval := keyval.(type) { | ||
case string: | ||
kn = keyval | ||
default: | ||
kn = fmt.Sprintf("%v", keyval) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is another example of a micro-optimization that I think we're unlikely to enforce in this repository for the future. It makes the code more verbose but doesn't benefit most of the use cases of the repo.
If you add a clear comment explaining why this is here I'm ok with accepting it, but I want you to be aware that we might very well choose to not to abide by such micro-optimizations in the future.
ygot/render.go
Outdated
func writeIETFScalarJSON(i interface{}) interface{} { | ||
switch reflect.ValueOf(i).Kind() { | ||
case reflect.Uint64, reflect.Int64, reflect.Float64: | ||
case reflect.Float64: | ||
return fmt.Sprintf("%v", i) | ||
case reflect.Int64: | ||
if val, ok := i.(int64); ok { | ||
return strconv.FormatInt(int64(val), 10) | ||
} | ||
|
||
return fmt.Sprintf("%d", i) | ||
case reflect.Uint64: | ||
if val, ok := i.(uint64); ok { | ||
return strconv.FormatUint(uint64(val), 10) | ||
} | ||
|
||
return fmt.Sprintf("%d", i) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As suggested below it would be nice if the cases here can be updated and the cases below removed in favour of this to benefit all users of this function.
ygot/render.go
Outdated
func() { | ||
mapPaths := mapPathsPool.Get().([]*gnmiPath) | ||
defer mapPathsPool.Put(mapPaths) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if this can be refactored into its own function now that there are more optimizations and code added.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for all the comments and notes. I agree that some of them seem more like trading complexity for small gains now (many are side changes added while hunting for big performance improvements for our particular use case). The use of sync.Pool
here isn't justified by significant GC pressure reduction as well. I'll read through all of your comments again and I think we can probably revert the use of sync.Pool
, that way this PR can be much simplified and I think it can focus on improving writeIETFScalarJSON
based on your comment.
render
and struct_validation_map
render
cleanups and minor improvements for reflect
uses
Since last time we visited this PR there have been lots of changes made to the master branch. I simplified this PR as it was mentioned previously that there are micro-optimizations we may not want to include, and uses of |
This is a split change from the combined PR of node cache implementation (#830).