diff options
author | Luke Shumaker <lukeshu@lukeshu.com> | 2023-01-30 23:07:13 -0700 |
---|---|---|
committer | Luke Shumaker <lukeshu@lukeshu.com> | 2023-01-30 23:07:13 -0700 |
commit | 9eef4dd91c36b60a2d5a68141f1d0c07e25be129 (patch) | |
tree | 4754b06e54d7e888e636472007fc1bc87aa72d72 /lib/textui | |
parent | 0134f07a4b97a455557277b2c89e0ee5ad6b2e62 (diff) | |
parent | 50a8b3eac39caccedb3ec34c150ba37e40cc2da5 (diff) |
Merge branch 'lukeshu/fast-json'
Diffstat (limited to 'lib/textui')
-rw-r--r-- | lib/textui/log.go | 62 | ||||
-rw-r--r-- | lib/textui/progress.go | 12 |
2 files changed, 46 insertions, 28 deletions
diff --git a/lib/textui/log.go b/lib/textui/log.go index f73b271..2bcd9af 100644 --- a/lib/textui/log.go +++ b/lib/textui/log.go @@ -18,12 +18,12 @@ import ( "path/filepath" "runtime" "sort" - "strconv" "strings" "sync" "time" "unicode" + "git.lukeshu.com/go/typedsync" "github.com/datawire/dlib/dlog" "github.com/spf13/pflag" ) @@ -153,7 +153,11 @@ func (l *logger) UnformattedLogf(lvl dlog.LogLevel, format string, args ...any) } var ( - logBuf bytes.Buffer + logBufPool = typedsync.Pool[*bytes.Buffer]{ + New: func() *bytes.Buffer { + return new(bytes.Buffer) + }, + } logMu sync.Mutex thisModDir string ) @@ -169,13 +173,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 +221,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 +257,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 @@ -344,35 +346,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) diff --git a/lib/textui/progress.go b/lib/textui/progress.go index 68d986f..48a3901 100644 --- a/lib/textui/progress.go +++ b/lib/textui/progress.go @@ -7,9 +7,9 @@ package textui import ( "context" "fmt" - "sync/atomic" "time" + "git.lukeshu.com/go/typedsync" "github.com/datawire/dlib/dlog" ) @@ -26,7 +26,7 @@ type Progress[T Stats] struct { cancel context.CancelFunc done chan struct{} - cur atomic.Value // Value[T] + cur typedsync.Value[T] oldStat T oldLine string } @@ -45,7 +45,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 +56,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 } |