Skip to content

Commit 43d99e1

Browse files
committed
perf: bulk calls to Otter
1 parent b1bdce3 commit 43d99e1

File tree

3 files changed

+47
-16
lines changed

3 files changed

+47
-16
lines changed

cgi.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,18 +187,39 @@ func packCgiVariable(key *C.zend_string, value string) C.ht_key_value_pair {
187187
}
188188

189189
func addHeadersToServer(ctx context.Context, request *http.Request, trackVarsArray *C.zval) {
190+
var totalCommonHeaders int
191+
190192
for field, val := range request.Header {
191193
if k := mainThread.commonHeaders[field]; k != nil {
194+
totalCommonHeaders++
192195
v := strings.Join(val, ", ")
193196
C.frankenphp_register_single(k, toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
197+
}
198+
}
199+
200+
if totalCommonHeaders == len(request.Header) {
201+
return
202+
}
203+
204+
// if the header name could not be cached, it needs to be registered safely
205+
// this is more inefficient but allows additional sanitizing by PHP
206+
nbUncommonHeaders := len(request.Header)-totalCommonHeaders
207+
uncommonKeys := make([]string, nbUncommonHeaders)
208+
uncommonHeaders := make(map[string]string, nbUncommonHeaders)
209+
var i int
210+
211+
for field, val := range request.Header {
212+
if k := mainThread.commonHeaders[field]; k != nil {
194213
continue
195214
}
196215

197-
// if the header name could not be cached, it needs to be registered safely
198-
// this is more inefficient but allows additional sanitizing by PHP
199-
k := phpheaders.GetUnCommonHeader(ctx, field)
200-
v := strings.Join(val, ", ")
201-
C.frankenphp_register_variable_safe(toUnsafeChar(k), toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
216+
uncommonKeys[i] = field
217+
uncommonHeaders[field] = strings.Join(val, ", ")
218+
}
219+
220+
keys := phpheaders.GetUnCommonHeaders(ctx, uncommonKeys)
221+
for k, v := range uncommonHeaders {
222+
C.frankenphp_register_variable_safe(toUnsafeChar(keys[k]), toUnsafeChar(v), C.size_t(len(v)), trackVarsArray)
202223
}
203224
}
204225

internal/phpheaders/phpheaders.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,21 +118,28 @@ var CommonRequestHeaders = map[string]string{
118118

119119
// Cache up to 256 uncommon headers
120120
// This is ~2.5x faster than converting the header each time
121-
var headerKeyCache = otter.Must[string, string](&otter.Options[string, string]{MaximumSize: 256})
121+
var (
122+
headerKeyCache = otter.Must[string, string](&otter.Options[string, string]{MaximumSize: 256})
123+
headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
124+
bulkLoader = otter.BulkLoaderFunc[string, string](func(ctx context.Context, keys []string) (map[string]string, error) {
125+
result := make(map[string]string, len(keys))
126+
for _, k := range keys {
127+
result[k] = "HTTP_" + headerNameReplacer.Replace(strings.ToUpper(k)) + "\x00"
128+
}
122129

123-
var headerNameReplacer = strings.NewReplacer(" ", "_", "-", "_")
130+
return result, nil
131+
})
132+
)
124133

125-
func GetUnCommonHeader(ctx context.Context, key string) string {
126-
phpHeaderKey, err := headerKeyCache.Get(
134+
func GetUnCommonHeaders(ctx context.Context, keys []string) map[string]string {
135+
phpHeaderKeys, err := headerKeyCache.BulkGet(
127136
ctx,
128-
key,
129-
otter.LoaderFunc[string, string](func(_ context.Context, key string) (string, error) {
130-
return "HTTP_" + headerNameReplacer.Replace(strings.ToUpper(key)) + "\x00", nil
131-
}),
137+
keys,
138+
bulkLoader,
132139
)
133140
if err != nil {
134141
panic(err)
135142
}
136143

137-
return phpHeaderKey
144+
return phpHeaderKeys
138145
}

phpmainthread_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ package frankenphp
33
import (
44
"io"
55
"log/slog"
6+
"maps"
67
"math/rand/v2"
78
"net/http/httptest"
89
"path/filepath"
910
"runtime"
11+
"slices"
1012
"sync"
1113
"sync/atomic"
1214
"testing"
@@ -249,12 +251,13 @@ func allPossibleTransitions(worker1Path string, worker2Path string) []func(*phpT
249251
}
250252

251253
func TestAllCommonHeadersAreCorrect(t *testing.T) {
254+
keys := slices.Collect(maps.Keys(phpheaders.CommonRequestHeaders))
255+
uncommonHeaders := phpheaders.GetUnCommonHeaders(t.Context(), keys)
252256
fakeRequest := httptest.NewRequest("GET", "http://localhost", nil)
253257

254258
for header, phpHeader := range phpheaders.CommonRequestHeaders {
255259
// verify that common and uncommon headers return the same result
256-
expectedPHPHeader := phpheaders.GetUnCommonHeader(t.Context(), header)
257-
assert.Equal(t, phpHeader+"\x00", expectedPHPHeader, "header is not well formed: "+phpHeader)
260+
assert.Equal(t, phpHeader+"\x00", uncommonHeaders[header], "header is not well formed: "+phpHeader)
258261

259262
// net/http will capitalize lowercase headers, verify that headers are capitalized
260263
fakeRequest.Header.Add(header, "foo")

0 commit comments

Comments
 (0)