Performance
Binding from path, query, cookie or header costs a few hundred nanoseconds and a handful of allocations. Binding a body costs more, because the body has to be read and parsed before any field can be converted.
How these were taken
Measured on an Apple M-series laptop with Go 1.27,
-benchtime=200ms -count=10, reporting the median of ten runs.
Each benchmark times binding alone: the request is built once and its body
re-armed between iterations, so httptest.NewRequest is not
folded into the figures — it costs more memory than the binding itself.
make benchResults
| Benchmark | ns/op | B/op | allocs/op |
|---|---|---|---|
BindPathOnly | 105 | 72 | 3 |
BindCookieOnly | 184 | 280 | 5 |
BindNoQueryParams | 186 | 280 | 5 |
BindQueryOnly | 228 | 496 | 6 |
BindOmitEmpty | 250 | 528 | 6 |
BindBodyOnly/JSONBody | 886 | 1,824 | 31 |
BindParallel | 904 | 2,600 | 25 |
BindManyQueryParams | 918 | 832 | 20 |
BindBodyOnly/FormBody | 1,030 | 2,600 | 25 |
Bind | 1,254 | 2,152 | 29 |
BindMixed/WithJSON | 1,304 | 2,544 | 39 |
BindMixed/WithForm | 1,632 | 3,712 | 36 |
BindWithoutCache | 1,902 | 3,464 | 35 |
BindMultipart | 8,850 | 38,069 | 91 |
Reading the numbers
The tag cache
Bind against BindWithoutCache measures the
per-type tag cache: 1,254 ns and 29 allocations with it warm, against
1,902 ns and 35 allocations when it is cleared before every iteration.
Reflecting over a type's tags happens once per type, not once per request,
which is why the first request against a handler is the expensive one and
no other is.
The query string is parsed lazily
BindManyQueryParams binds eight query parameters and
BindNoQueryParams binds none. The gap shows that the query
string is parsed once per call and only when some field asks for it — a
struct with no query tag never pays for
url.Values.
Bodies cost more than the rest
A body has to be read before any field can be converted, which is why every body benchmark starts higher than the ones that read a header or a path value. A JSON body goes straight from the parser into your fields, and a member no field binds is skipped without being decoded at all; a form body still goes through a map, which is why it allocates more despite being simpler to parse. A handler that reads only a path parameter and a header touches none of it.
Multipart is an order of magnitude dearer
BindMultipart carries two text fields and a 4 KB file.
The gap is inherent to the encoding rather than to binding: the parser
copies each part, and the file is held in memory rather than spilled to
disk. It is the price of an upload, not of the tag lookup.
Context
Setting the upload aside, the slowest case here is under two microseconds. On any endpoint that touches a database, a cache or another service, binding is not the thing to optimise — these numbers exist to show that it stays out of the way, not to invite tuning.