Skip to content

Commit 1828bd4

Browse files
Optimize fs to have 0 allocations (#2052)
When using caching fs shouldn't cause any allocations. Only do []byte to string conversions when really needed. When a file is already cached the conversion shouldn't be needed. Fixes #2045
1 parent 81ebee8 commit 1828bd4

File tree

2 files changed

+84
-44
lines changed

2 files changed

+84
-44
lines changed

allocation_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,31 @@ func TestAllocationURI(t *testing.T) {
8181
t.Fatalf("expected 0 allocations, got %f", n)
8282
}
8383
}
84+
85+
func TestAllocationFS(t *testing.T) {
86+
// Create a simple test filesystem handler
87+
fs := &FS{
88+
Root: ".",
89+
GenerateIndexPages: false,
90+
Compress: false,
91+
AcceptByteRange: false,
92+
}
93+
h := fs.NewRequestHandler()
94+
95+
ctx := &RequestCtx{}
96+
97+
n := testing.AllocsPerRun(100, func() {
98+
ctx.Request.Reset()
99+
ctx.Response.Reset()
100+
ctx.Request.SetRequestURI("/allocation_test.go")
101+
ctx.Request.Header.Set("Host", "localhost")
102+
103+
h(ctx)
104+
})
105+
106+
t.Logf("FS operations allocate %f times per request", n)
107+
108+
if n > 0 {
109+
t.Fatalf("expected 0 allocations, got %f", n)
110+
}
111+
}

fs.go

Lines changed: 56 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -553,16 +553,12 @@ type fsFile struct {
553553

554554
func (ff *fsFile) NewReader() (io.Reader, error) {
555555
if ff.isBig() {
556-
r, err := ff.bigFileReader()
557-
if err != nil {
558-
ff.decReadersCount()
559-
}
560-
return r, err
556+
return ff.bigFileReader()
561557
}
562-
return ff.smallFileReader()
558+
return ff.smallFileReader(), nil
563559
}
564560

565-
func (ff *fsFile) smallFileReader() (io.Reader, error) {
561+
func (ff *fsFile) smallFileReader() io.Reader {
566562
v := ff.h.smallFileReaderPool.Get()
567563
if v == nil {
568564
v = &fsSmallFileReader{}
@@ -571,9 +567,9 @@ func (ff *fsFile) smallFileReader() (io.Reader, error) {
571567
r.ff = ff
572568
r.endPos = ff.contentLength
573569
if r.startPos > 0 {
574-
return nil, errors.New("bug: fsSmallFileReader with non-nil startPos found in the pool")
570+
panic("bug: fsSmallFileReader with non-nil startPos found in the pool")
575571
}
576-
return r, nil
572+
return r
577573
}
578574

579575
// Files bigger than this size are sent with sendfile.
@@ -631,12 +627,12 @@ func (ff *fsFile) Release() {
631627
}
632628

633629
func (ff *fsFile) decReadersCount() {
634-
ff.h.cacheManager.WithLock(func() {
635-
ff.readersCount--
636-
if ff.readersCount < 0 {
637-
ff.readersCount = 0
638-
}
639-
})
630+
ff.h.cacheManager.Lock()
631+
ff.readersCount--
632+
if ff.readersCount < 0 {
633+
panic("bug: fsFile.readersCount < 0")
634+
}
635+
ff.h.cacheManager.Unlock()
640636
}
641637

642638
// bigFileReader attempts to trigger sendfile
@@ -796,9 +792,10 @@ func (r *fsSmallFileReader) WriteTo(w io.Writer) (int64, error) {
796792
}
797793

798794
type cacheManager interface {
799-
WithLock(work func())
800-
GetFileFromCache(cacheKind CacheKind, path string) (*fsFile, bool)
801-
SetFileToCache(cacheKind CacheKind, path string, ff *fsFile) *fsFile
795+
Lock()
796+
Unlock()
797+
GetFileFromCache(cacheKind CacheKind, path []byte) (*fsFile, bool)
798+
SetFileToCache(cacheKind CacheKind, path []byte, ff *fsFile) *fsFile
802799
}
803800

804801
var (
@@ -842,19 +839,22 @@ type noopCacheManager struct {
842839
cacheLock sync.Mutex
843840
}
844841

845-
func (n *noopCacheManager) WithLock(work func()) {
842+
func (n *noopCacheManager) Lock() {
846843
n.cacheLock.Lock()
844+
}
847845

848-
work()
849-
846+
func (n *noopCacheManager) Unlock() {
850847
n.cacheLock.Unlock()
851848
}
852849

853-
func (*noopCacheManager) GetFileFromCache(cacheKind CacheKind, path string) (*fsFile, bool) {
850+
func (*noopCacheManager) GetFileFromCache(cacheKind CacheKind, path []byte) (*fsFile, bool) {
854851
return nil, false
855852
}
856853

857-
func (*noopCacheManager) SetFileToCache(cacheKind CacheKind, path string, ff *fsFile) *fsFile {
854+
func (n *noopCacheManager) SetFileToCache(cacheKind CacheKind, path []byte, ff *fsFile) *fsFile {
855+
n.cacheLock.Lock()
856+
ff.readersCount++
857+
n.cacheLock.Unlock()
858858
return ff
859859
}
860860

@@ -867,11 +867,11 @@ type inMemoryCacheManager struct {
867867
cacheLock sync.Mutex
868868
}
869869

870-
func (cm *inMemoryCacheManager) WithLock(work func()) {
870+
func (cm *inMemoryCacheManager) Lock() {
871871
cm.cacheLock.Lock()
872+
}
872873

873-
work()
874-
874+
func (cm *inMemoryCacheManager) Unlock() {
875875
cm.cacheLock.Unlock()
876876
}
877877

@@ -889,11 +889,11 @@ func (cm *inMemoryCacheManager) getFsCache(cacheKind CacheKind) map[string]*fsFi
889889
return fileCache
890890
}
891891

892-
func (cm *inMemoryCacheManager) GetFileFromCache(cacheKind CacheKind, path string) (*fsFile, bool) {
892+
func (cm *inMemoryCacheManager) GetFileFromCache(cacheKind CacheKind, path []byte) (*fsFile, bool) {
893893
fileCache := cm.getFsCache(cacheKind)
894894

895895
cm.cacheLock.Lock()
896-
ff, ok := fileCache[path]
896+
ff, ok := fileCache[string(path)]
897897
if ok {
898898
ff.readersCount++
899899
}
@@ -902,13 +902,13 @@ func (cm *inMemoryCacheManager) GetFileFromCache(cacheKind CacheKind, path strin
902902
return ff, ok
903903
}
904904

905-
func (cm *inMemoryCacheManager) SetFileToCache(cacheKind CacheKind, path string, ff *fsFile) *fsFile {
905+
func (cm *inMemoryCacheManager) SetFileToCache(cacheKind CacheKind, path []byte, ff *fsFile) *fsFile {
906906
fileCache := cm.getFsCache(cacheKind)
907907

908908
cm.cacheLock.Lock()
909-
ff1, ok := fileCache[path]
909+
ff1, ok := fileCache[string(path)]
910910
if !ok {
911-
fileCache[path] = ff
911+
fileCache[string(path)] = ff
912912
ff.readersCount++
913913
} else {
914914
ff1.readersCount++
@@ -1005,14 +1005,31 @@ func cleanCacheNolock(
10051005
return pendingFiles, filesToRelease
10061006
}
10071007

1008-
func (h *fsHandler) pathToFilePath(path string) string {
1008+
func (h *fsHandler) pathToFilePath(path []byte, hasTrailingSlash bool) string {
10091009
if _, ok := h.filesystem.(*osFS); !ok {
10101010
if len(path) < 1 {
1011-
return path
1011+
return ""
1012+
} else if len(path) == 1 && path[0] == '/' {
1013+
return ""
1014+
}
1015+
if hasTrailingSlash {
1016+
return string(path[1 : len(path)-1])
10121017
}
1013-
return path[1:]
1018+
return string(path[1:])
1019+
}
1020+
1021+
// Use byte buffer pool to avoid string concatenation allocations
1022+
b := bytebufferpool.Get()
1023+
defer bytebufferpool.Put(b)
1024+
1025+
b.B = append(b.B, h.root...)
1026+
if hasTrailingSlash {
1027+
b.B = append(b.B, path[:len(path)-1]...)
1028+
} else {
1029+
b.B = append(b.B, path...)
10141030
}
1015-
return filepath.FromSlash(h.root + path)
1031+
1032+
return filepath.FromSlash(string(b.B))
10161033
}
10171034

10181035
func (h *fsHandler) filePathToCompressed(filePath string) string {
@@ -1071,15 +1088,9 @@ func (h *fsHandler) handleRequest(ctx *RequestCtx) {
10711088
}
10721089
}
10731090

1074-
originalPathStr := string(path)
1075-
pathStr := originalPathStr
1076-
if hasTrailingSlash {
1077-
pathStr = originalPathStr[:len(originalPathStr)-1]
1078-
}
1079-
1080-
ff, ok := h.cacheManager.GetFileFromCache(fileCacheKind, originalPathStr)
1091+
ff, ok := h.cacheManager.GetFileFromCache(fileCacheKind, path)
10811092
if !ok {
1082-
filePath := h.pathToFilePath(pathStr)
1093+
filePath := h.pathToFilePath(path, hasTrailingSlash)
10831094

10841095
var err error
10851096
ff, err = h.openFSFile(filePath, mustCompress, fileEncoding)
@@ -1112,7 +1123,7 @@ func (h *fsHandler) handleRequest(ctx *RequestCtx) {
11121123
return
11131124
}
11141125

1115-
ff = h.cacheManager.SetFileToCache(fileCacheKind, originalPathStr, ff)
1126+
ff = h.cacheManager.SetFileToCache(fileCacheKind, path, ff)
11161127
}
11171128

11181129
if !ctx.IfModifiedSince(ff.lastModified) {
@@ -1123,6 +1134,7 @@ func (h *fsHandler) handleRequest(ctx *RequestCtx) {
11231134

11241135
r, err := ff.NewReader()
11251136
if err != nil {
1137+
ff.decReadersCount()
11261138
ctx.Logger().Printf("cannot obtain file reader for path=%q: %v", path, err)
11271139
ctx.Error("Internal Server Error", StatusInternalServerError)
11281140
return

0 commit comments

Comments
 (0)