Repository navigation
Conversation
Currently, retrieveNodeList iterates over all map keys via rv.MapKeys() and invokes getKeyValue (which reflects over every struct field and parses YANG path struct tags) for every existing list element on each ytypes.SetNode / retrieveNode call. Inserting N elements into a YANG list therefore performs O(N^2) struct-tag parses and reflection lookups. For single-keyed lists (where the map key is not a struct) and non-wildcard/non-partial-key queries: 1. Convert the gNMI path key string to the map key's reflect.Type via StringToType and perform a direct O(1) rv.MapIndex(kfv) lookup. 2. When the element is absent and modifyRoot is set, insert the new entry directly via insertAndGetKey without scanning existing keys. 3. For types where StringToType cannot directly construct the map key (e.g., union interface keys), pre-compute the key field struct index once outside the rv.MapKeys() loop instead of calling getKeyValue on every element.
| schemaKeyMap[fieldName] = schemaKey | ||
| listKeyFieldNameMap[i] = fieldName | ||
| } | ||
| } else if rv.Len() > 0 { |
There was a problem hiding this comment.
This code is a bit of a mess. I'll add comments throughout.
| schemaKeyMap[fieldName] = schemaKey | ||
| listKeyFieldNameMap[i] = fieldName | ||
| } | ||
| } else if rv.Len() > 0 { |
There was a problem hiding this comment.
I'm not sure why this is an else if -- if the list key type was a struct (as above), then we handle the fields above. I understand they are mutually exclusive, but it seems like we should make this clearer to understand (at least with some comments).
|
|
||
| // Reduce FieldByName calls in the loop and reduce Field calls and tag lookup calls in the loop. | ||
| singleKeyFieldIdx := -1 | ||
| if util.IsTypeStruct(listKeyT) { |
There was a problem hiding this comment.
I'd suggest here that we have a check that says:
if rv.Len() == 0 {
// we've got a 0 length map the root value -- it seems
// like we should just be able to return nothing since there are
// no nodes that match the keys that we specified.
}
switch {
case util.IsTypeStruct(listKeyT):
// handle struct type
default:
// we know here that we have a length > 0 so we can just go ahead and handle this case.
}This needs checking though, since there seems to be a case that we're handling transitory cases later down the function -- but still, if there's nothing in the map, we don't have any list elements to traverse.
| if !ok { | ||
| return nil, status.Errorf(codes.NotFound, "schema key %s is not found in gNMI path %v, root %T", schema.Key, path, root) | ||
| } | ||
| if kfv, err := StringToType(listKeyT, pathKey); err == nil { |
There was a problem hiding this comment.
We should prefer to handle the err != nil case here, and understand what it is we want to do at that point.
Almost all paths inside this function seem to return just very late in the code -- not close to when we do the check.
| schemaKeyMap[fieldName] = schemaKey | ||
| listKeyFieldNameMap[i] = fieldName | ||
| } | ||
| } else if rv.Len() > 0 { |
There was a problem hiding this comment.
I think this function should be refactored out -- since it seems like we're saying that there's a shortcut that we can take when we're not handling wildcard keys.
Can you split the function that we have:
func retrieveNodeList(...) {
// validity checks
switch {
// check whether we are handling wildcards
case noWildcards:
return retrieveNodeListConcrete(...)
case wildCards:
return retrieveNodeListWildcard(...)
}
}
Since this doesn't add a bunch of lines into an already very long function.
|
TLDR: I created #1112 as an alternative to #1111, and I added benchmarks in #1113. Across all benchmarks, #1113 is ~39% faster than #1111 and uses ~38% fewer allocations.
|
…to O(1) (#1112) * ytypes: refactor retrieveNodeList and optimize concrete list lookups to O(1) Proposed alternative implementation to #1111 addressing review comments in #1111 (review): * **`ytypes/node.go`**: - Split `retrieveNodeList` into upfront validity checks and a `switch` via `isWildcardListQuery` dispatching to `retrieveNodeListWildcard` vs. `retrieveNodeListConcrete`. - Handle `rv.Len() == 0` upfront in both `retrieveNodeListConcrete` (inserting directly via `insertAndRetrieveNodeList` when `args.modifyRoot` is set, and returning `nil, nil` otherwise) and `retrieveNodeListWildcard`. - Replace the chained `else if rv.Len() > 0` with an explicit `switch` on `listKeyT` (`util.IsTypeStruct`, `util.IsTypeInterface`, `default`). - Handle `StringToType` errors immediately at the call site by returning `codes.InvalidArgument`, rather than ignoring `err != nil` and falling through to full-map iteration. - Extend $O(1)$ `rv.MapIndex` lookup to concrete multi-keyed lists (`retrieveNodeListConcreteStructKey`) in addition to single-keyed lists. - Deduplicate list-element delete/prune and insert-and-recurse logic into `retrieveNodeListElem`, `insertAndRetrieveNodeList`, and `structKeyFields`. * **`ytypes/util_types.go`**: - Extend `StringToType` to handle `reflect.Float64` (`decimal64` keys, rejecting non-finite `NaN`/`Inf`) and `.Convert(t)` on `reflect.String` and `reflect.Bool` (for defined types such as `YANGEmpty`). * **`ytypes/node_test.go`, `ytypes/util_test.go`**: - Add unit tests covering `StringToType`, concrete/wildcard single-key and multi-key lookups, `decimal64` and wrapper-union keys, immediate `InvalidArgument` errors on malformed keys, `GetOrCreateNode` on union-keyed lists, and batch-delete transitory states. - Add `SetNode` and `GetNode` benchmarks for single-keyed and multi-keyed lists (`n = 10, 100, 1000`). * Consolidate GetNode list test cases into TestGetNode. * ytypes: align list lookup refactor and tests with robjs coding style * ytypes: add GetOrCreateNode and DeleteNode single/multi-key benchmarks Add BenchmarkGetOrCreateNode{Single,Multi}KeyList and BenchmarkDeleteNode{Single,Multi}KeyList alongside SetNode and GetNode benchmarks across n = 10, 100, 1000, and cycle GetNode/GetOrCreateNode queries uniformly across all populated list keys. * ytypes: add 5% allocation regression test sharing list benchmark workloads Share the single-keyed and multi-keyed SetNode, GetNode, GetOrCreateNode, and DeleteNode workloads between Benchmark* and TestListTraversalPerformance, encoding baseline heap-allocation constants and failing if any case regresses by more than 5%. * ytypes: extract wildcardKey constant and move list benchmarks to stacked PR * ytypes: add list traversal benchmarks and 5% allocation regression tests (#1113)
|
Closing as we merged the other implementation. |
Currently, retrieveNodeList iterates over all map keys via rv.MapKeys() and invokes getKeyValue (which reflects over every struct field and parses YANG path struct tags) for every existing list element on each ytypes.SetNode / retrieveNode call. Inserting N elements into a YANG list therefore performs O(N^2) struct-tag parses and reflection lookups.
For single-keyed lists (where the map key is not a struct) and non-wildcard/non-partial-key queries: