1
1
mirror of https://github.com/walles/moar.git synced 2024-08-16 15:30:34 +03:00

Compare commits

...

13 Commits

Author SHA1 Message Date
Johan Walles
f434895eef Fix backwards search performance issue
This problem was here all along, but it was excarberated by the newly
introduced parallel search.
2024-05-18 08:37:06 +02:00
Johan Walles
7b032b2fa4 Merge branch 'johan/parallel-search' 2024-05-18 08:17:13 +02:00
Johan Walles
686882ffcd Improve docs 2024-05-18 08:13:08 +02:00
Johan Walles
60d3577b59 Don't crash searching in empty buffer 2024-05-18 07:59:44 +02:00
Johan Walles
64ae443e1f Log searches 2024-05-18 07:55:46 +02:00
Johan Walles
856574c289 Don't divide by zero 2024-05-18 07:52:20 +02:00
Johan Walles
ee35c6b166 Fix the chunk before positions 2024-05-18 07:47:40 +02:00
Johan Walles
625193933d Fix the line counts 2024-05-18 07:44:01 +02:00
Johan Walles
387fb348c7 Fix loop variables warning 2024-05-18 07:33:53 +02:00
Johan Walles
84cf4a9cd9 Return the search results
And fix backwards searches.
2024-05-18 07:31:28 +02:00
Johan Walles
aa342d4325 Implement more of the parallel search 2024-05-18 07:28:17 +02:00
Johan Walles
27d03630a5 Half way implement the parallel search 2024-05-18 07:23:40 +02:00
Johan Walles
0ba0415a50 Set the stage for parallel search 2024-05-18 07:15:31 +02:00

View File

@ -2,6 +2,9 @@ package m
import (
"fmt"
"runtime"
log "github.com/sirupsen/logrus"
"github.com/walles/moar/m/linenumbers"
)
@ -12,10 +15,16 @@ func (p *Pager) scrollToSearchHits() {
return
}
firstHitPosition := p.findFirstHit(*p.scrollPosition.lineNumber(p), nil, false)
lineNumber := p.scrollPosition.lineNumber(p)
if lineNumber == nil {
// No lines to search
return
}
firstHitPosition := p.findFirstHit(*lineNumber, nil, false)
if firstHitPosition == nil {
// Try again from the top
firstHitPosition = p.findFirstHit(linenumbers.LineNumber{}, p.scrollPosition.lineNumber(p), false)
firstHitPosition = p.findFirstHit(linenumbers.LineNumber{}, lineNumber, false)
}
if firstHitPosition == nil {
// No match, give up
@ -30,12 +39,97 @@ func (p *Pager) scrollToSearchHits() {
p.scrollPosition = *firstHitPosition
}
// NOTE: When we search, we do that by looping over the *input lines*, not
// the screen lines. That's why we're using a line number rather than a
// scrollPosition for searching.
// NOTE: When we search, we do that by looping over the *input lines*, not the
// screen lines. That's why startPosition is a LineNumber rather than a
// scrollPosition.
//
// The `beforePosition` parameter is exclusive, meaning that line will not be
// searched.
//
// For the actual searching, this method will call _findFirstHit() in parallel
// on multiple cores, to help large file search performance.
//
// FIXME: We should take startPosition.deltaScreenLines into account as well!
func (p *Pager) findFirstHit(startPosition linenumbers.LineNumber, beforePosition *linenumbers.LineNumber, backwards bool) *scrollPosition {
// If the number of lines to search matches the number of cores (or more),
// divide the search into chunks. Otherwise use one chunk.
chunkCount := runtime.NumCPU()
var linesCount int
if backwards {
// If the startPosition is zero, that should make the count one
linesCount = startPosition.AsZeroBased() + 1
if beforePosition != nil {
// Searching from 1 with before set to 0 should make the count 1
linesCount = startPosition.AsZeroBased() - beforePosition.AsZeroBased()
}
} else {
linesCount = p.reader.GetLineCount() - startPosition.AsZeroBased()
if beforePosition != nil {
// Searching from 1 with before set to 2 should make the count 1
linesCount = beforePosition.AsZeroBased() - startPosition.AsZeroBased()
}
}
if linesCount < chunkCount {
chunkCount = 1
}
chunkSize := linesCount / chunkCount
log.Debugf("Searching %d lines across %d cores with %d lines per core", linesCount, chunkCount, chunkSize)
// Each parallel search will start at one of these positions
searchStarts := make([]linenumbers.LineNumber, chunkCount)
direction := 1
if backwards {
direction = -1
}
for i := 0; i < chunkCount; i++ {
searchStarts[i] = startPosition.NonWrappingAdd(i * direction * chunkSize)
}
// Make a results array, with one result per chunk
findings := make([]chan *scrollPosition, chunkCount)
// Search all chunks in parallel
for i, searchStart := range searchStarts {
findings[i] = make(chan *scrollPosition)
searchEndIndex := i + 1
var chunkBefore *linenumbers.LineNumber
if searchEndIndex < len(searchStarts) {
chunkBefore = &searchStarts[searchEndIndex]
} else if beforePosition != nil {
chunkBefore = beforePosition
}
go func(i int, searchStart linenumbers.LineNumber, chunkBefore *linenumbers.LineNumber) {
findings[i] <- p._findFirstHit(searchStart, chunkBefore, backwards)
}(i, searchStart, chunkBefore)
}
// Return the first non-nil result
for _, finding := range findings {
result := <-finding
if result != nil {
return result
}
}
return nil
}
// NOTE: When we search, we do that by looping over the *input lines*, not the
// screen lines. That's why startPosition is a LineNumber rather than a
// scrollPosition.
//
// The `beforePosition` parameter is exclusive, meaning that line will not be
// searched.
//
// This method will run over multiple chunks of the input file in parallel to
// help large file search performance.
//
// FIXME: We should take startPosition.deltaScreenLines into account as well!
func (p *Pager) _findFirstHit(startPosition linenumbers.LineNumber, beforePosition *linenumbers.LineNumber, backwards bool) *scrollPosition {
searchPosition := startPosition
for {
line := p.reader.GetLine(searchPosition)
@ -51,18 +145,18 @@ func (p *Pager) findFirstHit(startPosition linenumbers.LineNumber, beforePositio
if backwards {
if (searchPosition == linenumbers.LineNumber{}) {
// No match, give up
// Reached the top without any match, give up
return nil
}
searchPosition = searchPosition.NonWrappingAdd(-1)
} else {
searchPosition = searchPosition.NonWrappingAdd(1)
}
if beforePosition != nil && searchPosition == *beforePosition {
// No match, give up
return nil
}
if beforePosition != nil && searchPosition == *beforePosition {
// No match, give up
return nil
}
}
}