bigywater.tech
Gin · 54

基准:Gin 与标准库 ServeMux(Go 1.22+ 的模式匹配)

Go 1.22 之后标准库的 ServeMux 也支持方法和通配符了,还需要 Gin 吗?这篇不做口号式的“谁更快”,而是先对齐行为差异,再测纯路由匹配的耗时,最后加上 handler 的真实工作,看路由的几十纳秒在整个请求里占多大比重。

26 vs 130 ns
1 条静态路由时 Gin 与 ServeMux 的单次路由耗时
88 vs 162 ns
1 万条静态路由时的耗时
517 vs 589 ns
含 JSON 序列化时 ServeMux(json.Encoder)与 Gin(c.JSON)的耗时

行为不是对等的

实测 · 本机执行输出
    GET   /users/42        ServeMux → 200 "mux id=42"                                      Gin → 200 "gin id=42"
    GET   /users/new       ServeMux → 200 "mux new"                                        Gin → 200 "gin new"
    POST  /users/42        ServeMux → 405 "Method Not Allowed" Allow=GET, HEAD             Gin → 404 "404 page not found"
    GET   /users/42/       ServeMux → 404 "404 page not found"                             Gin → 301 "<a href=\"/users/42\">Moved Permanently</a>." Location=/users/42
    GET   /files/a/b.txt   ServeMux → 200 "mux path=a/b.txt"                               Gin → 200 "gin path=/a/b.txt"
    GET   /files/          ServeMux → 200 "mux path="                                      Gin → 200 "gin path=/"
    GET   /dir             ServeMux → 307 "<a href=\"/dir/\">Temporary Redirect</a>." Location=/dir/ Gin → 301 "<a href=\"/dir/\">Moved Permanently</a>." Location=/dir/
    GET   /dir/x           ServeMux → 200 "mux dir"                                        Gin → 404 "404 page not found"
    GET   /USERS/42        ServeMux → 404 "404 page not found"                             Gin → 404 "404 page not found"
    HEAD  /users/42        ServeMux → 200 "mux id=42"                                      Gin → 404 "404 page not found"
    GET   /users//42       ServeMux → 307 "<a href=\"/users/42\">Temporary Redirect</a>." Location=/users/42 Gin → 404 "404 page not found"
    GET   /nope            ServeMux → 404 "404 page not found"                             Gin → 404 "404 page not found"

ServeMux 有一些 Gin 默认没有的“自动行为”:方法不匹配返回 405 与 Allow,HEAD 自动匹配 GET 模式,多余斜杠 307 重定向。Gin 则默认 404(需要 HandleMethodNotAllowed)、HEAD 单独注册、尾斜杠 301。通配符的值也不同:ServeMux 的 {path...} 不带开头的斜杠,Gin 的 *path 带。迁移时这些都是需要逐项确认的地方。

纯路由匹配

实测 · 本机执行输出
    [静态路由]
        路由数 | http.ServeMux          | Gin
          1 |     130 ns  0 allocs |      26 ns  0 allocs
        100 |     145 ns  0 allocs |      52 ns  0 allocs
       1000 |     158 ns  0 allocs |      67 ns  0 allocs
      10000 |     162 ns  0 allocs |      88 ns  0 allocs
    [带 {id} / :id 的路由]
        路由数 | http.ServeMux          | Gin
          1 |     174 ns  1 allocs |      31 ns  0 allocs
        100 |     188 ns  1 allocs |      59 ns  0 allocs
       1000 |     199 ns  1 allocs |      78 ns  0 allocs
      10000 |     201 ns  1 allocs |     102 ns  0 allocs

只测路由匹配(空 handler),Gin 的 radix 树明显更快:1 条静态路由 26 ns 对 130 ns,1 万条带参数的路由 102 ns 对 201 ns;Gin 命中路由 0 次分配,ServeMux 带参数时每次 1 次分配(取参数)。两者随路由数的增长都很缓慢。

放到真实请求里

实测 · 本机执行输出
    第 1 轮:JSON:ServeMux(json.Encoder) 517 ns 7 allocs | Gin c.JSON(gin.H) 589 ns 9 allocs;字符串:ServeMux(Fprintf) 140 ns 2 allocs | Gin c.String 188 ns 3 allocs
    第 2 轮:JSON:ServeMux(json.Encoder) 513 ns 7 allocs | Gin c.JSON(gin.H) 591 ns 9 allocs;字符串:ServeMux(Fprintf) 139 ns 2 allocs | Gin c.String 188 ns 3 allocs

handler 里一旦做点事,差距就没了:同样写一个小 JSON,ServeMux 配 json.Encoder 517 ns,Gin 的 c.JSON(gin.H) 589 ns,反而慢一点(gin.H 是 map、Context 的封装也有开销)。一个真实服务的单次请求是微秒到毫秒级(数据库、网络),路由那几十纳秒不是选型的理由。选 Gin 的理由应该是中间件、绑定、校验、渲染这些功能,而不是性能。

分步演示

Gin 与 ServeMux
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew)/ Gin v1.12.0(go.mod 里 require,源码在模块缓存里);用 go run / go test 直接编译运行,需要库时经代理从 proxy.golang.org 下载;HTTP 实验用 httptest 或本机回环端口(9700 以上);计时用 time.Now / testing.B,数字每次运行会有小幅抖动。ServeMux 用 Go 1.27.1 的 http.NewServeMux(带方法与通配符的模式);基准是 testing.Benchmark,5 轮中位数,整个实验跑了两遍,数字相差在 3 ns 以内(正文取最后一遍),输出是丢弃内容的 ResponseWriter;命中的请求是 1024 个随机路径轮流;不含网络与真实 handler 逻辑
    • 没有验证:多核并发下两者的吞吐;注册 1 万条路由的耗时(尝试时程序编译失败,没有重测,所以没有数字);其它路由库(httprouter、chi 等);带中间件链的整体对比;真实网络下的 QPS;ServeMux 在更大路由表(几十万)下的表现
    京ICP备2026067502号-1京公网安备11010502063487号