Skip to content

Commit adac9a7

Browse files
authored
Add new benchmarks; update readme (#10)
* Add new benchmarks; update readme * Fix benchmarks
1 parent d3695ec commit adac9a7

File tree

3 files changed

+118
-38
lines changed

3 files changed

+118
-38
lines changed

README.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
# lambdamux
1+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 120">
2+
<defs>
3+
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="0%">
4+
<stop offset="0%" style="stop-color:#232F3E;stop-opacity:1" />
5+
<stop offset="100%" style="stop-color:#FF9900;stop-opacity:1" />
6+
</linearGradient>
7+
</defs>
8+
<text x="200" y="80" font-family="'Space Mono', 'Courier New', monospace" font-size="56" font-weight="bold" fill="url(#grad)" text-anchor="middle">
9+
Lambda<tspan fill="#FF9900">Mux</tspan>
10+
</text>
11+
<text x="200" y="100" font-family="'Space Mono', 'Courier New', monospace" font-size="14" fill="#8C8C8C" text-anchor="middle">HTTP Router for AWS Lambda</text>
12+
</svg>
213

314
[![Test](https://github.com/D-Andreev/lambdamux/actions/workflows/test.yml/badge.svg)](https://github.com/D-Andreev/lambdamux/actions/workflows/test.yml)
415
[![GoDoc](https://godoc.org/github.com/D-Andreev/lambdamux?status.svg)](https://godoc.org/github.com/D-Andreev/lambdamux)
@@ -151,16 +162,15 @@ To run the example locally:
151162

152163
## Benchmarks
153164

165+
Benchmarks can be run with `make benchmark` and the full benchmark code can be found [here](https://github.com/D-Andreev/lambdamux/blob/main/lambdamux_benchmark_test.go).
166+
The router used in the benchmarks consists of 50 routes in total, some static and some dynamic.
154167
The following table shows the benchmark results for `lambdamux` compared to other popular routers, including those using `aws-lambda-go-api-proxy`:
155168

156169
| Benchmark | Operations | Time per Operation | Bytes per Operation | Allocations per Operation | Using aws-lambda-go-api-proxy | % Slower than LambdaMux |
157170
|-----------|------------|---------------------|---------------------|---------------------------|-------------------------------|--------------------------|
158-
| LambdaMux | 535,137 | 2,229 ns/op | 1,852 B/op | 29 allocs/op | No | 0% |
159-
| LmdRouter | 508,597 | 2,329 ns/op | 1,615 B/op | 25 allocs/op | No | 4.49% |
160-
| AWSLambdaGoAPIProxyWithGin | 372,218 | 3,169 ns/op | 3,430 B/op | 38 allocs/op | Yes | 42.17% |
161-
| AWSLambdaGoAPIProxyWithChi | 348,360 | 3,394 ns/op | 3,786 B/op | 40 allocs/op | Yes | 52.27% |
162-
| AWSLambdaGoAPIProxyWithFiber | 259,388 | 4,572 ns/op | 5,770 B/op | 52 allocs/op | Yes | 105.11% |
163-
164-
`lambdamux` performs competitively, being slightly faster than LmdRouter and using marginally more memory. It's much faster than using `aws-lambda-go-api-proxy` with Gin, Fiber, and Chi.
171+
| LambdaMux | 382,891 | 3,134 ns/op | 2,444 B/op | 40 allocs/op | No | 0% |
172+
| [LmdRouter](https://github.com/aquasecurity/lmdrouter) | 320,187 | 3,701 ns/op | 2,086 B/op | 34 allocs/op | No | 18.09% |
173+
| [AWSLambdaGoAPIProxyWithGin](https://github.com/gin-gonic/gin) | 289,932 | 4,081 ns/op | 3,975 B/op | 47 allocs/op | Yes | 30.22% |
174+
| [AWSLambdaGoAPIProxyWithChi](https://github.com/go-chi/chi) | 268,380 | 4,384 ns/op | 4,304 B/op | 49 allocs/op | Yes | 39.89% |
175+
| [AWSLambdaGoAPIProxyWithFiber](https://github.com/gofiber/fiber) | 210,759 | 5,603 ns/op | 6,324 B/op | 61 allocs/op | Yes | 78.78% |
165176

166-
Benchmarks can be run with `make benchmark` and the full benchmark code can be found [here](https://github.com/D-Andreev/lambdamux/blob/main/lambdamux_benchmark_test.go).

internal/radix/radix.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -263,17 +263,16 @@ func (n *Node) getEdge(label byte, matchParam bool) *Node {
263263

264264
// getCommonPrefix returns the length of the longest common prefix between two strings
265265
func getCommonPrefix(k1, k2 string) int {
266-
maxL := len(k1)
267-
if l := len(k2); l < maxL {
268-
maxL = l
266+
maxLen := len(k1)
267+
if len(k2) < maxLen {
268+
maxLen = len(k2)
269269
}
270-
var i int
271-
for i = 0; i < maxL; i++ {
270+
for i := 0; i < maxLen; i++ {
272271
if k1[i] != k2[i] {
273-
break
272+
return i
274273
}
275274
}
276-
return i
275+
return maxLen
277276
}
278277

279278
// GetAllCompleteItems returns all complete items in the tree

lambdamux_benchmark_test.go

Lines changed: 93 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,41 @@ var routes = []struct {
4343
{"GET", "/user/:username"},
4444
{"PUT", "/user/:username"},
4545
{"DELETE", "/user/:username"},
46+
{"GET", "/pet/:petId/medical-history"},
47+
{"POST", "/pet/:petId/vaccination"},
48+
{"GET", "/store/order/:orderId/tracking"},
49+
{"PUT", "/store/order/:orderId/status"},
50+
{"GET", "/user/:username/preferences"},
51+
{"POST", "/user/:username/address"},
52+
{"GET", "/pet/:petId/appointments"},
53+
{"POST", "/pet/:petId/appointment"},
54+
{"PUT", "/pet/:petId/appointment/:appointmentId"},
55+
{"DELETE", "/pet/:petId/appointment/:appointmentId"},
56+
{"GET", "/store/products"},
57+
{"GET", "/store/product/:productId"},
58+
{"POST", "/store/product"},
59+
{"PUT", "/store/product/:productId"},
60+
{"DELETE", "/store/product/:productId"},
61+
{"GET", "/user/:username/orders"},
62+
{"POST", "/user/:username/review"},
63+
{"GET", "/user/:username/review/:reviewId"},
64+
{"PUT", "/user/:username/review/:reviewId"},
65+
{"DELETE", "/user/:username/review/:reviewId"},
66+
{"GET", "/clinic/:clinicId"},
67+
{"POST", "/clinic"},
68+
{"PUT", "/clinic/:clinicId"},
69+
{"DELETE", "/clinic/:clinicId"},
70+
{"GET", "/clinic/:clinicId/staff"},
71+
{"POST", "/clinic/:clinicId/staff"},
72+
{"GET", "/clinic/:clinicId/staff/:staffId"},
73+
{"PUT", "/clinic/:clinicId/staff/:staffId"},
74+
{"DELETE", "/clinic/:clinicId/staff/:staffId"},
75+
{"GET", "/clinic/:clinicId/appointments"},
76+
{"POST", "/clinic/:clinicId/appointment/:appointmentId/reschedule"},
77+
}
78+
79+
var allParams = []string{
80+
"petId", "orderId", "username", "appointmentId", "productId", "reviewId", "clinicId", "staffId",
4681
}
4782

4883
func setupLambdaMux() *LambdaMux {
@@ -84,13 +119,14 @@ func ginCreateHandler(method, path string) gin.HandlerFunc {
84119
responseBody := map[string]interface{}{
85120
"message": "Handled " + method + " request for " + path,
86121
}
87-
params := c.Params
88-
if len(params) > 0 {
89-
paramMap := make(map[string]string)
90-
for _, param := range params {
91-
paramMap[param.Key] = param.Value
122+
params := make(map[string]string)
123+
for _, param := range allParams {
124+
if value := c.Param(param); value != "" {
125+
params[param] = value
92126
}
93-
responseBody["params"] = paramMap
127+
}
128+
if len(params) > 0 {
129+
responseBody["params"] = params
94130
}
95131
c.JSON(200, responseBody)
96132
}
@@ -109,7 +145,12 @@ func fiberCreateHandler(method, path string) fiber.Handler {
109145
responseBody := map[string]interface{}{
110146
"message": "Handled " + method + " request for " + path,
111147
}
112-
params := c.AllParams()
148+
params := make(map[string]string)
149+
for _, param := range allParams {
150+
if value := c.Params(param); value != "" {
151+
params[param] = value
152+
}
153+
}
113154
if len(params) > 0 {
114155
responseBody["params"] = params
115156
}
@@ -122,7 +163,7 @@ func setupChiRouter() *chi.Mux {
122163
for _, route := range routes {
123164
// Convert :param to {param} for Chi router
124165
chiPath := route.path
125-
for _, param := range []string{"petId", "orderId", "username"} {
166+
for _, param := range allParams {
126167
chiPath = strings.Replace(chiPath, ":"+param, "{"+param+"}", -1)
127168
}
128169
r.MethodFunc(route.method, chiPath, chiCreateHandler(route.method, route.path))
@@ -137,10 +178,9 @@ func chiCreateHandler(method, path string) http.HandlerFunc {
137178
}
138179

139180
params := make(map[string]string)
140-
rctx := chi.RouteContext(r.Context())
141-
if rctx != nil {
142-
for i, key := range rctx.URLParams.Keys {
143-
params[key] = rctx.URLParams.Values[i]
181+
for _, param := range allParams {
182+
if value := chi.URLParam(r, param); value != "" {
183+
params[param] = value
144184
}
145185
}
146186

@@ -198,6 +238,37 @@ var benchmarkRequests = []events.APIGatewayProxyRequest{
198238
{HTTPMethod: "GET", Path: "/user/johndoe", PathParameters: map[string]string{"username": "johndoe"}},
199239
{HTTPMethod: "PUT", Path: "/user/janedoe", PathParameters: map[string]string{"username": "janedoe"}},
200240
{HTTPMethod: "DELETE", Path: "/user/bobsmith", PathParameters: map[string]string{"username": "bobsmith"}},
241+
{HTTPMethod: "GET", Path: "/pet/404/medical-history", PathParameters: map[string]string{"petId": "404"}},
242+
{HTTPMethod: "POST", Path: "/pet/505/vaccination", PathParameters: map[string]string{"petId": "505"}},
243+
{HTTPMethod: "GET", Path: "/store/order/606/tracking", PathParameters: map[string]string{"orderId": "606"}},
244+
{HTTPMethod: "PUT", Path: "/store/order/707/status", PathParameters: map[string]string{"orderId": "707"}},
245+
{HTTPMethod: "GET", Path: "/user/alicesmith/preferences", PathParameters: map[string]string{"username": "alicesmith"}},
246+
{HTTPMethod: "POST", Path: "/user/bobdoe/address", PathParameters: map[string]string{"username": "bobdoe"}},
247+
{HTTPMethod: "GET", Path: "/pet/808/appointments", PathParameters: map[string]string{"petId": "808"}},
248+
{HTTPMethod: "POST", Path: "/pet/909/appointment", PathParameters: map[string]string{"petId": "909"}},
249+
{HTTPMethod: "PUT", Path: "/pet/1010/appointment/2020", PathParameters: map[string]string{"petId": "1010", "appointmentId": "2020"}},
250+
{HTTPMethod: "DELETE", Path: "/pet/1111/appointment/2121", PathParameters: map[string]string{"petId": "1111", "appointmentId": "2121"}},
251+
{HTTPMethod: "GET", Path: "/store/products"},
252+
{HTTPMethod: "GET", Path: "/store/product/3030", PathParameters: map[string]string{"productId": "3030"}},
253+
{HTTPMethod: "POST", Path: "/store/product"},
254+
{HTTPMethod: "PUT", Path: "/store/product/4040", PathParameters: map[string]string{"productId": "4040"}},
255+
{HTTPMethod: "DELETE", Path: "/store/product/5050", PathParameters: map[string]string{"productId": "5050"}},
256+
{HTTPMethod: "GET", Path: "/user/charlielee/orders", PathParameters: map[string]string{"username": "charlielee"}},
257+
{HTTPMethod: "POST", Path: "/user/davidwang/review", PathParameters: map[string]string{"username": "davidwang"}},
258+
{HTTPMethod: "GET", Path: "/user/evebrown/review/6060", PathParameters: map[string]string{"username": "evebrown", "reviewId": "6060"}},
259+
{HTTPMethod: "PUT", Path: "/user/frankgreen/review/7070", PathParameters: map[string]string{"username": "frankgreen", "reviewId": "7070"}},
260+
{HTTPMethod: "DELETE", Path: "/user/gracewu/review/8080", PathParameters: map[string]string{"username": "gracewu", "reviewId": "8080"}},
261+
{HTTPMethod: "GET", Path: "/clinic/9090", PathParameters: map[string]string{"clinicId": "9090"}},
262+
{HTTPMethod: "POST", Path: "/clinic"},
263+
{HTTPMethod: "PUT", Path: "/clinic/1212", PathParameters: map[string]string{"clinicId": "1212"}},
264+
{HTTPMethod: "DELETE", Path: "/clinic/1313", PathParameters: map[string]string{"clinicId": "1313"}},
265+
{HTTPMethod: "GET", Path: "/clinic/1414/staff", PathParameters: map[string]string{"clinicId": "1414"}},
266+
{HTTPMethod: "POST", Path: "/clinic/1515/staff", PathParameters: map[string]string{"clinicId": "1515"}},
267+
{HTTPMethod: "GET", Path: "/clinic/1616/staff/1717", PathParameters: map[string]string{"clinicId": "1616", "staffId": "1717"}},
268+
{HTTPMethod: "PUT", Path: "/clinic/1818/staff/1919", PathParameters: map[string]string{"clinicId": "1818", "staffId": "1919"}},
269+
{HTTPMethod: "DELETE", Path: "/clinic/2020/staff/2121", PathParameters: map[string]string{"clinicId": "2020", "staffId": "2121"}},
270+
{HTTPMethod: "GET", Path: "/clinic/2222/appointments", PathParameters: map[string]string{"clinicId": "2222"}},
271+
{HTTPMethod: "POST", Path: "/clinic/2323/appointment/2424/reschedule", PathParameters: map[string]string{"clinicId": "2323", "appointmentId": "2424"}},
201272
}
202273

203274
func assertResponse(b *testing.B, resp events.APIGatewayProxyResponse, req events.APIGatewayProxyRequest) {
@@ -228,21 +299,20 @@ func BenchmarkLambdaMux(b *testing.B) {
228299
}
229300
}
230301

231-
func BenchmarkAWSLambdaGoAPIProxyWithGin(b *testing.B) {
232-
r := setupGinRouter()
233-
adapter := ginadapter.New(r)
302+
func BenchmarkLmdRouter(b *testing.B) {
303+
router := setupLmdRouter()
234304
b.ResetTimer()
235305
for i := 0; i < b.N; i++ {
236306
req := benchmarkRequests[i%len(benchmarkRequests)]
237-
resp, err := adapter.ProxyWithContext(context.Background(), req)
307+
resp, err := router.Handler(context.Background(), req)
238308
assert.NoError(b, err)
239309
assertResponse(b, resp, req)
240310
}
241311
}
242312

243-
func BenchmarkAWSLambdaGoAPIProxyWithFiber(b *testing.B) {
244-
app := setupFiberRouter()
245-
adapter := fiberadapter.New(app)
313+
func BenchmarkAWSLambdaGoAPIProxyWithGin(b *testing.B) {
314+
r := setupGinRouter()
315+
adapter := ginadapter.New(r)
246316
b.ResetTimer()
247317
for i := 0; i < b.N; i++ {
248318
req := benchmarkRequests[i%len(benchmarkRequests)]
@@ -252,12 +322,13 @@ func BenchmarkAWSLambdaGoAPIProxyWithFiber(b *testing.B) {
252322
}
253323
}
254324

255-
func BenchmarkLmdRouter(b *testing.B) {
256-
router := setupLmdRouter()
325+
func BenchmarkAWSLambdaGoAPIProxyWithFiber(b *testing.B) {
326+
app := setupFiberRouter()
327+
adapter := fiberadapter.New(app)
257328
b.ResetTimer()
258329
for i := 0; i < b.N; i++ {
259330
req := benchmarkRequests[i%len(benchmarkRequests)]
260-
resp, err := router.Handler(context.Background(), req)
331+
resp, err := adapter.ProxyWithContext(context.Background(), req)
261332
assert.NoError(b, err)
262333
assertResponse(b, resp, req)
263334
}

0 commit comments

Comments
 (0)