Skip to content

perf: avoid intermediate strings whend decoding or encoding JSON #1874

@RomainFranceschini

Description

@RomainFranceschini

Description

dart_frog provides helpers to encode and decode JSON content from requests and responses.

These helpers can be optimized by avoiding the allocation of intermediate strings via jsonDecode and jsonEncode. There is a nice talk from Flutter and Friends 2024 that covers this topic in more detail.

For example, consider the benchmark of the following "echo" route:

import 'package:dart_frog/dart_frog.dart';

Future<Response> onRequest(RequestContext context) async {
  final json = await context.request.json();
  return Response.json(body: json);
}

With a 6,558-byte JSON payload, the results were:

Running 30s test @ http://0.0.0.0:8080/foo
  2 threads and 64 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     8.05ms    1.13ms  90.16ms   99.38%
    Req/Sec     4.00k   166.63     7.58k    97.34%
  239420 requests in 30.10s, 1.05GB read
Requests/sec:   7953.43
Transfer/sec:     35.64MB

Here’s a variant of the route that avoids using the built-in request.json and Response.json, eliminating intermediate string allocations:

import 'dart:convert';
import 'package:chunked_stream/chunked_stream.dart';
import 'package:dart_frog/dart_frog.dart';

Future<Response> onRequest(RequestContext context) async {
  final decoder = utf8.decoder.fuse(const JsonDecoder());
  final json = decoder.convert(await readByteStream(context.request.bytes()));
  return Response.stream(body: Stream.value(JsonUtf8Encoder().convert(json)));
}

With the same payload, this version produced the following results:

Running 30s test @ http://0.0.0.0:8080/bar
  2 threads and 64 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     6.12ms  629.45us  62.37ms   98.74%
    Req/Sec     5.26k   128.10     5.49k    78.00%
  314219 requests in 30.02s, 1.38GB read
Requests/sec:  10466.89
Transfer/sec:     47.18MB

While this seems straightforward, applying this to Dart Frog means it would affect how the request body is consumed and cached as a string today. Same goes for the Request.json factory signature, which would introduce a breaking change.

Requirements

  • All CI/CD checks are passing.
  • There is no drop in the test coverage percentage.

Additional Context

  • dart_frog version 1.2.9
  • benchmarks done using wrk on a M1 laptop

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs triageIssue requires triageperformanceChanges that improve performance

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions