perf: eliminate O(N²) sort cost and reduce viewer search allocations - #96
Merged
Conversation
The inner loop in renderViewerLine converted rune slices to strings on every candidate position to find search matches, producing O(N·W) temporary allocations per visible line (N lines × W candidate positions). Replaced with strings.Index on the lowercased tail of the line, which finds each match in O(N) without per-position allocation. The byte offset returned by strings.Index is converted back to a rune offset once per confirmed match via utf8.RuneCountInString.
…time nodeEffectiveSize performed a full recursive subtree walk for every directory in the visible list on each sort invocation, making sort cost O(N²) in node count for large layers with deep directory trees. Added FileNode.EffectiveSize, populated by a single post-order DFS (computeEffectiveSizes) called once per tree after it is fully built in Stack and BuildAggregatedTrees. The TUI sort and size-column paths read the field directly, turning per-sort subtree walks into O(1) field reads.
gofmt splits the struct's field-alignment block at the EffectiveSize doc comment; align the pre-comment fields to their own width so gofmt is clean. No behaviour change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Directory sizes used by sort-by-size are now computed once when a layer's file tree is built (post-order DFS populating
FileNode.EffectiveSize), rather than re-walking each directory's subtree on every sort invocation. For large layers with deep directory trees, sort cost drops from O(N²) to O(1) per node after the one-time build pass.Incremental search in the file viewer no longer allocates a temporary string per candidate position when scanning each visible line for matches. The inner scan now uses
strings.Indexon the lowercased line tail, with a singleutf8.RuneCountInStringcall to convert the confirmed byte offset back to a rune offset. Reduces allocation pressure during active search through large files.Changes
image/filetree.go— addedFileNode.EffectiveSizefield andcomputeEffectiveSizes()post-order DFSimage/stack.go— callcomputeEffectiveSizes()on every tree produced byStackandBuildAggregatedTreestui/filetree.go—applySortBySizeand the size-column path readEffectiveSizedirectly;nodeEffectiveSizeretained as tested referencetui/fileview.go— replaced allocating inner match-scan loop inrenderViewerLinewithstrings.Indeximage/filetree_test.go— 7 new tests coveringcomputeEffectiveSizesedge casesTest plan
go build ./...andgo vet ./...cleango test ./...including newimage/filetree_test.gotests)s) while navigating — held-key scroll stays responsive/), type a query — no visible lag while scanning results