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.
This commit is contained in:
Junegunn Choi 2017-07-18 03:10:49 +09:00
parent 5e72709613
commit bbe10f4f77
No known key found for this signature in database
GPG Key ID: 254BC280FEF9C627
9 changed files with 65 additions and 68 deletions

View File

@ -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
}

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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...)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 &&

View File

@ -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