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.
This commit is contained in:
Jakob Borg 2015-05-23 20:38:41 +02:00
parent e215cf6fb8
commit 90a1d99785

View File

@ -103,6 +103,7 @@ type Subscription struct {
mask EventType mask EventType
id int id int
events chan Event events chan Event
timeout *time.Timer
} }
var Default = NewLogger() var Default = NewLogger()
@ -152,6 +153,7 @@ func (l *Logger) Subscribe(mask EventType) *Subscription {
mask: mask, mask: mask,
id: l.nextID, id: l.nextID,
events: make(chan Event, BufferSize), events: make(chan Event, BufferSize),
timeout: time.NewTimer(0),
} }
l.nextID++ l.nextID++
l.subs[s.id] = s l.subs[s.id] = s
@ -169,19 +171,22 @@ func (l *Logger) Unsubscribe(s *Subscription) {
l.mutex.Unlock() 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) { func (s *Subscription) Poll(timeout time.Duration) (Event, error) {
if debug { if debug {
dl.Debugln("poll", timeout) dl.Debugln("poll", timeout)
} }
to := time.After(timeout) s.timeout.Reset(timeout)
select { select {
case e, ok := <-s.events: case e, ok := <-s.events:
if !ok { if !ok {
return e, ErrClosed return e, ErrClosed
} }
return e, nil return e, nil
case <-to: case <-s.timeout.C:
return Event{}, ErrTimeout return Event{}, ErrTimeout
} }
} }