Skip to content

Going from 9.3.1 to 10.0.0 sees a consistent ~12% preformance hit #449

@Nicholas-Ball

Description

@Nicholas-Ball

Hi!

This is my first time making a bug report on a public repo ever so I am sorry if I miss something.

I understand, as this seems to be a small / one man team maintaining this, if this isn't the highest of priorities but I thought it was worth bringing up

Overview

The upgrade from 9.3.1 to 10 seems to result in a 12% reduction in validation speed.

System

I am running this benchmark on a laptop running a Ryzen 7 5800H on Nixos version 25.11.20250905.8eb28ad (Xantusia) using cargo 1.89.0 (c24e10642 2025-06-23).

Issue

On version 9.3.1 running the benchmark provided below, I get:
time: [4.3268 µs 4.3421 µs 4.3595 µs]

On 10.0.0 with aws_lc_rs, I get (change is against 9.3.1 version):
time: [4.9559 µs 4.9599 µs 4.9636 µs]
change: [+10.881% +12.039% +13.235%]

On 10.0.0 with rust_crypto, I get (change is against 9.3.1 version):
time: [7.6077 µs 7.6146 µs 7.6221 µs]
change: [+75.762% +77.116% +78.459%]

I see the new example is using aws_lc_rs so that is what I am basing my changes on for that ~12% change.

Expected

I see that jsonwebtoken has made the underlying crypto library a feature but I would expect that, even as a feature, not a 12% penalty.

I don't have a deep understanding what changed from 9.3.1 to 10.0.0 and if there were necessary library changes but I would expect to maintain under +5% of the original performance even in a major version bump.

My files

Here is my config just in case.
.cargo/config.toml

rustflags = [
    "-Ctarget-cpu=native",
]

I combined all my functions to a single benchmark.rs to make it easier to test. I still see the 12% with this file. I hope it helps.
benchmark.rs

use anyhow::Result;
use chrono::Utc;
use criterion::Criterion;
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation, encode};
use std::hint::black_box;

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Claim {
    pub sub: u64,
    pub exp: i64,
    pub first_name: String,
    pub last_name: String,
    pub app_id: u64,
}

pub fn create_from_claim(claim: &Claim) -> Result<String> {
    let jwt = match encode(
        &Header::default(),
        &claim,
        &EncodingKey::from_secret(b"test-key"),
    ) {
        Ok(token) => token,
        Err(e) => {
            return Err(e.into());
        }
    };

    Ok(jwt)
}

pub fn validate_token(token: &str) -> Result<Claim> {
    let token_data = match jsonwebtoken::decode::<Claim>(
        token,
        &DecodingKey::from_secret(b"test-key"),
        &Validation::default(),
    ) {
        Ok(data) => data,
        Err(e) => {
            return Err(e.into());
        }
    };

    // Check if the token is expired
    if token_data.claims.exp < Utc::now().timestamp() {
        return Err(anyhow::anyhow!("Token has expired"));
    }

    Ok(token_data.claims)
}

pub fn benchmark(c: &mut Criterion) {
    let claim = Claim {
        sub: 32u64,
        exp: chrono::Utc::now().timestamp() + 3600,
        first_name: "John".into(),
        last_name: "Doe".into(),
        app_id: 0,
    };

    c.bench_function("token validation", |b| {
        let token = create_from_claim(&claim).unwrap();
        b.iter(|| {
            let _ = validate_token(black_box(&token)).unwrap();
        })
    });
}

criterion_group!(benches, benchmark);
criterion_main!(benches);

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions