binder / Docs / Comparison

Comparison

Numbers for one library on its own say little. These bind the same request into the same shape of struct with binder, Echo, Gin, gorilla/schema and hand-written standard library code, so the cost of each approach can be read against the others.

Read the method before the tables. Two of the figures here changed materially once a measurement fault was found, and one of those faults had been flattering a competitor. A benchmark you cannot reproduce is an advertisement.

Method

Apple M-series laptop, Go 1.27, -benchtime=300ms -count=6, median of six runs. Libraries at Echo v4.15.4, Gin v1.12.0, gorilla/schema v1.4.1.

The comparison lives in its own Go module, so nothing it imports reaches binder's own go.mod:

cd benchmarks
go test -bench . -benchmem

What was held equal

Framework setup is kept out of the timed loop wherever the library allows a context to be reused, since constructing a context is not binding. The request is built once and its body re-armed between iterations, so httptest.NewRequest is not folded into any figure — it costs more memory than the binding under test.

The correction worth knowing about

Echo caches the parsed query on its context, and SetRequest does not clear it. Reusing one context across iterations therefore measures a single parse for the whole run: Echo came out at 556 ns and one allocation, a figure no real server would ever see. The loop now calls Reset, which is what Echo's own server calls per request, and Echo measures 871 ns and eight allocations.

If you reproduce these and get Echo faster than binder, this is why. The uncorrected number favoured Echo; correcting it favoured binder, so it is stated here rather than left in a commit message.

What is not equal

The libraries do not do the same amount of work, and no weighting can make them. Binder reads every source in one call. Echo's binder reads path, query and body. Gin needs a call per source. gorilla/schema decodes a url.Values and nothing else. Each table below says what was asked of each.

At a glance

Both measures, all three shapes. Every bar is labelled and every number appears in the tables below, so nothing here depends on telling the colours apart — binder is picked out only to save you hunting for it.

Time per bind

Query string, 5 fields hand-written stdlib: 363 hand-written stdlib 363 binder: 661 binder 661 Echo: 871 Echo 871 Gin: 1104 Gin 1,104 gorilla/schema: 1944 gorilla/schema 1,944 JSON body, 5 fields hand-written stdlib: 741 hand-written stdlib 741 binder: 799 binder 799 Echo: 816 Echo 816 Gin: 856 Gin 856 Every source at once binder: 1131 binder 1,131 Echo: 1327 Echo 1,327 Gin: 1464 Gin 1,464
Nanoseconds per bind — lower is better. Shared scale across the three panels, so the bars are comparable between them.

Allocations per bind

Query string, 5 fields hand-written stdlib: 7 hand-written stdlib 7 Echo: 8 Echo 8 Gin: 9 Gin 9 binder: 14 binder 14 gorilla/schema: 46 gorilla/schema 46 JSON body, 5 fields hand-written stdlib: 8 hand-written stdlib 8 Echo: 8 Echo 8 Gin: 8 Gin 8 binder: 23 binder 23 Every source at once Echo: 15 Echo 15 Gin: 21 Gin 21 binder: 27 binder 27
Allocations per bind — lower is better. Shared scale across the three panels, so the bars are comparable between them.

The shape of it: binder leads on the query string and on filling a struct from every source, and is level with Echo on a JSON body. It allocates more than the framework binders on that JSON body, some of which is the cost of putting the body back for middleware to read — a guarantee the others do not make.

Query string, five fields

Every library binds the same five query parameters into a struct of string, int, bool, string, string.

Implementationns/opB/opallocs/op
hand-written stdlib3634807
binder66167214
Echo8715448
Gin1,1046089
gorilla/schema1,9441,36746

Reading fields by name with strconv is the floor, and it is 1.8× faster than the quickest reflective binder. That gap is what reflection costs; whether it is worth paying depends on how many handlers you would otherwise write by hand.

JSON body, five fields

The same five values, this time as a JSON object. gorilla/schema is absent because it does not read bodies.

Implementationns/opB/opallocs/op
hand-written stdlib7416818
binder7991,24023
Echo8166818
Gin8566818

Binder is the quickest of the three by a small margin, and within eight per cent of decoding straight into the struct by hand. It also allocates roughly three times as much. Echo and Gin hand the body to encoding/json and let it fill the struct; binder walks the body itself so that a member can be converted by its own rules — a JSON string into an integer field, a TextUnmarshaler, a value bound alongside a path parameter. Values that do not take the direct path still pass through an interface, and that is where the extra allocations are.

Every source at once

Filling one struct from a path value, a query parameter, a JSON body, a header and a cookie. Binder does it in one call; Echo takes three plus two manual reads, Gin takes four. The comparison is of the whole job.

Implementationns/opB/opallocs/op
binder — one call1,1311,92827
Echo — three calls plus manual reads1,3271,20215
Gin — four calls plus manual reads1,4641,64421

This is the case binder exists for, and the margin is still only about fifteen per cent. The stronger argument is the one the table cannot show: one call and one error, against four calls whose failures you have to combine yourself.

What to take from this

Binder is the quickest of the reflective binders measured, in all three shapes, and it allocates more than Echo or Gin on a JSON body. Both are true, and on any endpoint that touches a database neither matters: the whole spread here is under a microsecond.

If you already use Echo or Gin, their binders are built in and near enough in cost that switching for speed would be difficult to justify. The reason to reach for binder is that you are on net/http and want one call to fill a struct from every part of the request. That these numbers are competitive is the point — it means choosing it costs you nothing.

Measured on one machine, one Go version and one shape of struct, against the library versions named above. Results move with all four. Run go test -bench . in benchmarks/ on your own hardware before drawing a conclusion that matters to you.