Skip to content

πŸ”₯ feat: reduce binder allocations by pooling or pre-allocating data mapsΒ #4360

@pageton

Description

@pageton

Problem

Every Bind().Query(), Bind().Header(), Bind().Cookie(), and Bind().RespHeader() call creates a fresh make(map[string][]string) on the heap β€” 1 alloc + additional allocs per key.

Files: binder/query.go:20, binder/header.go:20, binder/cookie.go:20, binder/resp_header.go:20

Notably, binder/uri.go:13 correctly pre-allocates with make(map[string][]string, len(params)), but the other binders don't follow this pattern.

Impact

For a typical JSON API binding query params on every request:

  • 3–10 allocs/req minimum (1 for the map, ~2 per key for []string slice backing).
  • At 50K QPS with binding, this is 150K–500K allocs/sec of GC pressure.

Proposed fix (two options)

Option A (low effort): Add capacity hints from fasthttp args count:

args := c.Request().URI().QueryArgs()
data := make(map[string][]string, args.Len())

Option B (better): Pool the data maps like oldInputPool in redirect.go:

var dataMapPool = sync.Pool{
    New: func() any {
        m := make(map[string][]string, 8)
        return &m
    },
}

With a guard in Put to reject maps >64 entries (same pattern as redirect.go:141-147).

Priority

P0 β€” One of the few remaining per-request allocation sources in the hot path.


Identified during a full performance architecture review of the Fiber codebase.

Metadata

Metadata

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions