From bbe10f4f7745000c121b629ff68e81bba5a497f6 Mon Sep 17 00:00:00 2001 From: Junegunn Choi Date: Tue, 18 Jul 2017 03:10:49 +0900 Subject: [PATCH] Consolidate Result and rank structs By not storing item index twice, we can cut down the size of Result struct and now it makes more sense to store and pass Results by values. Benchmarks show no degradation of performance by additional pointer indirection for looking up index. --- src/cache.go | 8 ++++---- src/cache_test.go | 4 ++-- src/matcher.go | 8 ++++---- src/merger.go | 18 +++++++++--------- src/merger_test.go | 16 ++++++++-------- src/pattern.go | 16 +++++++++------- src/result.go | 33 ++++++++++++++------------------- src/result_test.go | 22 +++++++++++----------- src/terminal.go | 8 ++++---- 9 files changed, 65 insertions(+), 68 deletions(-) diff --git a/src/cache.go b/src/cache.go index 0540bdc..df1a6ab 100644 --- a/src/cache.go +++ b/src/cache.go @@ -3,7 +3,7 @@ package fzf import "sync" // queryCache associates strings to lists of items -type queryCache map[string][]*Result +type queryCache map[string][]Result // ChunkCache associates Chunk and query string to lists of items type ChunkCache struct { @@ -17,7 +17,7 @@ func NewChunkCache() ChunkCache { } // Add adds the list to the cache -func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Result) { +func (cc *ChunkCache) Add(chunk *Chunk, key string, list []Result) { if len(key) == 0 || !chunk.IsFull() || len(list) > queryCacheMax { return } @@ -34,7 +34,7 @@ func (cc *ChunkCache) Add(chunk *Chunk, key string, list []*Result) { } // Lookup is called to lookup ChunkCache -func (cc *ChunkCache) Lookup(chunk *Chunk, key string) []*Result { +func (cc *ChunkCache) Lookup(chunk *Chunk, key string) []Result { if len(key) == 0 || !chunk.IsFull() { return nil } @@ -52,7 +52,7 @@ func (cc *ChunkCache) Lookup(chunk *Chunk, key string) []*Result { return nil } -func (cc *ChunkCache) Search(chunk *Chunk, key string) []*Result { +func (cc *ChunkCache) Search(chunk *Chunk, key string) []Result { if len(key) == 0 || !chunk.IsFull() { return nil } diff --git a/src/cache_test.go b/src/cache_test.go index 54f3fb0..8a2d2cf 100644 --- a/src/cache_test.go +++ b/src/cache_test.go @@ -7,8 +7,8 @@ func TestChunkCache(t *testing.T) { chunk2 := make(Chunk, chunkSize) chunk1p := &Chunk{} chunk2p := &chunk2 - items1 := []*Result{&Result{}} - items2 := []*Result{&Result{}, &Result{}} + items1 := []Result{Result{}} + items2 := []Result{Result{}, Result{}} cache.Add(chunk1p, "foo", items1) cache.Add(chunk2p, "foo", items1) cache.Add(chunk2p, "bar", items2) diff --git a/src/matcher.go b/src/matcher.go index 57c263a..c29f2b6 100644 --- a/src/matcher.go +++ b/src/matcher.go @@ -131,7 +131,7 @@ func (m *Matcher) sliceChunks(chunks []*Chunk) [][]*Chunk { type partialResult struct { index int - matches []*Result + matches []Result } func (m *Matcher) scan(request MatchRequest) (*Merger, bool) { @@ -162,7 +162,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) { go func(idx int, slab *util.Slab, chunks []*Chunk) { defer func() { waitGroup.Done() }() count := 0 - allMatches := make([][]*Result, len(chunks)) + allMatches := make([][]Result, len(chunks)) for idx, chunk := range chunks { matches := request.pattern.Match(chunk, slab) allMatches[idx] = matches @@ -172,7 +172,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) { } countChan <- len(matches) } - sliceMatches := make([]*Result, 0, count) + sliceMatches := make([]Result, 0, count) for _, matches := range allMatches { sliceMatches = append(sliceMatches, matches...) } @@ -212,7 +212,7 @@ func (m *Matcher) scan(request MatchRequest) (*Merger, bool) { } } - partialResults := make([][]*Result, numSlices) + partialResults := make([][]Result, numSlices) for _ = range slices { partialResult := <-resultChan partialResults[partialResult.index] = partialResult.matches diff --git a/src/merger.go b/src/merger.go index 950ba04..7d30a76 100644 --- a/src/merger.go +++ b/src/merger.go @@ -3,14 +3,14 @@ package fzf import "fmt" // EmptyMerger is a Merger with no data -var EmptyMerger = NewMerger(nil, [][]*Result{}, false, false) +var EmptyMerger = NewMerger(nil, [][]Result{}, false, false) // Merger holds a set of locally sorted lists of items and provides the view of // a single, globally-sorted list type Merger struct { pattern *Pattern - lists [][]*Result - merged []*Result + lists [][]Result + merged []Result chunks *[]*Chunk cursors []int sorted bool @@ -35,11 +35,11 @@ func PassMerger(chunks *[]*Chunk, tac bool) *Merger { } // NewMerger returns a new Merger -func NewMerger(pattern *Pattern, lists [][]*Result, sorted bool, tac bool) *Merger { +func NewMerger(pattern *Pattern, lists [][]Result, sorted bool, tac bool) *Merger { mg := Merger{ pattern: pattern, lists: lists, - merged: []*Result{}, + merged: []Result{}, chunks: nil, cursors: make([]int, len(lists)), sorted: sorted, @@ -59,13 +59,13 @@ func (mg *Merger) Length() int { } // Get returns the pointer to the Result object indexed by the given integer -func (mg *Merger) Get(idx int) *Result { +func (mg *Merger) Get(idx int) Result { if mg.chunks != nil { if mg.tac { idx = mg.count - idx - 1 } chunk := (*mg.chunks)[idx/chunkSize] - return &Result{item: &(*chunk)[idx%chunkSize]} + return Result{item: &(*chunk)[idx%chunkSize]} } if mg.sorted { @@ -89,7 +89,7 @@ func (mg *Merger) cacheable() bool { return mg.count < mergerCacheMax } -func (mg *Merger) mergedGet(idx int) *Result { +func (mg *Merger) mergedGet(idx int) Result { for i := len(mg.merged); i <= idx; i++ { minRank := minRank() minIdx := -1 @@ -100,7 +100,7 @@ func (mg *Merger) mergedGet(idx int) *Result { continue } if cursor >= 0 { - rank := list[cursor].rank + rank := list[cursor] if minIdx < 0 || compareRanks(rank, minRank, mg.tac) { minRank = rank minIdx = listIdx diff --git a/src/merger_test.go b/src/merger_test.go index a4adee1..b98aca8 100644 --- a/src/merger_test.go +++ b/src/merger_test.go @@ -15,11 +15,11 @@ func assert(t *testing.T, cond bool, msg ...string) { } } -func randResult() *Result { +func randResult() Result { str := fmt.Sprintf("%d", rand.Uint32()) - return &Result{ - item: &Item{text: util.RunesToChars([]rune(str))}, - rank: rank{index: rand.Int31()}} + chars := util.RunesToChars([]rune(str)) + chars.Index = rand.Int31() + return Result{item: &Item{text: chars}} } func TestEmptyMerger(t *testing.T) { @@ -29,14 +29,14 @@ func TestEmptyMerger(t *testing.T) { assert(t, len(EmptyMerger.merged) == 0, "Invalid merged list") } -func buildLists(partiallySorted bool) ([][]*Result, []*Result) { +func buildLists(partiallySorted bool) ([][]Result, []Result) { numLists := 4 - lists := make([][]*Result, numLists) + lists := make([][]Result, numLists) cnt := 0 for i := 0; i < numLists; i++ { numResults := rand.Int() % 20 cnt += numResults - lists[i] = make([]*Result, numResults) + lists[i] = make([]Result, numResults) for j := 0; j < numResults; j++ { item := randResult() lists[i][j] = item @@ -45,7 +45,7 @@ func buildLists(partiallySorted bool) ([][]*Result, []*Result) { sort.Sort(ByRelevance(lists[i])) } } - items := []*Result{} + items := []Result{} for _, list := range lists { items = append(items, list...) } diff --git a/src/pattern.go b/src/pattern.go index 07ed9cd..97ee8fd 100644 --- a/src/pattern.go +++ b/src/pattern.go @@ -243,7 +243,7 @@ func (p *Pattern) CacheKey() string { } // Match returns the list of matches Items in the given Chunk -func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []*Result { +func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []Result { // ChunkCache: Exact match cacheKey := p.CacheKey() if p.cacheable { @@ -263,19 +263,19 @@ func (p *Pattern) Match(chunk *Chunk, slab *util.Slab) []*Result { return matches } -func (p *Pattern) matchChunk(chunk *Chunk, space []*Result, slab *util.Slab) []*Result { - matches := []*Result{} +func (p *Pattern) matchChunk(chunk *Chunk, space []Result, slab *util.Slab) []Result { + matches := []Result{} if space == nil { for idx := range *chunk { if match, _, _ := p.MatchItem(&(*chunk)[idx], false, slab); match != nil { - matches = append(matches, match) + matches = append(matches, *match) } } } else { for _, result := range space { if match, _, _ := p.MatchItem(result.item, false, slab); match != nil { - matches = append(matches, match) + matches = append(matches, *match) } } } @@ -286,14 +286,16 @@ func (p *Pattern) matchChunk(chunk *Chunk, space []*Result, slab *util.Slab) []* func (p *Pattern) MatchItem(item *Item, withPos bool, slab *util.Slab) (*Result, []Offset, *[]int) { if p.extended { if offsets, bonus, pos := p.extendedMatch(item, withPos, slab); len(offsets) == len(p.termSets) { - return buildResult(item, offsets, bonus), offsets, pos + result := buildResult(item, offsets, bonus) + return &result, offsets, pos } return nil, nil, nil } offset, bonus, pos := p.basicMatch(item, withPos, slab) if sidx := offset[0]; sidx >= 0 { offsets := []Offset{offset} - return buildResult(item, offsets, bonus), offsets, pos + result := buildResult(item, offsets, bonus) + return &result, offsets, pos } return nil, nil, nil } diff --git a/src/result.go b/src/result.go index fd4d1a9..2df101b 100644 --- a/src/result.go +++ b/src/result.go @@ -19,22 +19,17 @@ type colorOffset struct { index int32 } -type rank struct { - points [4]uint16 - index int32 -} - type Result struct { - item *Item - rank rank + item *Item + points [4]uint16 } -func buildResult(item *Item, offsets []Offset, score int) *Result { +func buildResult(item *Item, offsets []Offset, score int) Result { if len(offsets) > 1 { sort.Sort(ByOrder(offsets)) } - result := Result{item: item, rank: rank{index: item.Index()}} + result := Result{item: item} numChars := item.text.Length() minBegin := math.MaxUint16 minEnd := math.MaxUint16 @@ -75,10 +70,10 @@ func buildResult(item *Item, offsets []Offset, score int) *Result { } } } - result.rank.points[idx] = val + result.points[idx] = val } - return &result + return result } // Sort criteria to use. Never changes once fzf is started. @@ -89,8 +84,8 @@ func (result *Result) Index() int32 { return result.item.Index() } -func minRank() rank { - return rank{index: 0, points: [4]uint16{math.MaxUint16, 0, 0, 0}} +func minRank() Result { + return Result{item: &nilItem, points: [4]uint16{math.MaxUint16, 0, 0, 0}} } func (result *Result) colorOffsets(matchOffsets []Offset, theme *tui.ColorTheme, color tui.ColorPair, attr tui.Attr, current bool) []colorOffset { @@ -201,7 +196,7 @@ func (a ByOrder) Less(i, j int) bool { } // ByRelevance is for sorting Items -type ByRelevance []*Result +type ByRelevance []Result func (a ByRelevance) Len() int { return len(a) @@ -212,11 +207,11 @@ func (a ByRelevance) Swap(i, j int) { } func (a ByRelevance) Less(i, j int) bool { - return compareRanks((*a[i]).rank, (*a[j]).rank, false) + return compareRanks(a[i], a[j], false) } // ByRelevanceTac is for sorting Items -type ByRelevanceTac []*Result +type ByRelevanceTac []Result func (a ByRelevanceTac) Len() int { return len(a) @@ -227,10 +222,10 @@ func (a ByRelevanceTac) Swap(i, j int) { } func (a ByRelevanceTac) Less(i, j int) bool { - return compareRanks((*a[i]).rank, (*a[j]).rank, true) + return compareRanks(a[i], a[j], true) } -func compareRanks(irank rank, jrank rank, tac bool) bool { +func compareRanks(irank Result, jrank Result, tac bool) bool { for idx := 0; idx < 4; idx++ { left := irank.points[idx] right := jrank.points[idx] @@ -240,5 +235,5 @@ func compareRanks(irank rank, jrank rank, tac bool) bool { return false } } - return (irank.index <= jrank.index) != tac + return (irank.item.Index() <= jrank.item.Index()) != tac } diff --git a/src/result_test.go b/src/result_test.go index 8c74691..1d86b1d 100644 --- a/src/result_test.go +++ b/src/result_test.go @@ -31,10 +31,10 @@ func TestOffsetSort(t *testing.T) { } func TestRankComparison(t *testing.T) { - rank := func(vals ...uint16) rank { - return rank{ + rank := func(vals ...uint16) Result { + return Result{ points: [4]uint16{vals[0], vals[1], vals[2], vals[3]}, - index: int32(vals[4])} + item: &Item{text: util.Chars{Index: int32(vals[4])}}} } if compareRanks(rank(3, 0, 0, 0, 5), rank(2, 0, 0, 0, 7), false) || !compareRanks(rank(3, 0, 0, 0, 5), rank(3, 0, 0, 0, 6), false) || @@ -59,23 +59,23 @@ func TestResultRank(t *testing.T) { strs := [][]rune{[]rune("foo"), []rune("foobar"), []rune("bar"), []rune("baz")} item1 := buildResult( withIndex(&Item{text: util.RunesToChars(strs[0])}, 1), []Offset{}, 2) - if item1.rank.points[0] != math.MaxUint16-2 || // Bonus - item1.rank.points[1] != 3 || // Length - item1.rank.points[2] != 0 || // Unused - item1.rank.points[3] != 0 || // Unused + if item1.points[0] != math.MaxUint16-2 || // Bonus + item1.points[1] != 3 || // Length + item1.points[2] != 0 || // Unused + item1.points[3] != 0 || // Unused item1.item.Index() != 1 { - t.Error(item1.rank) + t.Error(item1) } // Only differ in index item2 := buildResult(&Item{text: util.RunesToChars(strs[0])}, []Offset{}, 2) - items := []*Result{item1, item2} + items := []Result{item1, item2} sort.Sort(ByRelevance(items)) if items[0] != item2 || items[1] != item1 { t.Error(items) } - items = []*Result{item2, item1, item1, item2} + items = []Result{item2, item1, item1, item2} sort.Sort(ByRelevance(items)) if items[0] != item2 || items[1] != item2 || items[2] != item1 || items[3] != item1 { @@ -91,7 +91,7 @@ func TestResultRank(t *testing.T) { withIndex(&Item{}, 2), []Offset{Offset{1, 3}, Offset{5, 7}}, 5) item6 := buildResult( withIndex(&Item{}, 2), []Offset{Offset{1, 2}, Offset{6, 7}}, 6) - items = []*Result{item1, item2, item3, item4, item5, item6} + items = []Result{item1, item2, item3, item4, item5, item6} sort.Sort(ByRelevance(items)) if !(items[0] == item6 && items[1] == item5 && items[2] == item4 && items[3] == item3 && diff --git a/src/terminal.go b/src/terminal.go index d3c808d..d4f4c85 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -714,7 +714,7 @@ func (t *Terminal) printHeader() { colors: colors} t.move(line, 2, true) - t.printHighlighted(&Result{item: item}, + t.printHighlighted(Result{item: item}, tui.AttrRegular, tui.ColHeader, tui.ColDefault, false, false) } } @@ -742,7 +742,7 @@ func (t *Terminal) printList() { } } -func (t *Terminal) printItem(result *Result, line int, i int, current bool) { +func (t *Terminal) printItem(result Result, line int, i int, current bool) { item := result.item _, selected := t.selected[item.Index()] label := " " @@ -758,7 +758,7 @@ func (t *Terminal) printItem(result *Result, line int, i int, current bool) { // Avoid unnecessary redraw newLine := itemLine{current: current, selected: selected, label: label, - result: *result, queryLen: len(t.input), width: 0} + result: result, queryLen: len(t.input), width: 0} prevLine := t.prevLines[i] if prevLine.current == newLine.current && prevLine.selected == newLine.selected && @@ -840,7 +840,7 @@ func (t *Terminal) overflow(runes []rune, max int) bool { return t.displayWidthWithLimit(runes, 0, max) > max } -func (t *Terminal) printHighlighted(result *Result, attr tui.Attr, col1 tui.ColorPair, col2 tui.ColorPair, current bool, match bool) int { +func (t *Terminal) printHighlighted(result Result, attr tui.Attr, col1 tui.ColorPair, col2 tui.ColorPair, current bool, match bool) int { item := result.item // Overflow