1
0
mirror of https://github.com/golang/go synced 2024-11-22 00:34:40 -07:00

io, net, http: sendfile support

Speeds up static fileserver, avoiding kernel/userspace copies.

Numbers: downloading 14 MB AppEngine Go SDK with ab (Apache Bench)
with 5 threads:

Before/after numbers:

CPU:
user    0m3.910s
sys     0m23.650s
->
user    0m0.720s
sys     0m4.890s

Time taken for tests:   8.906 seconds
->
Time taken for tests:   8.545 seconds

Percentage of the requests served within a certain time (ms)
50%     44
66%     45
75%     46
80%     46
90%     48
95%     51
98%     59
99%     71
100     74 (longest request)
->
50%     42
66%     43
75%     43
80%     44
90%     46
95%     57
98%     62
99%     63
100%    64 (longest request)

R=iant, gary.burd, rsc, bradfitz
CC=golang-dev
https://golang.org/cl/4543071
This commit is contained in:
Brad Fitzpatrick 2011-05-25 10:15:26 -07:00
parent e94eb38975
commit b0f39cc27c
7 changed files with 172 additions and 21 deletions

View File

@ -119,6 +119,27 @@ type response struct {
closeAfterReply bool closeAfterReply bool
} }
type writerOnly struct {
io.Writer
}
func (r *response) ReadFrom(src io.Reader) (n int64, err os.Error) {
// Flush before checking r.chunking, as Flush will call
// WriteHeader if it hasn't been called yet, and WriteHeader
// is what sets r.chunking.
r.Flush()
if !r.chunking {
if rf, ok := r.conn.rwc.(io.ReaderFrom); ok {
n, err = rf.ReadFrom(src)
r.written += n
return
}
}
// Fall back to default io.Copy implementation.
// Use wrapper to hide r.ReadFrom from io.Copy.
return io.Copy(writerOnly{r}, src)
}
// Create new connection from rwc. // Create new connection from rwc.
func newConn(rwc net.Conn, handler Handler) (c *conn, err os.Error) { func newConn(rwc net.Conn, handler Handler) (c *conn, err os.Error) {
c = new(conn) c = new(conn)

View File

@ -303,22 +303,26 @@ func Copy(dst Writer, src Reader) (written int64, err os.Error) {
// LimitReader returns a Reader that reads from r // LimitReader returns a Reader that reads from r
// but stops with os.EOF after n bytes. // but stops with os.EOF after n bytes.
func LimitReader(r Reader, n int64) Reader { return &limitedReader{r, n} } // The underlying implementation is a *LimitedReader.
func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }
type limitedReader struct { // A LimitedReader reads from R but limits the amount of
r Reader // data returned to just N bytes. Each call to Read
n int64 // updates N to reflect the new amount remaining.
type LimitedReader struct {
R Reader // underlying reader
N int64 // max bytes remaining
} }
func (l *limitedReader) Read(p []byte) (n int, err os.Error) { func (l *LimitedReader) Read(p []byte) (n int, err os.Error) {
if l.n <= 0 { if l.N <= 0 {
return 0, os.EOF return 0, os.EOF
} }
if int64(len(p)) > l.n { if int64(len(p)) > l.N {
p = p[0:l.n] p = p[0:l.N]
} }
n, err = l.r.Read(p) n, err = l.R.Read(p)
l.n -= int64(n) l.N -= int64(n)
return return
} }

View File

@ -23,12 +23,13 @@ GOFILES=\
unixsock.go\ unixsock.go\
GOFILES_freebsd=\ GOFILES_freebsd=\
newpollserver.go\ dnsclient.go\
dnsconfig.go\
fd.go\ fd.go\
file.go\ file.go\
dnsconfig.go\ newpollserver.go\
dnsclient.go\
port.go\ port.go\
sendfile_stub.go\
sock_bsd.go\ sock_bsd.go\
CGOFILES_freebsd=\ CGOFILES_freebsd=\
@ -36,27 +37,32 @@ CGOFILES_freebsd=\
cgo_unix.go\ cgo_unix.go\
GOFILES_darwin=\ GOFILES_darwin=\
newpollserver.go\ dnsclient.go\
dnsconfig.go\
fd.go\ fd.go\
file.go\ file.go\
dnsconfig.go\ newpollserver.go\
dnsclient.go\
port.go\ port.go\
sendfile_stub.go\
sock_bsd.go\ sock_bsd.go\
CGOFILES_darwin=\ CGOFILES_darwin=\
cgo_bsd.go\ cgo_bsd.go\
cgo_unix.go\ cgo_unix.go\
GOFILES_linux=\ GOFILES_linux=\
newpollserver.go\ dnsclient.go\
dnsconfig.go\
fd.go\ fd.go\
file.go\ file.go\
dnsconfig.go\ newpollserver.go\
dnsclient.go\
port.go\ port.go\
sendfile_linux.go\
sock_linux.go\ sock_linux.go\
GOFILES_plan9=\
sendfile_stub.go\
ifeq ($(GOARCH),arm) ifeq ($(GOARCH),arm)
# ARM has no cgo, so use the stubs. # ARM has no cgo, so use the stubs.
GOFILES_linux+=cgo_stub.go GOFILES_linux+=cgo_stub.go
@ -68,8 +74,9 @@ endif
GOFILES_windows=\ GOFILES_windows=\
cgo_stub.go\ cgo_stub.go\
resolv_windows.go\
file_windows.go\ file_windows.go\
resolv_windows.go\
sendfile_stub.go\
sock_windows.go\ sock_windows.go\
GOFILES+=$(GOFILES_$(GOOS)) GOFILES+=$(GOFILES_$(GOOS))

View File

@ -0,0 +1,84 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package net
import (
"io"
"os"
"syscall"
)
// maxSendfileSize is the largest chunk size we ask the kernel to copy
// at a time.
const maxSendfileSize int = 4 << 20
// sendFile copies the contents of r to c using the sendfile
// system call to minimize copies.
//
// if handled == true, sendFile returns the number of bytes copied and any
// non-EOF error.
//
// if handled == false, sendFile performed no work.
func sendFile(c *netFD, r io.Reader) (written int64, err os.Error, handled bool) {
var remain int64 = 1 << 62 // by default, copy until EOF
lr, ok := r.(*io.LimitedReader)
if ok {
remain, r = lr.N, lr.R
if remain <= 0 {
return 0, nil, true
}
}
f, ok := r.(*os.File)
if !ok {
return 0, nil, false
}
c.wio.Lock()
defer c.wio.Unlock()
c.incref()
defer c.decref()
if c.wdeadline_delta > 0 {
// This is a little odd that we're setting the timeout
// for the entire file but Write has the same issue
// (if one slurps the whole file into memory and
// do one large Write). At least they're consistent.
c.wdeadline = pollserver.Now() + c.wdeadline_delta
} else {
c.wdeadline = 0
}
dst := c.sysfd
src := f.Fd()
for remain > 0 {
n := maxSendfileSize
if int64(n) > remain {
n = int(remain)
}
n, errno := syscall.Sendfile(dst, src, nil, n)
if n > 0 {
written += int64(n)
remain -= int64(n)
}
if n == 0 && errno == 0 {
break
}
if errno == syscall.EAGAIN && c.wdeadline >= 0 {
pollserver.WaitWrite(c)
continue
}
if errno != 0 {
// This includes syscall.ENOSYS (no kernel
// support) and syscall.EINVAL (fd types which
// don't implement sendfile together)
err = &OpError{"sendfile", c.net, c.raddr, os.Errno(errno)}
break
}
}
if lr != nil {
lr.N = remain
}
return written, err, written > 0
}

View File

@ -0,0 +1,14 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package net
import (
"io"
"os"
)
func sendFile(c *netFD, r io.Reader) (n int64, err os.Error, handled bool) {
return 0, nil, false
}

View File

@ -7,6 +7,7 @@
package net package net
import ( import (
"io"
"os" "os"
"reflect" "reflect"
"syscall" "syscall"
@ -153,3 +154,14 @@ type UnknownSocketError struct {
func (e *UnknownSocketError) String() string { func (e *UnknownSocketError) String() string {
return "unknown socket address type " + reflect.TypeOf(e.sa).String() return "unknown socket address type " + reflect.TypeOf(e.sa).String()
} }
type writerOnly struct {
io.Writer
}
// Fallback implementation of io.ReaderFrom's ReadFrom, when sendfile isn't
// applicable.
func genericReadFrom(w io.Writer, r io.Reader) (n int64, err os.Error) {
// Use wrapper to hide existing r.ReadFrom from io.Copy.
return io.Copy(writerOnly{w}, r)
}

View File

@ -7,6 +7,7 @@
package net package net
import ( import (
"io"
"os" "os"
"syscall" "syscall"
) )
@ -95,6 +96,14 @@ func (c *TCPConn) Read(b []byte) (n int, err os.Error) {
return c.fd.Read(b) return c.fd.Read(b)
} }
// ReadFrom implements the io.ReaderFrom ReadFrom method.
func (c *TCPConn) ReadFrom(r io.Reader) (int64, os.Error) {
if n, err, handled := sendFile(c.fd, r); handled {
return n, err
}
return genericReadFrom(c, r)
}
// Write implements the net.Conn Write method. // Write implements the net.Conn Write method.
func (c *TCPConn) Write(b []byte) (n int, err os.Error) { func (c *TCPConn) Write(b []byte) (n int, err os.Error) {
if !c.ok() { if !c.ok() {