From 90a1d9978546bbd232e56c5231a59f7d95916286 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Sat, 23 May 2015 20:38:41 +0200 Subject: [PATCH] Reuse a timer instead of allocating a new one in subscription.Poll This is surprisingly memory expensive when Poll gets called a lot, such as when syncing lots of small files generating itemstarted/itemfinished events. It's line number three in this heap profile on the TestBenchmarkManyFiles test: jb@syno:~/src/github.com/syncthing/syncthing/test (master) $ go tool pprof ../bin/syncthing heap-13194.pprof Entering interactive mode (type "help" for commands) (pprof) top 80.91MB of 83.05MB total (97.42%) Dropped 1024 nodes (cum <= 0.42MB) Showing top 10 nodes out of 85 (cum >= 1.75MB) flat flat% sum% cum cum% 32MB 38.53% 38.53% 32.01MB 38.54% github.com/syndtr/goleveldb/leveldb/memdb.New 22.16MB 26.68% 65.21% 22.16MB 26.68% github.com/syndtr/goleveldb/leveldb/util.(*BufferPool).Get 13.02MB 15.68% 80.89% 13.02MB 15.68% time.NewTimer 6.94MB 8.35% 89.24% 6.94MB 8.35% github.com/syndtr/goleveldb/leveldb/memdb.(*DB).Put 3.18MB 3.82% 93.06% 3.18MB 3.82% github.com/calmh/xdr.(*Reader).ReadBytesMaxInto With this change the allocation is removed entirely. --- internal/events/events.go | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/internal/events/events.go b/internal/events/events.go index 09523c324..f25f0991d 100644 --- a/internal/events/events.go +++ b/internal/events/events.go @@ -100,9 +100,10 @@ type Event struct { } type Subscription struct { - mask EventType - id int - events chan Event + mask EventType + id int + events chan Event + timeout *time.Timer } var Default = NewLogger() @@ -149,9 +150,10 @@ func (l *Logger) Subscribe(mask EventType) *Subscription { dl.Debugln("subscribe", mask) } s := &Subscription{ - mask: mask, - id: l.nextID, - events: make(chan Event, BufferSize), + mask: mask, + id: l.nextID, + events: make(chan Event, BufferSize), + timeout: time.NewTimer(0), } l.nextID++ l.subs[s.id] = s @@ -169,19 +171,22 @@ func (l *Logger) Unsubscribe(s *Subscription) { l.mutex.Unlock() } +// Poll returns an event from the subscription or an error if the poll times +// out of the event channel is closed. Poll should not be called concurrently +// from multiple goroutines for a single subscription. func (s *Subscription) Poll(timeout time.Duration) (Event, error) { if debug { dl.Debugln("poll", timeout) } - to := time.After(timeout) + s.timeout.Reset(timeout) select { case e, ok := <-s.events: if !ok { return e, ErrClosed } return e, nil - case <-to: + case <-s.timeout.C: return Event{}, ErrTimeout } }