-
Notifications
You must be signed in to change notification settings - Fork 323
fix: reduce memory consumption by eliminating redundant string copies #965
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
Changes from all commits
229bf9e
c8b3679
30b3e80
a8fed47
fe9603d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -109,9 +109,14 @@ func (d *release) differentiateHelm3() error { | |
| } | ||
|
|
||
| if releaseChart1 == releaseChart2 { | ||
| oldSpecs := manifest.Parse(releaseResponse1, namespace1, d.normalizeManifests, excludes...) | ||
| newSpecs := manifest.Parse(releaseResponse2, namespace2, d.normalizeManifests, excludes...) | ||
| releaseResponse1 = nil //nolint:ineffassign // nil to allow GC to reclaim raw bytes before diff computation | ||
| releaseResponse2 = nil //nolint:ineffassign // nil to allow GC to reclaim raw bytes before diff computation | ||
|
|
||
| seenAnyChanges := diff.Releases( | ||
| manifest.Parse(string(releaseResponse1), namespace1, d.normalizeManifests, excludes...), | ||
| manifest.Parse(string(releaseResponse2), namespace2, d.normalizeManifests, excludes...), | ||
| oldSpecs, | ||
| newSpecs, | ||
| &d.Options, | ||
|
Comment on lines
111
to
120
|
||
| os.Stdout) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -99,9 +99,14 @@ func (d *revision) differentiateHelm3() error { | |
| return err | ||
| } | ||
|
|
||
| oldSpecs := manifest.Parse(revisionResponse, namespace, d.normalizeManifests, excludes...) | ||
| newSpecs := manifest.Parse(releaseResponse, namespace, d.normalizeManifests, excludes...) | ||
| revisionResponse = nil //nolint:ineffassign // nil to allow GC to reclaim raw bytes before diff computation | ||
| releaseResponse = nil //nolint:ineffassign // nil to allow GC to reclaim raw bytes before diff computation | ||
|
|
||
|
Comment on lines
101
to
+106
|
||
| diff.Manifests( | ||
| manifest.Parse(string(revisionResponse), namespace, d.normalizeManifests, excludes...), | ||
| manifest.Parse(string(releaseResponse), namespace, d.normalizeManifests, excludes...), | ||
| oldSpecs, | ||
| newSpecs, | ||
| &d.Options, | ||
| os.Stdout) | ||
|
Comment on lines
107
to
111
|
||
|
|
||
|
|
@@ -122,9 +127,14 @@ func (d *revision) differentiateHelm3() error { | |
| return err | ||
| } | ||
|
|
||
| oldSpecs := manifest.Parse(revisionResponse1, namespace, d.normalizeManifests, excludes...) | ||
| newSpecs := manifest.Parse(revisionResponse2, namespace, d.normalizeManifests, excludes...) | ||
| revisionResponse1 = nil //nolint:ineffassign // nil to allow GC to reclaim raw bytes before diff computation | ||
| revisionResponse2 = nil //nolint:ineffassign // nil to allow GC to reclaim raw bytes before diff computation | ||
|
|
||
| seenAnyChanges := diff.Manifests( | ||
| manifest.Parse(string(revisionResponse1), namespace, d.normalizeManifests, excludes...), | ||
| manifest.Parse(string(revisionResponse2), namespace, d.normalizeManifests, excludes...), | ||
| oldSpecs, | ||
| newSpecs, | ||
| &d.Options, | ||
| os.Stdout) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -90,9 +90,14 @@ func (d *rollback) backcastHelm3() error { | |||||||||||
| } | ||||||||||||
|
|
||||||||||||
| // create a diff between the current manifest and the version of the manifest that a user is intended to rollback | ||||||||||||
| oldSpecs := manifest.Parse(releaseResponse, namespace, d.normalizeManifests, excludes...) | ||||||||||||
| newSpecs := manifest.Parse(revisionResponse, namespace, d.normalizeManifests, excludes...) | ||||||||||||
| releaseResponse = nil //nolint:ineffassign // nil to allow GC to reclaim raw bytes before diff computation | ||||||||||||
| revisionResponse = nil //nolint:ineffassign // nil to allow GC to reclaim raw bytes before diff computation | ||||||||||||
|
|
||||||||||||
|
||||||||||||
| // Allow GC to reclaim raw manifest bytes before potentially expensive diff processing. | |
| releaseResponse = nil | |
| revisionResponse = nil |
Copilot
AI
Mar 28, 2026
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.
To reduce peak memory during the diff, consider dropping references to the large releaseResponse / revisionResponse byte slices after parsing (e.g., parse into locals, then set the byte slices to nil before calling diff.Manifests). As written, both raw buffers remain live for the duration of the diff.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -327,11 +327,12 @@ func (d *diffCmd) runHelm3() error { | |
| releaseManifest = append(releaseManifest, hooks...) | ||
| } | ||
| if d.includeTests { | ||
| currentSpecs = manifest.Parse(string(releaseManifest), d.namespace, d.normalizeManifests) | ||
| currentSpecs = manifest.Parse(releaseManifest, d.namespace, d.normalizeManifests) | ||
| } else { | ||
| currentSpecs = manifest.Parse(string(releaseManifest), d.namespace, d.normalizeManifests, manifest.Helm3TestHook, manifest.Helm2TestSuccessHook) | ||
| currentSpecs = manifest.Parse(releaseManifest, d.namespace, d.normalizeManifests, manifest.Helm3TestHook, manifest.Helm2TestSuccessHook) | ||
| } | ||
|
Comment on lines
329
to
333
|
||
| } | ||
| releaseManifest = nil //nolint:ineffassign // nil to allow GC to reclaim raw bytes before diff computation | ||
|
|
||
| var newOwnedReleases map[string]diff.OwnershipDiff | ||
| if d.takeOwnership { | ||
|
|
@@ -347,10 +348,11 @@ func (d *diffCmd) runHelm3() error { | |
|
|
||
| var newSpecs map[string]*manifest.MappingResult | ||
| if d.includeTests { | ||
| newSpecs = manifest.Parse(string(installManifest), d.namespace, d.normalizeManifests) | ||
| newSpecs = manifest.Parse(installManifest, d.namespace, d.normalizeManifests) | ||
| } else { | ||
| newSpecs = manifest.Parse(string(installManifest), d.namespace, d.normalizeManifests, manifest.Helm3TestHook, manifest.Helm2TestSuccessHook) | ||
| newSpecs = manifest.Parse(installManifest, d.namespace, d.normalizeManifests, manifest.Helm3TestHook, manifest.Helm2TestSuccessHook) | ||
| } | ||
|
Comment on lines
350
to
354
|
||
| installManifest = nil //nolint:ineffassign // nil to allow GC to reclaim raw bytes before diff computation | ||
|
|
||
| seenAnyChanges := diff.ManifestsOwnership(currentSpecs, newSpecs, newOwnedReleases, &d.Options, os.Stdout) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -181,6 +181,11 @@ func actualChanges(diff []difflib.DiffRecord) int { | |
| return changes | ||
| } | ||
|
|
||
| const ( | ||
| renameDetectionMinLengthRatio float32 = 0.1 | ||
| renameDetectionMaxLengthRatio float32 = 10.0 | ||
| ) | ||
|
|
||
| func contentSearch(report *Report, possiblyRemoved []string, oldIndex map[string]*manifest.MappingResult, possiblyAdded []string, newIndex map[string]*manifest.MappingResult, options *Options) ([]string, []string) { | ||
| if options.FindRenames <= 0 { | ||
| return possiblyRemoved, possiblyAdded | ||
|
|
@@ -198,6 +203,21 @@ func contentSearch(report *Report, possiblyRemoved []string, oldIndex map[string | |
| continue | ||
| } | ||
|
|
||
| oldLen := len(oldContent.Content) | ||
| newLen := len(newContent.Content) | ||
| if oldLen == 0 || newLen == 0 { | ||
| continue | ||
| } | ||
| // Skip the length-ratio filter for Secrets: their raw content length can | ||
| // differ greatly from the post-processed (redacted/decoded) length, so the | ||
| // ratio would be an unreliable predictor of content similarity. | ||
| if oldContent.Kind != "Secret" { | ||
| ratio := float32(oldLen) / float32(newLen) | ||
| if ratio < renameDetectionMinLengthRatio || ratio > renameDetectionMaxLengthRatio { | ||
| continue | ||
| } | ||
| } | ||
|
Comment on lines
+206
to
+219
|
||
|
|
||
| switch { | ||
| case options.ShowSecretsDecoded: | ||
| decodeSecrets(oldContent, newContent) | ||
|
|
@@ -208,7 +228,7 @@ func contentSearch(report *Report, possiblyRemoved []string, oldIndex map[string | |
| diff := diffMappingResults(oldContent, newContent, options.StripTrailingCR) | ||
| delta := actualChanges(diff) | ||
| if delta == 0 || len(diff) == 0 { | ||
| continue // Should never happen, but better safe than sorry | ||
| continue | ||
| } | ||
| fraction := float32(delta) / float32(len(diff)) | ||
| if fraction > 0 && fraction < smallestFraction { | ||
|
|
||
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.
Likewise,
releaseResponse1/releaseResponse2remain referenced after parsing intooldSpecs/newSpecs. If these raw manifest bytes aren’t used later, consider setting them tonilafter parsing to reduce peak memory duringdiff.Releases(...)for large releases.