From dc586c25206a4b41a7d8c505e3700f5704134228 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Thu, 5 Jan 2023 19:20:25 -0700 Subject: containers: Add SyncValue and SyncPool types --- lib/textui/progress.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'lib/textui') diff --git a/lib/textui/progress.go b/lib/textui/progress.go index 68d986f..1a5e7d8 100644 --- a/lib/textui/progress.go +++ b/lib/textui/progress.go @@ -7,10 +7,11 @@ package textui import ( "context" "fmt" - "sync/atomic" "time" "github.com/datawire/dlib/dlog" + + "git.lukeshu.com/btrfs-progs-ng/lib/containers" ) type Stats interface { @@ -26,7 +27,7 @@ type Progress[T Stats] struct { cancel context.CancelFunc done chan struct{} - cur atomic.Value // Value[T] + cur containers.SyncValue[T] oldStat T oldLine string } @@ -45,7 +46,7 @@ func NewProgress[T Stats](ctx context.Context, lvl dlog.LogLevel, interval time. } func (p *Progress[T]) Set(val T) { - if p.cur.Swap(val) == nil { + if _, hadOld := p.cur.Swap(val); !hadOld { go p.run() } } @@ -56,8 +57,10 @@ func (p *Progress[T]) Done() { } func (p *Progress[T]) flush(force bool) { - //nolint:forcetypeassert // It wasn't worth it to me (yet?) to make a typed wrapper around atomic.Value. - cur := p.cur.Load().(T) + cur, ok := p.cur.Load() + if !ok { + panic("should not happen") + } if !force && cur == p.oldStat { return } -- cgit v1.2.3-2-g168b From 21fc68264ae07da11ed90d1f44b9a24225c799dc Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Thu, 5 Jan 2023 19:40:32 -0700 Subject: textui: Go ahead and implement that logBufPool --- lib/textui/log.go | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'lib/textui') diff --git a/lib/textui/log.go b/lib/textui/log.go index f73b271..41c874f 100644 --- a/lib/textui/log.go +++ b/lib/textui/log.go @@ -26,6 +26,8 @@ import ( "github.com/datawire/dlib/dlog" "github.com/spf13/pflag" + + "git.lukeshu.com/btrfs-progs-ng/lib/containers" ) type LogLevelFlag struct { @@ -153,7 +155,11 @@ func (l *logger) UnformattedLogf(lvl dlog.LogLevel, format string, args ...any) } var ( - logBuf bytes.Buffer + logBufPool = containers.SyncPool[*bytes.Buffer]{ + New: func() *bytes.Buffer { + return new(bytes.Buffer) + }, + } logMu sync.Mutex thisModDir string ) @@ -169,13 +175,8 @@ func (l *logger) log(lvl dlog.LogLevel, writeMsg func(io.Writer)) { if lvl > l.lvl { return } - // This is optimized for mostly-single-threaded usage. If I cared more - // about multi-threaded performance, I'd trade in some - // memory-use/allocations and (1) instead of using a static `logBuf`, - // I'd have a `logBufPool` `sync.Pool`, and (2) have the final call to - // `l.out.Write()` be the only thing protected by `logMu`. - logMu.Lock() - defer logMu.Unlock() + logBuf, _ := logBufPool.Get() + defer logBufPool.Put(logBuf) defer logBuf.Reset() // time //////////////////////////////////////////////////////////////// @@ -222,19 +223,19 @@ func (l *logger) log(lvl dlog.LogLevel, writeMsg func(io.Writer)) { nextField = i break } - writeField(&logBuf, fieldKey, fields[fieldKey]) + writeField(logBuf, fieldKey, fields[fieldKey]) } // message ///////////////////////////////////////////////////////////// logBuf.WriteString(" : ") - writeMsg(&logBuf) + writeMsg(logBuf) // fields (late) /////////////////////////////////////////////////////// if nextField < len(fieldKeys) { logBuf.WriteString(" :") } for _, fieldKey := range fieldKeys[nextField:] { - writeField(&logBuf, fieldKey, fields[fieldKey]) + writeField(logBuf, fieldKey, fields[fieldKey]) } // caller ////////////////////////////////////////////////////////////// @@ -258,13 +259,16 @@ func (l *logger) log(lvl dlog.LogLevel, writeMsg func(io.Writer)) { logBuf.WriteString(" :") } file := f.File[strings.LastIndex(f.File, thisModDir+"/")+len(thisModDir+"/"):] - fmt.Fprintf(&logBuf, " (from %s:%d)", file, f.Line) + fmt.Fprintf(logBuf, " (from %s:%d)", file, f.Line) break } // boilerplate ///////////////////////////////////////////////////////// logBuf.WriteByte('\n') + + logMu.Lock() _, _ = l.out.Write(logBuf.Bytes()) + logMu.Unlock() } // fieldOrd returns the sort-position for a given log-field-key. Lower return -- cgit v1.2.3-2-g168b From 9c5d61133af93ddf279e355a06d4c1ae78c568b3 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Thu, 5 Jan 2023 19:41:12 -0700 Subject: textui: Avoid allocating strings when logging --- lib/textui/log.go | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) (limited to 'lib/textui') diff --git a/lib/textui/log.go b/lib/textui/log.go index 41c874f..605755d 100644 --- a/lib/textui/log.go +++ b/lib/textui/log.go @@ -18,7 +18,6 @@ import ( "path/filepath" "runtime" "sort" - "strconv" "strings" "sync" "time" @@ -348,35 +347,49 @@ func fieldOrd(key string) int { } func writeField(w io.Writer, key string, val any) { - valStr := printer.Sprint(val) + valBuf, _ := logBufPool.Get() + defer func() { + // The wrapper `func()` is important to defer + // evaluating `valBuf`, since we might re-assign it + // below. + valBuf.Reset() + logBufPool.Put(valBuf) + }() + _, _ = printer.Fprint(valBuf, val) needsQuote := false - if strings.HasPrefix(valStr, `"`) { + if bytes.HasPrefix(valBuf.Bytes(), []byte(`"`)) { needsQuote = true } else { - for _, r := range valStr { - if !(unicode.IsPrint(r) && r != ' ') { + for _, r := range valBuf.Bytes() { + if !(unicode.IsPrint(rune(r)) && r != ' ') { needsQuote = true break } } } if needsQuote { - valStr = strconv.Quote(valStr) + valBuf2, _ := logBufPool.Get() + fmt.Fprintf(valBuf2, "%q", valBuf.Bytes()) + valBuf.Reset() + logBufPool.Put(valBuf) + valBuf = valBuf2 } + valStr := valBuf.Bytes() name := key switch { case name == "THREAD": name = "thread" - switch valStr { - case "", "/main": + switch { + case len(valStr) == 0 || bytes.Equal(valStr, []byte("/main")): return default: - if strings.HasPrefix(valStr, "/main/") { - valStr = strings.TrimPrefix(valStr, "/main") + if bytes.HasPrefix(valStr, []byte("/main/")) { + valStr = valStr[len("/main/"):] + } else if bytes.HasPrefix(valStr, []byte("/")) { + valStr = valStr[len("/"):] } - valStr = strings.TrimPrefix(valStr, "/") } case strings.HasSuffix(name, ".pass"): fmt.Fprintf(w, "/pass-%s", valStr) -- cgit v1.2.3-2-g168b From 86063b41fc1a235930d6c79e6b7cd38ae8d8c147 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Thu, 26 Jan 2023 14:53:37 -0700 Subject: Split lib/containers.Sync* to git.lukeshu.com/go/typedsync --- lib/textui/log.go | 5 ++--- lib/textui/progress.go | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) (limited to 'lib/textui') diff --git a/lib/textui/log.go b/lib/textui/log.go index 605755d..2bcd9af 100644 --- a/lib/textui/log.go +++ b/lib/textui/log.go @@ -23,10 +23,9 @@ import ( "time" "unicode" + "git.lukeshu.com/go/typedsync" "github.com/datawire/dlib/dlog" "github.com/spf13/pflag" - - "git.lukeshu.com/btrfs-progs-ng/lib/containers" ) type LogLevelFlag struct { @@ -154,7 +153,7 @@ func (l *logger) UnformattedLogf(lvl dlog.LogLevel, format string, args ...any) } var ( - logBufPool = containers.SyncPool[*bytes.Buffer]{ + logBufPool = typedsync.Pool[*bytes.Buffer]{ New: func() *bytes.Buffer { return new(bytes.Buffer) }, diff --git a/lib/textui/progress.go b/lib/textui/progress.go index 1a5e7d8..48a3901 100644 --- a/lib/textui/progress.go +++ b/lib/textui/progress.go @@ -9,9 +9,8 @@ import ( "fmt" "time" + "git.lukeshu.com/go/typedsync" "github.com/datawire/dlib/dlog" - - "git.lukeshu.com/btrfs-progs-ng/lib/containers" ) type Stats interface { @@ -27,7 +26,7 @@ type Progress[T Stats] struct { cancel context.CancelFunc done chan struct{} - cur containers.SyncValue[T] + cur typedsync.Value[T] oldStat T oldLine string } -- cgit v1.2.3-2-g168b