golang-jwt / jwt

Go implementation of JSON Web Tokens (JWT).
https://golang-jwt.github.io/jwt/
MIT License
7.2k stars 347 forks source link

Avoid use of `json.NewDecoder` if not needed #303

Closed oxisto closed 1 year ago

oxisto commented 1 year ago

We use both json.Unmarshal(for the header) and json.NewDecoder/Decode in ParseUnverified (for the claims). However, we only really need to use the decode type if we have UseNumber enabled. The decoder has about a 30 % performance drawback compared to only using json.Unmarshal + more allocations.

var (
    token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg"
)

func BenchmarkBearer(b *testing.B) {
    b.Run("json.Unmarshal only", func(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            var header = map[string]any{}
            var claims = map[string]any{}
            var parts = strings.Split(token, ".")
            headerBytes, _ := base64.URLEncoding.DecodeString(parts[0])
            claimBytes, _ := base64.URLEncoding.DecodeString(parts[1])
            json.Unmarshal(headerBytes, &header)
            json.Unmarshal(claimBytes, &claims)
        }
    })
    b.Run("json.Unmarshal + json.NewDecoder.Decode", func(b *testing.B) {
        b.ResetTimer()
        for i := 0; i < b.N; i++ {
            var header = map[string]any{}
            var claims = map[string]any{}
            var parts = strings.Split(token, ".")
            headerBytes, _ := base64.URLEncoding.DecodeString(parts[0])
            claimBytes, _ := base64.URLEncoding.DecodeString(parts[1])
            json.Unmarshal(headerBytes, &header)
            dec := json.NewDecoder(bytes.NewReader(claimBytes))
            dec.Decode(&claims)
        }
    })
}

Results:

Running tool: /opt/homebrew/bin/go test -benchmem -run=^$ -bench ^BenchmarkBearer$ github.com/golang-jwt/jwt/v5

2023/04/02 23:00:58 Listening...
goos: darwin
goarch: arm64
pkg: github.com/golang-jwt/jwt/v5
BenchmarkBearer/json.Unmarshal_only-8            1000000          1076 ns/op        1016 B/op         25 allocs/op
BenchmarkBearer/json.Unmarshal_+_json.NewDecoder.Decode-8             830516          1436 ns/op        3264 B/op         27 allocs/op
PASS
ok      github.com/golang-jwt/jwt/v5    2.405s