Kaynağa Gözat

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.
Jakob Borg 10 yıl önce
ebeveyn
işleme
90a1d99785
1 değiştirilmiş dosya ile 13 ekleme ve 8 silme
  1. 13 8
      internal/events/events.go

+ 13 - 8
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
 	}
 }