Skip to content

jsonEncode() is (almost) 40% slower than the Node.js counterpart #51779

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
fsoft72 opened this issue Mar 19, 2023 · 11 comments
Open

jsonEncode() is (almost) 40% slower than the Node.js counterpart #51779

fsoft72 opened this issue Mar 19, 2023 · 11 comments
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. P3 A lower priority bug or feature request triaged Issue has been triaged by sub team type-performance Issue relates to performance or code size

Comments

@fsoft72
Copy link

fsoft72 commented Mar 19, 2023

I am testing Dart capabilities as a web server and I am doing some easy tests against Node.js

I have written a simple program using the Alfred web server and I stumbled upon this issue: dart.convert/jsonEncode() is slower than the Node.js counterpart.

This is the Dart code of my server, it simply gets a num parameter and generates a list of ints with the number.
In the end, it returns a JSON. The Alfred library calls dart.convert/jsonEncode on it.

This is the server code (Node.js version is exactly the same, only using ExpressJS):

import 'package:alfred/alfred.dart';

void main() async {
  final app = Alfred();

  app.get('/count/:num:int', (req, res) {
    int number = req.params['num'];
    List<int> list = [];

    while (number > 0) {
      list.add(number);
      number--;
    }

    res.json({'res': list});
  });

  app.printRoutes(); //Will print the routes to the console

  await app.listen(3001);
}

These are some tests I have run on both servers using restest
NOTE: Dart version is an executable compiled with dart compile exe command.

Node.js version

restest --base-url http://localhost:3000 test.json 

 GET    /count/10000                        {} auth: False - status: 200 - t: 5 ms / 0.005 s
 GET    /count/100000                       {} auth: False - status: 200 - t: 5 ms / 0.005 s
 GET    /count/1000000                      {} auth: False - status: 200 - t: 55 ms / 0.055 s
 GET    /count/10000000                     {} auth: False - status: 200 - t: 805 ms / 0.805 s

===== FINISHED. Total Errors: 0 / 4 [Total time: 0.872000]

Dart version

restest --base-url http://localhost:3001 test.json 

 GET    /count/10000                        {} auth: False - status: 200 - t: 3 ms / 0.003 s
 GET    /count/100000                       {} auth: False - status: 200 - t: 14 ms / 0.014 s
 GET    /count/1000000                      {} auth: False - status: 200 - t: 114 ms / 0.114 s
 GET    /count/10000000                     {} auth: False - status: 200 - t: 1189 ms / 1.189 s

===== FINISHED. Total Errors: 0 / 4 [Total time: 1.323000]

As you can see, from second request and beyond the Node.js version is faster than the compiled Dart version.
Everything is related to jsonEncode(), because if I change the server code, removing the list to be encoded in this way:

    res.json({'res': 1});

The Dart version becomes the fastest, as you can see from these benchmarks:

Node.js version

restest --base-url http://localhost:3000 test.json 

 GET    /count/10000                        {} auth: False - status: 200 - t: 7 ms / 0.007 s
 GET    /count/100000                       {} auth: False - status: 200 - t: 4 ms / 0.004 s
 GET    /count/1000000                      {} auth: False - status: 200 - t: 24 ms / 0.024 s
 GET    /count/10000000                     {} auth: False - status: 200 - t: 222 ms / 0.222 s

===== FINISHED. Total Errors: 0 / 4 [Total time: 0.257000]

Dart version

restest --base-url http://localhost:3001 test.json 

 GET    /count/10000                        {} auth: False - status: 200 - t: 3 ms / 0.003 s
 GET    /count/100000                       {} auth: False - status: 200 - t: 2 ms / 0.002 s
 GET    /count/1000000                      {} auth: False - status: 200 - t: 13 ms / 0.013 s
 GET    /count/10000000                     {} auth: False - status: 200 - t: 172 ms / 0.172 s

===== FINISHED. Total Errors: 0 / 4 [Total time: 0.189000]
  • My Dart SDK Version is: Dart SDK version: 2.19.3 (stable) (Tue Feb 28 15:52:19 2023 +0000) on "linux_x64"
  • I am running tests on Linux Kubuntu 22.10 x64

I am attaching two very simple scripts (one for Dart and one for Node.js) that do not require any dependency, the Dart one is like this:

import 'dart:convert';

void main() {
  int n = 10000000;
  List<int> list = [];

  while (n > 0) {
    list.add(n);
    n--;
  }

  // get the time in milliseconds
  int start = new DateTime.now().millisecondsSinceEpoch;
  jsonEncode(list);
  int end = new DateTime.now().millisecondsSinceEpoch;

  print("Dart time: ${end - start} ms");
}

Node.js version executes in: 246 ms
Dart version executes in: 615 ms

count.tar.gz

@gintominto5329
Copy link

gintominto5329 commented Mar 19, 2023

Slightly better script.dart:

import "dart:convert";

void main() {
  final List<int> list = List<int>.generate(
    10000000,
    (final int i) => i,
    growable: false,
  );

  final Stopwatch watch = Stopwatch()..start();

  const JsonEncoder().convert(list);

  watch.stop();

  print("Dart time: ${watch.elapsedMilliseconds} ms");
}

this script is took, 1172 ms plus minus 2,
run with the command dart compile aot-snapshot drt.dart && dartaotruntime drt.aot, thrice,
on intel-amd64-11gen, with dart-stable-2.19

@mzsdev1

This comment was marked as off-topic.

@gintominto5329
Copy link

Related:
dart/sdk/issues/51596,

@mzsdev1

This comment was marked as off-topic.

@gintominto5329

This comment was marked as off-topic.

@lrhn lrhn added area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. type-performance Issue relates to performance or code size labels Mar 19, 2023
@mzsdev1

This comment was marked as off-topic.

@fsoft72

This comment was marked as off-topic.

@mraleph
Copy link
Member

mraleph commented Mar 20, 2023

This is most likely caused by us going num -> String -> Sink route and then concatenating all strings together instead of simply formatting numbers into the buffer, which is what V8 is likely doing. We can optimise it by rewriting our JSON serialiser. I notice that _JsonUtf8Stringifier seems to be doing the same suboptimal thing. (/cc @aam @lrhn)

Not that it really matters unless you want to serialise large amount of data (at which point you probably should not be using JSON in the first place).

FWIW alfred should also do this add(JsonUtf8Encoder().convert(json)) instead of this write(jsonEncode(json)) here because that will fuse JSON serialisation and UTF8 conversion. (Though it probably will not immediately fix the performance difference because _JsonUtf8Stringifier does not write numbers directly into the output buffer.

@mraleph
Copy link
Member

mraleph commented Mar 20, 2023

(I have marked most of the server-side Dart discussion as off topic for this issue, and respectfully encourage to continue it through other community channels).

@lrhn
Copy link
Member

lrhn commented Mar 20, 2023

I'd love to have an int writeInt(int value, List<int> bytes, [int start = 0, int end?]) which writes the string representation of an int into a byte buffer, if there is room, and returns the end index.
And int parseInt(List<int> source, [int start = 0, int end?]) which parses the same way.
Since digits are ASCII, you can then directly parse from/write to an UTF-8 encoded byte sequence.

Let's see if we can find somewhere to add that. (Still need parsing to/from strings too, for all the usual uses, but avoiding going through strings when not necessary can be more performant.)

@a-siva a-siva added P3 A lower priority bug or feature request triaged Issue has been triaged by sub team labels Dec 14, 2023
@aam
Copy link
Contributor

aam commented Dec 14, 2023

Related to #51619

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. P3 A lower priority bug or feature request triaged Issue has been triaged by sub team type-performance Issue relates to performance or code size
Projects
None yet
Development

No branches or pull requests

7 participants