{client,agent}: don't alloc on client.Do

This commit is contained in:
Aleksandr Razumov
2017-12-15 15:06:49 +03:00
parent 0c5e708c79
commit cbb64515aa
7 changed files with 134 additions and 123 deletions

View File

@@ -73,46 +73,47 @@ goos: linux
goarch: amd64
pkg: github.com/gortc/stun
PASS
benchmark iter time/iter throughput bytes alloc allocs
--------- ---- --------- ---------- ----------- ------
BenchmarkMappedAddress_AddTo-12 100000000 22.50 ns/op 0 B/op 0 allocs/op
BenchmarkAlternateServer_AddTo-12 100000000 22.20 ns/op 0 B/op 0 allocs/op
BenchmarkAgent_GC-12 1000000 2038.00 ns/op 0 B/op 0 allocs/op
BenchmarkAgent_Process-12 30000000 47.60 ns/op 0 B/op 0 allocs/op
BenchmarkMessage_GetNotFound-12 300000000 4.29 ns/op 0 B/op 0 allocs/op
BenchmarkClient_Do-12 2000000 534.00 ns/op 16 B/op 1 allocs/op
BenchmarkErrorCode_AddTo-12 30000000 42.00 ns/op 0 B/op 0 allocs/op
BenchmarkErrorCodeAttribute_AddTo-12 50000000 30.90 ns/op 0 B/op 0 allocs/op
BenchmarkErrorCodeAttribute_GetFrom-12 200000000 7.78 ns/op 0 B/op 0 allocs/op
BenchmarkFingerprint_AddTo-12 30000000 47.30 ns/op 931.09 MB/s 0 B/op 0 allocs/op
BenchmarkFingerprint_Check-12 50000000 38.20 ns/op 1360.04 MB/s 0 B/op 0 allocs/op
BenchmarkBuildOverhead/Build-12 10000000 139.00 ns/op 0 B/op 0 allocs/op
BenchmarkBuildOverhead/BuildNonPointer-12 5000000 249.00 ns/op 100 B/op 4 allocs/op
BenchmarkBuildOverhead/Raw-12 20000000 114.00 ns/op 0 B/op 0 allocs/op
BenchmarkMessageIntegrity_AddTo-12 2000000 1022.00 ns/op 19.56 MB/s 480 B/op 6 allocs/op
BenchmarkMessageIntegrity_Check-12 1000000 1084.00 ns/op 29.50 MB/s 480 B/op 6 allocs/op
BenchmarkMessage_Write-12 100000000 16.10 ns/op 1743.03 MB/s 0 B/op 0 allocs/op
BenchmarkMessageType_Value-12 2000000000 0.23 ns/op 0 B/op 0 allocs/op
BenchmarkMessage_WriteTo-12 200000000 8.11 ns/op 0 B/op 0 allocs/op
BenchmarkMessage_ReadFrom-12 100000000 18.30 ns/op 1095.75 MB/s 0 B/op 0 allocs/op
BenchmarkMessage_ReadBytes-12 100000000 10.70 ns/op 1870.83 MB/s 0 B/op 0 allocs/op
BenchmarkIsMessage-12 2000000000 0.68 ns/op 29576.35 MB/s 0 B/op 0 allocs/op
BenchmarkMessage_NewTransactionID-12 3000000 393.00 ns/op 0 B/op 0 allocs/op
BenchmarkMessageFull-12 10000000 138.00 ns/op 0 B/op 0 allocs/op
BenchmarkMessageFullHardcore-12 30000000 53.90 ns/op 0 B/op 0 allocs/op
BenchmarkMessage_WriteHeader-12 300000000 5.45 ns/op 0 B/op 0 allocs/op
BenchmarkUsername_AddTo-12 100000000 15.00 ns/op 0 B/op 0 allocs/op
BenchmarkUsername_GetFrom-12 100000000 11.90 ns/op 0 B/op 0 allocs/op
BenchmarkNonce_AddTo-12 100000000 20.70 ns/op 0 B/op 0 allocs/op
BenchmarkNonce_AddTo_BadLength-12 100000000 27.40 ns/op 32 B/op 1 allocs/op
BenchmarkNonce_GetFrom-12 200000000 11.90 ns/op 0 B/op 0 allocs/op
BenchmarkUnknownAttributes/AddTo-12 100000000 18.90 ns/op 0 B/op 0 allocs/op
BenchmarkUnknownAttributes/GetFrom-12 100000000 13.70 ns/op 0 B/op 0 allocs/op
BenchmarkXOR-12 100000000 14.60 ns/op 70066.01 MB/s 0 B/op 0 allocs/op
BenchmarkXORSafe-12 20000000 98.80 ns/op 10363.12 MB/s 0 B/op 0 allocs/op
BenchmarkXORFast-12 100000000 13.80 ns/op 74327.93 MB/s 0 B/op 0 allocs/op
BenchmarkXORMappedAddress_AddTo-12 50000000 35.20 ns/op 0 B/op 0 allocs/op
BenchmarkXORMappedAddress_GetFrom-12 100000000 22.30 ns/op 0 B/op 0 allocs/op
benchmark iter time/iter throughput bytes alloc allocs
--------- ---- --------- ---------- ----------- ------
BenchmarkMappedAddress_AddTo-12 1000000000 22.90 ns/op 0 B/op 0 allocs/op
BenchmarkAlternateServer_AddTo-12 1000000000 23.00 ns/op 0 B/op 0 allocs/op
BenchmarkAgent_GC-12 10000000 2217.00 ns/op 0 B/op 0 allocs/op
BenchmarkAgent_Process-12 300000000 52.80 ns/op 0 B/op 0 allocs/op
BenchmarkMessage_GetNotFound-12 3000000000 4.13 ns/op 0 B/op 0 allocs/op
BenchmarkClient_Do-12 30000000 495.00 ns/op 0 B/op 0 allocs/op
BenchmarkErrorCode_AddTo-12 300000000 41.10 ns/op 0 B/op 0 allocs/op
BenchmarkErrorCodeAttribute_AddTo-12 500000000 30.90 ns/op 0 B/op 0 allocs/op
BenchmarkErrorCodeAttribute_GetFrom-12 2000000000 7.84 ns/op 0 B/op 0 allocs/op
BenchmarkFingerprint_AddTo-12 300000000 46.60 ns/op 944.52 MB/s 0 B/op 0 allocs/op
BenchmarkFingerprint_Check-12 500000000 37.20 ns/op 1397.50 MB/s 0 B/op 0 allocs/op
BenchmarkBuildOverhead/Build-12 100000000 133.00 ns/op 0 B/op 0 allocs/op
BenchmarkBuildOverhead/BuildNonPointer-12 100000000 262.00 ns/op 100 B/op 4 allocs/op
BenchmarkBuildOverhead/Raw-12 100000000 119.00 ns/op 0 B/op 0 allocs/op
BenchmarkMessageIntegrity_AddTo-12 20000000 1058.00 ns/op 18.89 MB/s 480 B/op 6 allocs/op
BenchmarkMessageIntegrity_Check-12 20000000 1023.00 ns/op 31.27 MB/s 480 B/op 6 allocs/op
BenchmarkMessage_Write-12 1000000000 16.10 ns/op 1734.19 MB/s 0 B/op 0 allocs/op
BenchmarkMessageType_Value-12 10000000000 0.23 ns/op 0 B/op 0 allocs/op
BenchmarkMessage_WriteTo-12 2000000000 8.19 ns/op 0 B/op 0 allocs/op
BenchmarkMessage_ReadFrom-12 1000000000 18.00 ns/op 1108.32 MB/s 0 B/op 0 allocs/op
BenchmarkMessage_ReadBytes-12 2000000000 10.50 ns/op 1896.39 MB/s 0 B/op 0 allocs/op
BenchmarkIsMessage-12 10000000000 0.64 ns/op 31241.13 MB/s 0 B/op 0 allocs/op
BenchmarkMessage_NewTransactionID-12 50000000 385.00 ns/op 0 B/op 0 allocs/op
BenchmarkMessageFull-12 100000000 134.00 ns/op 0 B/op 0 allocs/op
BenchmarkMessageFullHardcore-12 300000000 52.70 ns/op 0 B/op 0 allocs/op
BenchmarkMessage_WriteHeader-12 3000000000 5.21 ns/op 0 B/op 0 allocs/op
BenchmarkUsername_AddTo-12 1000000000 14.60 ns/op 0 B/op 0 allocs/op
BenchmarkUsername_GetFrom-12 2000000000 11.70 ns/op 0 B/op 0 allocs/op
BenchmarkNonce_AddTo-12 1000000000 20.00 ns/op 0 B/op 0 allocs/op
BenchmarkNonce_AddTo_BadLength-12 300000000 49.40 ns/op 32 B/op 1 allocs/op
BenchmarkNonce_GetFrom-12 2000000000 11.80 ns/op 0 B/op 0 allocs/op
BenchmarkUnknownAttributes/AddTo-12 1000000000 18.50 ns/op 0 B/op 0 allocs/op
BenchmarkUnknownAttributes/GetFrom-12 1000000000 13.40 ns/op 0 B/op 0 allocs/op
BenchmarkXOR-12 1000000000 14.00 ns/op 73071.52 MB/s 0 B/op 0 allocs/op
BenchmarkXORSafe-12 100000000 102.00 ns/op 9945.44 MB/s 0 B/op 0 allocs/op
BenchmarkXORFast-12 1000000000 13.60 ns/op 75332.68 MB/s 0 B/op 0 allocs/op
BenchmarkXORMappedAddress_AddTo-12 500000000 35.20 ns/op 0 B/op 0 allocs/op
BenchmarkXORMappedAddress_GetFrom-12 1000000000 22.30 ns/op 0 B/op 0 allocs/op
ok github.com/gortc/stun 698.712s
```
# development

View File

@@ -8,7 +8,7 @@ import (
// AgentOptions are required to initialize Agent.
type AgentOptions struct {
Handler AgentFn // Default handler, can be nil.
Handler Handler // Default handler, can be nil.
}
// NewAgent initializes and returns new Agent from options.
@@ -32,17 +32,28 @@ type Agent struct {
transactions map[transactionID]agentTransaction
closed bool // all calls are invalid if true
mux sync.Mutex // protects transactions and closed
zeroHandler AgentFn // handles non-registered transactions if set
zeroHandler Handler // handles non-registered transactions if set
}
// AgentFn is called on transaction state change.
// Usage of e is valid only during call, user must
// copy needed fields explicitly.
type AgentFn func(e AgentEvent)
// Handler handles state changes of transaction.
type Handler interface {
// HandleEvent is called on transaction state change.
// Usage of e is valid only during call, user must
// copy needed fields explicitly.
HandleEvent(e Event)
}
// AgentEvent is set of arguments passed to AgentFn, describing
// HandlerFunc is function that implements Handler interface.
type HandlerFunc func(e Event)
// HandleEvent implements Handler.
func (f HandlerFunc) HandleEvent(e Event) {
f(e)
}
// Event is set of arguments passed to AgentFn, describing
// an transaction event. Do not reuse outside AgentFn.
type AgentEvent struct {
type Event struct {
Message *Message
Error error
}
@@ -54,7 +65,7 @@ type AgentEvent struct {
type agentTransaction struct {
id transactionID
deadline time.Time
f AgentFn
h Handler
}
var (
@@ -81,7 +92,7 @@ func (a *Agent) StopWithError(id [TransactionIDSize]byte, err error) error {
if !exists {
return ErrTransactionNotExists
}
t.f(AgentEvent{
t.h.HandleEvent(Event{
Error: err,
})
return nil
@@ -101,7 +112,7 @@ var ErrAgentClosed = errors.New("agent is closed")
// Could return ErrAgentClosed, ErrTransactionExists.
// Callback f is guaranteed to be eventually called. See AgentFn for
// callback processing constraints.
func (a *Agent) Start(id [TransactionIDSize]byte, deadline time.Time, f AgentFn) error {
func (a *Agent) Start(id [TransactionIDSize]byte, deadline time.Time, h Handler) error {
a.mux.Lock()
defer a.mux.Unlock()
if a.closed {
@@ -113,7 +124,7 @@ func (a *Agent) Start(id [TransactionIDSize]byte, deadline time.Time, f AgentFn)
}
a.transactions[id] = agentTransaction{
id: id,
f: f,
h: h,
deadline: deadline,
}
return nil
@@ -132,7 +143,7 @@ var ErrTransactionTimeOut = errors.New("transaction is timed out")
//
// It is safe to call Collect concurrently but makes no sense.
func (a *Agent) Collect(gcTime time.Time) error {
toCall := make([]AgentFn, 0, agentCollectCap)
toCall := make([]Handler, 0, agentCollectCap)
toRemove := make([]transactionID, 0, agentCollectCap)
a.mux.Lock()
if a.closed {
@@ -149,7 +160,7 @@ func (a *Agent) Collect(gcTime time.Time) error {
for id, t := range a.transactions {
if t.deadline.Before(gcTime) {
toRemove = append(toRemove, id)
toCall = append(toCall, t.f)
toCall = append(toCall, t.h)
}
}
// Un-registering timed out transactions.
@@ -161,11 +172,11 @@ func (a *Agent) Collect(gcTime time.Time) error {
a.mux.Unlock()
// Sending ErrTransactionTimeOut to all callbacks, blocking
// Collect until last one.
event := AgentEvent{
event := Event{
Error: ErrTransactionTimeOut,
}
for _, f := range toCall {
f(event)
for _, handler := range toCall {
handler.HandleEvent(event)
}
return nil
}
@@ -175,7 +186,7 @@ func (a *Agent) Collect(gcTime time.Time) error {
// handle is not provided, message is silently ignored.
// Call blocks until handler returns.
func (a *Agent) Process(m *Message) error {
e := AgentEvent{
e := Event{
Message: m,
}
a.mux.Lock()
@@ -187,9 +198,9 @@ func (a *Agent) Process(m *Message) error {
delete(a.transactions, m.TransactionID)
a.mux.Unlock()
if ok {
t.f(e)
t.h.HandleEvent(e)
} else if a.zeroHandler != nil {
a.zeroHandler(e)
a.zeroHandler.HandleEvent(e)
}
return nil
}
@@ -197,7 +208,7 @@ func (a *Agent) Process(m *Message) error {
// Close terminates all transactions with ErrAgentClosed and renders Agent to
// closed state.
func (a *Agent) Close() error {
e := AgentEvent{
e := Event{
Error: ErrAgentClosed,
}
a.mux.Lock()
@@ -206,7 +217,7 @@ func (a *Agent) Close() error {
return ErrAgentClosed
}
for _, t := range a.transactions {
t.f(e)
t.h.HandleEvent(e)
}
a.transactions = nil
a.closed = true

View File

@@ -8,22 +8,21 @@ import (
func TestAgent_ProcessInTransaction(t *testing.T) {
m := New()
a := NewAgent(AgentOptions{
Handler: func(e AgentEvent) {
Handler: HandlerFunc(func(e Event) {
t.Error("should not be called")
},
}),
})
if err := m.NewTransactionID(); err != nil {
t.Fatal(err)
}
if err := a.Start(m.TransactionID, time.Time{}, func(e AgentEvent) {
if err := a.Start(m.TransactionID, time.Time{}, HandlerFunc(func(e Event) {
if e.Error != nil {
t.Errorf("got error: %s", e.Error)
}
if !e.Message.Equal(m) {
t.Errorf("%s (got) != %s (expected)", e.Message, m)
}
}); err != nil {
})); err != nil {
t.Fatal(err)
}
if err := a.Process(m); err != nil {
@@ -37,14 +36,14 @@ func TestAgent_ProcessInTransaction(t *testing.T) {
func TestAgent_Process(t *testing.T) {
m := New()
a := NewAgent(AgentOptions{
Handler: func(e AgentEvent) {
Handler: HandlerFunc(func(e Event) {
if e.Error != nil {
t.Errorf("got error: %s", e.Error)
}
if !e.Message.Equal(m) {
t.Errorf("%s (got) != %s (expected)", e.Message, m)
}
},
}),
})
if err := m.NewTransactionID(); err != nil {
t.Fatal(err)
@@ -91,11 +90,11 @@ func TestAgent_Stop(t *testing.T) {
t.Fatalf("unexpected error: %s, should be %s", err, ErrTransactionNotExists)
}
id := NewTransactionID()
called := make(chan AgentEvent, 1)
called := make(chan Event, 1)
timeout := time.Millisecond * 200
if err := a.Start(id, time.Now().Add(timeout), func(e AgentEvent) {
if err := a.Start(id, time.Now().Add(timeout), HandlerFunc(func(e Event) {
called <- e
}); err != nil {
})); err != nil {
t.Fatal(err)
}
if err := a.Stop(id); err != nil {
@@ -119,18 +118,18 @@ func TestAgent_Stop(t *testing.T) {
}
}
var noopHandler = func(e AgentEvent) {}
var noopHandler HandlerFunc = func(e Event) {}
func TestAgent_GC(t *testing.T) {
a := NewAgent(AgentOptions{
Handler: noopHandler,
})
shouldTimeOut := func(e AgentEvent) {
var shouldTimeOut HandlerFunc = func(e Event) {
if e.Error != ErrTransactionTimeOut {
t.Errorf("should time out, but got <%s>", e.Error)
}
}
shouldNotTimeOut := func(e AgentEvent) {
var shouldNotTimeOut HandlerFunc = func(e Event) {
if e.Error == ErrTransactionTimeOut {
t.Error("should not time out")
}

View File

@@ -65,7 +65,7 @@ type Connection interface {
type ClientAgent interface {
Process(*Message) error
Close() error
Start(id [TransactionIDSize]byte, deadline time.Time, f AgentFn) error
Start(id [TransactionIDSize]byte, deadline time.Time, f Handler) error
Stop(id [TransactionIDSize]byte) error
Collect(time.Time) error
}
@@ -184,21 +184,14 @@ func (c *Client) Indicate(m *Message) error {
return c.Start(m, time.Time{}, nil)
}
type clientCallState struct {
callback func(event AgentEvent)
// callbackWaitHandler blocks on wait() call until callback is called.
type callbackWaitHandler struct {
callback func(event Event)
cond *sync.Cond
processed bool
}
func (s *clientCallState) wait() {
s.cond.L.Lock()
for !s.processed {
s.cond.Wait()
}
s.cond.L.Unlock()
}
func (s *clientCallState) wrapper(e AgentEvent) {
func (s *callbackWaitHandler) HandleEvent(e Event) {
if s.callback == nil {
panic("s.callback is nil")
}
@@ -209,66 +202,73 @@ func (s *clientCallState) wrapper(e AgentEvent) {
s.cond.L.Unlock()
}
func (s *clientCallState) setCallback(f func(event AgentEvent)) {
func (s *callbackWaitHandler) wait() {
s.cond.L.Lock()
for !s.processed {
s.cond.Wait()
}
s.cond.L.Unlock()
}
func (s *callbackWaitHandler) setCallback(f func(event Event)) {
if f == nil {
panic("f is nil")
}
s.callback = f
}
func (s *clientCallState) reset() {
func (s *callbackWaitHandler) reset() {
s.processed = false
s.callback = nil
}
func newClientCallState(f func(event AgentEvent)) *clientCallState {
return &clientCallState{
cond: sync.NewCond(new(sync.Mutex)),
callback: f,
}
}
var clientCallStatePool = sync.Pool{
var callbackWaitHandlerPool = sync.Pool{
New: func() interface{} {
return newClientCallState(nil)
return &callbackWaitHandler{
cond: sync.NewCond(new(sync.Mutex)),
}
},
}
// Do is Start wrapper that waits until callback is called. If no callback
// provided, Indicate is called instead.
//
// Do has memory allocation overhead due to blocking, see BenchmarkClient_Do.
// Use Start for zero overhead.
func (c *Client) Do(m *Message, d time.Time, f func(AgentEvent)) error {
// Do has cpu overhead due to blocking, see BenchmarkClient_Do.
// Use Start method for less overhead.
func (c *Client) Do(m *Message, d time.Time, f func(Event)) error {
if f == nil {
return c.Indicate(m)
}
state := clientCallStatePool.Get().(*clientCallState)
state.setCallback(f)
err := c.Start(m, d, state.wrapper)
state.wait()
state.reset()
clientCallStatePool.Put(state)
return err
h := callbackWaitHandlerPool.Get().(*callbackWaitHandler)
h.setCallback(f)
defer func() {
h.reset()
callbackWaitHandlerPool.Put(h)
}()
if err := c.Start(m, d, h); err != nil {
return err
}
h.wait()
return nil
}
// Start starts transaction (if f set) and writes message to server, callback
// Start starts transaction (if f set) and writes message to server, handler
// is called asynchronously.
func (c *Client) Start(m *Message, d time.Time, f func(AgentEvent)) error {
func (c *Client) Start(m *Message, d time.Time, h Handler) error {
c.closedMux.RLock()
closed := c.closed
c.closedMux.RUnlock()
if closed {
return ErrClientClosed
}
if f != nil {
// Starting transaction only if f is set. Useful for indications.
if err := c.a.Start(m.TransactionID, d, f); err != nil {
if h != nil {
// Starting transaction only if h is set. Useful for indications.
if err := c.a.Start(m.TransactionID, d, h); err != nil {
return err
}
}
_, err := m.WriteTo(c.c)
if err != nil && f != nil {
if err != nil && h != nil {
// Stopping transaction instead of waiting until deadline.
if stopErr := c.a.Stop(m.TransactionID); stopErr != nil {
return StopErr{

View File

@@ -8,7 +8,7 @@ import (
)
type TestAgent struct {
f chan AgentFn
f chan Handler
}
func (n *TestAgent) Close() error {
@@ -20,7 +20,7 @@ func (TestAgent) Collect(time.Time) error { return nil }
func (TestAgent) Process(m *Message) error { return nil }
func (n *TestAgent) Start(id [TransactionIDSize]byte, deadline time.Time, f AgentFn) error {
func (n *TestAgent) Start(id [TransactionIDSize]byte, deadline time.Time, f Handler) error {
n.f <- f
return nil
}
@@ -47,7 +47,7 @@ func (noopConnection) Close() error {
func BenchmarkClient_Do(b *testing.B) {
b.ReportAllocs()
agent := &TestAgent{
f: make(chan AgentFn, 1000),
f: make(chan Handler, 1000),
}
client := NewClient(ClientOptions{
Agent: agent,
@@ -55,17 +55,17 @@ func BenchmarkClient_Do(b *testing.B) {
})
defer client.Close()
go func() {
e := AgentEvent{
e := Event{
Error: nil,
Message: nil,
}
for f := range agent.f {
f(e)
f.HandleEvent(e)
}
}()
m := new(Message)
m.Encode()
noopF := func(event AgentEvent) {
noopF := func(event Event) {
// pass
}
for i := 0; i < b.N; i++ {
@@ -140,7 +140,7 @@ func TestClient_Do(t *testing.T) {
m.TransactionID = response.TransactionID
m.Encode()
d := time.Now().Add(time.Second)
if err := c.Do(m, d, func(event AgentEvent) {
if err := c.Do(m, d, func(event Event) {
if event.Error != nil {
t.Error(event.Error)
}

View File

@@ -25,7 +25,7 @@ func main() {
log.Fatal("dial:", err)
}
deadline := time.Now().Add(time.Second * 5)
if err := c.Do(stun.MustBuild(stun.TransactionID, stun.BindingRequest), deadline, func(res stun.AgentEvent) {
if err := c.Do(stun.MustBuild(stun.TransactionID, stun.BindingRequest), deadline, func(res stun.Event) {
if res.Error != nil {
log.Fatalln(err)
}

View File

@@ -44,7 +44,7 @@ func main() {
}
timeout := time.Second
deadline := time.Now().Add(timeout)
if err := client.Do(request, deadline, func(event stun.AgentEvent) {
if err := client.Do(request, deadline, func(event stun.Event) {
if event.Error != nil {
log.Fatalln("got event with error:", event.Error)
}