mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-13 03:59:23 +02:00
Add some benchmarking for choose_files
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -55,7 +56,7 @@ func (r *ResultItem) sorted_positions() []int {
|
||||
}
|
||||
|
||||
type FileSystemScanner struct {
|
||||
listeners []chan int
|
||||
listeners []chan bool
|
||||
in_progress, keep_going atomic.Bool
|
||||
root_dir string
|
||||
mutex sync.Mutex
|
||||
@@ -64,8 +65,8 @@ type FileSystemScanner struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func NewFileSystemScanner(root_dir string, notify chan int) (fss *FileSystemScanner) {
|
||||
ans := &FileSystemScanner{root_dir: root_dir, listeners: []chan int{notify}, results: make([]ResultItem, 0, 1024)}
|
||||
func NewFileSystemScanner(root_dir string, notify chan bool) (fss *FileSystemScanner) {
|
||||
ans := &FileSystemScanner{root_dir: root_dir, listeners: []chan bool{notify}, results: make([]ResultItem, 0, 1024)}
|
||||
ans.in_progress.Store(true)
|
||||
ans.keep_going.Store(true)
|
||||
ans.dir_reader = os.ReadDir
|
||||
@@ -75,7 +76,7 @@ func NewFileSystemScanner(root_dir string, notify chan int) (fss *FileSystemScan
|
||||
type Scanner interface {
|
||||
Start()
|
||||
Cancel()
|
||||
AddListener(chan int)
|
||||
AddListener(chan bool)
|
||||
Len() int
|
||||
Batch(offset int) []ResultItem
|
||||
Finished() bool
|
||||
@@ -99,7 +100,7 @@ func (fss *FileSystemScanner) Cancel() {
|
||||
fss.keep_going.Store(false)
|
||||
}
|
||||
|
||||
func (fss *FileSystemScanner) AddListener(x chan int) {
|
||||
func (fss *FileSystemScanner) AddListener(x chan bool) {
|
||||
fss.lock()
|
||||
defer fss.unlock()
|
||||
if !fss.in_progress.Load() {
|
||||
@@ -200,7 +201,10 @@ func (fss *FileSystemScanner) worker() {
|
||||
seen_dirs := make(map[string]bool)
|
||||
symlink_dir_map := make(map[string]string)
|
||||
root_dir, _ := filepath.Abs(fss.root_dir)
|
||||
dir := root_dir + string(os.PathSeparator)
|
||||
dir := root_dir
|
||||
if !strings.HasSuffix(dir, string(os.PathSeparator)) {
|
||||
dir += string(os.PathSeparator)
|
||||
}
|
||||
base := ""
|
||||
pos := 0
|
||||
var arena []sortable_dir_entry
|
||||
@@ -252,7 +256,7 @@ func (fss *FileSystemScanner) worker() {
|
||||
ns := fss.results
|
||||
new_sz := len(ns) + len(entries)
|
||||
if cap(ns) < new_sz {
|
||||
ns = make([]ResultItem, len(ns), max(16*1024, new_sz, cap(ns)*2))
|
||||
ns = make([]ResultItem, len(ns), max(1024, new_sz, cap(ns)*2))
|
||||
copy(ns, fss.results)
|
||||
}
|
||||
new_items := ns[len(ns):new_sz]
|
||||
@@ -267,11 +271,10 @@ func (fss *FileSystemScanner) worker() {
|
||||
fss.lock()
|
||||
fss.results = ns
|
||||
listeners := fss.listeners
|
||||
num := len(fss.results)
|
||||
fss.unlock()
|
||||
for _, l := range listeners {
|
||||
select {
|
||||
case l <- num:
|
||||
case l <- true:
|
||||
default:
|
||||
}
|
||||
}
|
||||
@@ -309,7 +312,7 @@ func (fss *FileSystemScorer) lock() { fss.mutex.Lock() }
|
||||
func (fss *FileSystemScorer) unlock() { fss.mutex.Unlock() }
|
||||
|
||||
func (fss *FileSystemScorer) Start() {
|
||||
on_results := make(chan int)
|
||||
on_results := make(chan bool)
|
||||
fss.is_complete.Store(false)
|
||||
fss.keep_going.Store(true)
|
||||
if fss.scanner == nil {
|
||||
@@ -338,7 +341,7 @@ func (fss *FileSystemScorer) Change_query(query string) {
|
||||
fss.Start()
|
||||
}
|
||||
|
||||
func (fss *FileSystemScorer) worker(on_results chan int, worker_wait *sync.WaitGroup) {
|
||||
func (fss *FileSystemScorer) worker(on_results chan bool, worker_wait *sync.WaitGroup) {
|
||||
defer func() {
|
||||
fss.is_complete.Store(true)
|
||||
defer worker_wait.Done()
|
||||
|
||||
@@ -2,7 +2,11 @@ package choose_files
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@@ -23,3 +27,114 @@ func TestAsLower(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type node struct {
|
||||
name string
|
||||
children map[string]*node
|
||||
}
|
||||
|
||||
func (n node) Name() string {
|
||||
return n.name
|
||||
}
|
||||
|
||||
func (n node) IsDir() bool {
|
||||
return n.children != nil
|
||||
}
|
||||
|
||||
func (n node) String() string {
|
||||
return fmt.Sprintf("{name: %s num_children: %d}", n.name, len(n.children))
|
||||
}
|
||||
|
||||
func (n node) Type() fs.FileMode {
|
||||
if n.children == nil {
|
||||
return 0
|
||||
}
|
||||
return fs.ModeDir
|
||||
}
|
||||
|
||||
func (n node) Info() (fs.FileInfo, error) {
|
||||
return nil, fmt.Errorf("Info() not implemented")
|
||||
}
|
||||
|
||||
func random_name() string {
|
||||
length := 3 + rand.Intn(23)
|
||||
bytes := make([]byte, length)
|
||||
for i := range length {
|
||||
// Printable ASCII range: 32 (space) to 126 (~)
|
||||
bytes[i] = byte(rand.Intn(26) + 'a')
|
||||
}
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func (n *node) generate_random_tree(depth, breadth int) {
|
||||
n.children = make(map[string]*node)
|
||||
for range breadth {
|
||||
c := &node{name: random_name()}
|
||||
n.children[c.name] = c
|
||||
if depth > 0 && rand.Intn(10) < 3 {
|
||||
c.generate_random_tree(depth-1, breadth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n node) dir_entries() []fs.DirEntry {
|
||||
entries := make([]fs.DirEntry, 0, len(n.children))
|
||||
for _, v := range n.children {
|
||||
entries = append(entries, v)
|
||||
}
|
||||
return entries
|
||||
}
|
||||
|
||||
func (n node) ReadDir(name string) ([]fs.DirEntry, error) {
|
||||
if name == string(os.PathSeparator) {
|
||||
return n.dir_entries(), nil
|
||||
}
|
||||
p := &n
|
||||
for _, x := range strings.Split(strings.Trim(name, string(os.PathSeparator)), string(os.PathSeparator)) {
|
||||
c, found := p.children[x]
|
||||
if !found {
|
||||
return nil, fs.ErrNotExist
|
||||
}
|
||||
if !c.IsDir() {
|
||||
return nil, fs.ErrExist
|
||||
}
|
||||
p = c
|
||||
}
|
||||
return p.dir_entries(), nil
|
||||
}
|
||||
|
||||
func run_scoring(b *testing.B, depth, breadth int, query string) {
|
||||
b.StopTimer()
|
||||
root := node{name: string(os.PathSeparator)}
|
||||
root.generate_random_tree(depth, breadth)
|
||||
b.StartTimer()
|
||||
for range b.N {
|
||||
b.StopTimer()
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
s := NewFileSystemScorer(string(os.PathSeparator), query, false, func(err error, is_complete bool) {
|
||||
if is_complete {
|
||||
wg.Done()
|
||||
}
|
||||
})
|
||||
sc := NewFileSystemScanner(s.root_dir, make(chan bool))
|
||||
s.scanner = sc
|
||||
sc.dir_reader = root.ReadDir
|
||||
b.StartTimer()
|
||||
s.scanner.Start()
|
||||
s.Start()
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
// To run this benchmark with profiling use:
|
||||
// go test -bench=FileNameScoringWithoutQuery -benchmem -cpuprofile=/tmp/cpu.prof -memprofile=/tmp/mem.prof github.com/kovidgoyal/kitty/kittens/choose_files -o /tmp/cfexe
|
||||
func BenchmarkFileNameScoringWithoutQuery(b *testing.B) {
|
||||
run_scoring(b, 5, 20, "")
|
||||
}
|
||||
|
||||
// To run this benchmark with profiling use:
|
||||
// go test -bench=FileNameScoringWithQuery -benchmem -cpuprofile=/tmp/cpu.prof -memprofile=/tmp/mem.prof github.com/kovidgoyal/kitty/kittens/choose_files -o /tmp/cfexe
|
||||
func BenchmarkFileNameScoringWithQuery(b *testing.B) {
|
||||
run_scoring(b, 5, 30, "abc")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user