Skip to content
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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

jayzhudev
Copy link
Contributor

This is a split change from the combined PR of node cache implementation (#830).

@coveralls
Copy link

coveralls commented May 15, 2023

Coverage Status

coverage: 89.644% (-0.005%) from 89.649% when pulling 26dc738 on jayzhudev:ygot-perf into 0a6b89a on openconfig:master.

ygot/render.go Outdated
Comment on lines 167 to 168
// CopyReserve performs normal Copy and reserves extra space for the slices.
func (g *gnmiPath) CopyReserve(reservedSize int) *gnmiPath {
Copy link
Collaborator

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.

Comment on lines 57 to 61
// 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) {
Copy link
Collaborator

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?

Comment on lines 80 to 84
// 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) {
Copy link
Collaborator

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
Comment on lines 1534 to 1661
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)
Copy link
Collaborator

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
Comment on lines 1512 to 1513
// When jType == RFC7951, per the IETF RFC7951 specificatioin,
// uint64, int64 and float64 values are represented as strings.
Copy link
Collaborator

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() {
Copy link
Collaborator

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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
}
Copy link
Collaborator

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
Comment on lines 1393 to 1401

var kn string
switch keyval := keyval.(type) {
case string:
kn = keyval
default:
kn = fmt.Sprintf("%v", keyval)
}

Copy link
Collaborator

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
Comment on lines 1332 to 1436
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)
}
Copy link
Collaborator

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
Comment on lines 1270 to 1272
func() {
mapPaths := mapPathsPool.Get().([]*gnmiPath)
defer mapPathsPool.Put(mapPaths)
Copy link
Collaborator

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.

Copy link
Contributor Author

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.

@jayzhudev jayzhudev changed the title perf(ygot): improve perf for render and struct_validation_map cleanup(ygot): render cleanups and minor improvements for reflect uses Jul 26, 2023
@jayzhudev
Copy link
Contributor Author

jayzhudev commented Jul 26, 2023

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 sync.Pool we may want to remove. This PR has now become a small change that brings in some cleanups and minor improvements to the use of reflect in render.go. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants