From a3fd75d2379eebcbc4ec99f0044bee9236a25474 Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Thu, 26 Nov 2015 17:04:27 +0200 Subject: [PATCH] Eliminated memory allocations from client's DoTimeout and GetTimeout --- client.go | 38 +++++++++++++++++++++++--------------- client_timing_test.go | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/client.go b/client.go index 5f2bce9..6b97da7 100644 --- a/client.go +++ b/client.go @@ -468,14 +468,19 @@ func clientGetURLTimeout(dst []byte, url string, timeout time.Duration, c client } } +type clientURLResponse struct { + statusCode int + body []byte + err error +} + func clientGetURLTimeoutFreeConn(dst []byte, url string, timeout time.Duration, c clientDoer) (statusCode int, body []byte, err error) { - var ch chan error + var ch chan clientURLResponse chv := errorChPool.Get() if chv == nil { - ch = make(chan error, 1) - } else { - ch = chv.(chan error) + chv = make(chan clientURLResponse, 1) } + ch = chv.(chan clientURLResponse) req := acquireRequest() @@ -486,29 +491,32 @@ func clientGetURLTimeoutFreeConn(dst []byte, url string, timeout time.Duration, // Without this 'hack' the load on slow host could exceed MaxConns* // concurrent requests, since timed out requests on client side // usually continue execution on the host. - var statusCodeCopy int - var bodyCopy []byte go func() { - var errCopy error - statusCodeCopy, bodyCopy, errCopy = doRequest(req, dst, url, c) - ch <- errCopy + statusCodeCopy, bodyCopy, errCopy := doRequest(req, dst, url, c) + ch <- clientURLResponse{ + statusCode: statusCodeCopy, + body: bodyCopy, + err: errCopy, + } }() var tc *time.Timer tcv := timerPool.Get() if tcv == nil { tc = time.NewTimer(timeout) + tcv = tc } else { tc = tcv.(*time.Timer) initTimer(tc, timeout) } select { - case err = <-ch: + case resp := <-ch: releaseRequest(req) errorChPool.Put(chv) - statusCode = statusCodeCopy - body = bodyCopy + statusCode = resp.statusCode + body = resp.body + err = resp.err case <-tc.C: body = dst err = ErrTimeout @@ -625,10 +633,9 @@ func clientDoTimeoutFreeConn(req *Request, resp *Response, timeout time.Duration var ch chan error chv := errorChPool.Get() if chv == nil { - ch = make(chan error, 1) - } else { - ch = chv.(chan error) + chv = make(chan error, 1) } + ch = chv.(chan error) // Make req and resp copies, since on timeout they no longer // may be accessed. @@ -651,6 +658,7 @@ func clientDoTimeoutFreeConn(req *Request, resp *Response, timeout time.Duration tcv := timerPool.Get() if tcv == nil { tc = time.NewTimer(timeout) + tcv = tc } else { tc = tcv.(*time.Timer) initTimer(tc, timeout) diff --git a/client_timing_test.go b/client_timing_test.go index 3166600..ec8f5b8 100644 --- a/client_timing_test.go +++ b/client_timing_test.go @@ -69,7 +69,37 @@ func acquireFakeServerConn(s []byte) *fakeClientConn { var fakeClientConnPool sync.Pool -func BenchmarkClientGetFastServer(b *testing.B) { +func BenchmarkClientGetTimeoutFastServer(b *testing.B) { + body := []byte("123456789099") + s := []byte(fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s", len(body), body)) + c := &Client{ + Dial: func(addr string) (net.Conn, error) { + return acquireFakeServerConn(s), nil + }, + } + + nn := uint32(0) + b.RunParallel(func(pb *testing.PB) { + url := fmt.Sprintf("http://foobar%d.com/aaa/bbb", atomic.AddUint32(&nn, 1)) + var statusCode int + var bodyBuf []byte + var err error + for pb.Next() { + statusCode, bodyBuf, err = c.GetTimeout(bodyBuf[:0], url, time.Second) + if err != nil { + b.Fatalf("unexpected error: %s", err) + } + if statusCode != StatusOK { + b.Fatalf("unexpected status code: %d", statusCode) + } + if !bytes.Equal(bodyBuf, body) { + b.Fatalf("unexpected response body: %q. Expected %q", bodyBuf, body) + } + } + }) +} + +func BenchmarkClientDoFastServer(b *testing.B) { body := []byte("012345678912") s := []byte(fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s", len(body), body)) c := &Client{ @@ -97,7 +127,7 @@ func BenchmarkClientGetFastServer(b *testing.B) { }) } -func BenchmarkNetHTTPClientGetFastServer(b *testing.B) { +func BenchmarkNetHTTPClientDoFastServer(b *testing.B) { body := []byte("012345678912") s := []byte(fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s", len(body), body)) c := &http.Client{