-
Notifications
You must be signed in to change notification settings - Fork 34
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
tftypes: Remove Extraneous Memory Allocations in ApplyTerraform5AttributePathStep #307
Labels
bug
Something isn't working
Comments
bflad
added a commit
that referenced
this issue
Jun 30, 2023
Reference: #307 These optimizations were performed by adding benchmark tests against a set of 1000 "simple" objects and viewing the cpu/memory profiles. The original implementations were spending an immense amount of time dealing with memory allocations and garbage collection, so reducing memory allocations was the main target of these optimizations, which in turn, also reduced compute time. The largest changes were accomplished by removing `(Value).Diff()` from the logic paths which were only testing equality. The new `(Value).deepEqual()` implementation started as a duplicate of that logic, removing all `ValueDiff` allocations. Then, the walking of `Value` needed a methodology to stop the walk immediately to prevent further allocations. The two changes in that regard were introducing a `stopWalkError` sentinel error type for which callback functions can signal that no remaining `Value` are necessary to traverse and updating the internal `walk()` implementation to include a new bool return to fully accomplish the intended behavior. The next biggest change was removing The other changes were smaller optimizations noticed from the profiling, such as avoiding the Go runtime immediately reallocating a larger slice with the `AttributePath` methods, avoiding memory allocations caused by `(Type).Is()` usage instead of using type switches, and directly referencing `List`, `Map`, `Object`, `Set`, and `Tuple` value storage instead of allocating an unneeded variable. This logic is heavily used both by this Go module and others, so even these minor optimizations have impact at scale. `benchstat` differences between prior implementation and proposed changes: ``` pkg: github.com/hashicorp/terraform-plugin-go/tftypes │ original.txt │ proposed.txt │ │ sec/op │ sec/op vs base │ ValueApplyTerraform5AttributePathStep1000-10 3838.5µ ± 1% 624.0µ ± 3% -83.74% (p=0.000 n=10) │ original.txt │ proposed.txt │ │ B/op │ B/op vs base │ ValueApplyTerraform5AttributePathStep1000-10 3887.0Ki ± 0% 389.3Ki ± 0% -89.98% (p=0.000 n=10) │ original.txt │ proposed.txt │ │ allocs/op │ allocs/op vs base │ ValueApplyTerraform5AttributePathStep1000-10 123.07k ± 0% 17.64k ± 0% -85.67% (p=0.000 n=10) pkg: github.com/hashicorp/terraform-plugin-go/tftypes │ transform-original.txt │ transform-proposed.txt │ │ sec/op │ sec/op vs base │ Transform1000-10 2876.6µ ± 1% 919.7µ ± 1% -68.03% (p=0.000 n=10) │ transform-original.txt │ transform-proposed.txt │ │ B/op │ B/op vs base │ Transform1000-10 3137.3Ki ± 0% 837.6Ki ± 0% -73.30% (p=0.000 n=10) │ transform-original.txt │ transform-proposed.txt │ │ allocs/op │ allocs/op vs base │ Transform1000-10 61.07k ± 0% 14.02k ± 0% -77.05% (p=0.000 n=10) ```
bflad
added a commit
that referenced
this issue
Jun 30, 2023
Reference: #307 These optimizations were performed by adding benchmark tests against a set of 1000 "simple" objects and viewing the cpu/memory profiles. The original implementations were spending an immense amount of time dealing with memory allocations and garbage collection, so reducing memory allocations was the main target of these optimizations, which in turn, also reduced compute time. The largest changes were accomplished by removing `(Value).Diff()` from the logic paths which were only testing equality. The new `(Value).deepEqual()` implementation started as a duplicate of that logic, removing all `ValueDiff` allocations. Then, the walking of `Value` needed a methodology to stop the walk immediately to prevent further allocations. The two changes in that regard were introducing a `stopWalkError` sentinel error type for which callback functions can signal that no remaining `Value` are necessary to traverse and updating the internal `walk()` implementation to include a new bool return to fully accomplish the intended behavior. The next biggest change was removing The other changes were smaller optimizations noticed from the profiling, such as avoiding the Go runtime immediately reallocating a larger slice with the `AttributePath` methods, avoiding memory allocations caused by `(Type).Is()` usage instead of using type switches, and directly referencing `List`, `Map`, `Object`, `Set`, and `Tuple` value storage instead of allocating an unneeded variable. This logic is heavily used both by this Go module and others, so even these minor optimizations have impact at scale. `benchstat` differences between prior implementation and proposed changes: ``` goos: darwin goarch: arm64 pkg: github.com/hashicorp/terraform-plugin-go/tftypes │ original │ proposed │ │ sec/op │ sec/op vs base │ Transform1000-10 2886.1µ ± 0% 924.6µ ± 0% -67.96% (p=0.000 n=10) ValueApplyTerraform5AttributePathStep1000-10 3863.4µ ± 1% 628.9µ ± 0% -83.72% (p=0.000 n=10) geomean 3.339m 762.5µ -77.16% │ original │ proposed │ │ B/op │ B/op vs base │ Transform1000-10 3137.3Ki ± 0% 837.6Ki ± 0% -73.30% (p=0.000 n=10) ValueApplyTerraform5AttributePathStep1000-10 3887.1Ki ± 0% 389.3Ki ± 0% -89.98% (p=0.000 n=10) geomean 3.410Mi 571.0Ki -83.65% │ original │ proposed │ │ allocs/op │ allocs/op vs base │ Transform1000-10 61.07k ± 0% 14.02k ± 0% -77.05% (p=0.000 n=10) ValueApplyTerraform5AttributePathStep1000-10 123.07k ± 0% 17.64k ± 0% -85.67% (p=0.000 n=10) geomean 86.69k 15.72k -81.86% ```
bflad
added a commit
that referenced
this issue
Jul 3, 2023
…#308) Reference: #307 These optimizations were performed by adding benchmark tests against a set of 1000 "simple" objects and viewing the cpu/memory profiles. The original implementations were spending an immense amount of time dealing with memory allocations and garbage collection, so reducing memory allocations was the main target of these optimizations, which in turn, also reduced compute time. The largest changes were accomplished by removing `(Value).Diff()` from the logic paths which were only testing equality. The new `(Value).deepEqual()` implementation started as a duplicate of that logic, removing all `ValueDiff` allocations. Then, the walking of `Value` needed a methodology to stop the walk immediately to prevent further allocations. The two changes in that regard were introducing a `stopWalkError` sentinel error type for which callback functions can signal that no remaining `Value` are necessary to traverse and updating the internal `walk()` implementation to include a new bool return to fully accomplish the intended behavior. The other changes were smaller optimizations noticed from the profiling, such as avoiding the Go runtime immediately reallocating a larger slice with the `AttributePath` methods, avoiding memory allocations caused by `(Type).Is()` usage instead of using type switches, and directly referencing `List`, `Map`, `Object`, `Set`, and `Tuple` value storage instead of allocating an unneeded variable. This logic is heavily used both by this Go module and others, so even these minor optimizations have impact at scale. `benchstat` differences between prior implementation and proposed changes: ``` goos: darwin goarch: arm64 pkg: github.com/hashicorp/terraform-plugin-go/tftypes │ original │ proposed │ │ sec/op │ sec/op vs base │ Transform1000-10 2886.1µ ± 0% 924.6µ ± 0% -67.96% (p=0.000 n=10) ValueApplyTerraform5AttributePathStep1000-10 3863.4µ ± 1% 628.9µ ± 0% -83.72% (p=0.000 n=10) geomean 3.339m 762.5µ -77.16% │ original │ proposed │ │ B/op │ B/op vs base │ Transform1000-10 3137.3Ki ± 0% 837.6Ki ± 0% -73.30% (p=0.000 n=10) ValueApplyTerraform5AttributePathStep1000-10 3887.1Ki ± 0% 389.3Ki ± 0% -89.98% (p=0.000 n=10) geomean 3.410Mi 571.0Ki -83.65% │ original │ proposed │ │ allocs/op │ allocs/op vs base │ Transform1000-10 61.07k ± 0% 14.02k ± 0% -77.05% (p=0.000 n=10) ValueApplyTerraform5AttributePathStep1000-10 123.07k ± 0% 17.64k ± 0% -85.67% (p=0.000 n=10) geomean 86.69k 15.72k -81.86% ```
I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. |
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
terraform-plugin-go version
Relevant provider source code
Code which generally calls
tftypes.Walk()
/tftypes.Transform()
and involves collection/object types.Terraform Configuration Files
Code with high numbers of elements/attributes.
Expected Behavior
Reasonable performance of
tftypes.Walk()
andtftypes.Transform()
calls, let's arbitrarily say less than 1 second for handling a set of 1000 object elements.Actual Behavior
Operations take many seconds with 1000 set of object elements, with a particular hot spot in
(tftypes.Value).ApplyTerraform5AttributePathStep()
. The code in that method is unnecessarily re-allocating an entire map/slice only to return the element at a map key or slice index. This causes many memory allocations on the heap and GC cycles.Steps to Reproduce
Should be able to unit test benchmark this, but the real-world reproduction was done via terraform-provider-hashicups-pf.
References
The text was updated successfully, but these errors were encountered: