dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.21k stars 1.57k forks source link

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

Open fsoft72 opened 1 year ago

fsoft72 commented 1 year ago

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]

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 commented 1 year ago

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 commented 1 year ago

Could dart be a good backend option in the future? Or do you think it will be killed by google?

gintominto5329 commented 1 year ago

Related: dart/sdk/issues/51596,

mzsdev1 commented 1 year ago

Could dart be a good backend option in the future? Or do you think it will be killed by google?

We have come too far, 99.9...% it won't be killed,

And the API is being optimized, with each release, by the dart's hard working team, for example this issue, once completed, would make socket reads, around 25 to 40 % faster,

But I think io_uring would be a much better choice now, as epoll would not be the prime focus of the linux's net-workers, due to its in-efficient and patchy design

Could dart be a good backend option in the future? Or do you think it will be killed by google?

We have come too far, 99.9...% it won't be killed,

And the API is being optimized, with each release, by the dart's hard working team, for example this issue, once completed, would make socket reads, around 25 to 40 % faster,

But I think io_uring would be a much better choice now, as epoll would not be the prime focus of the linux's net-workers, due to its in-efficient and patchy design

So, can we think that Dart will have an important position on the server side in the future? Of course, I don't think it will be a competitor to Node js right away, but the current serverpod or shelf is not enough.

gintominto5329 commented 1 year ago

"will" is not correct, I think. the time is now,

The optimizations, and usage, go hand-in-hand,

Just the community is sleeping,

Once they wake up, and report their findings, and needs, like this issue.

Then the spark will become fire, which will just burn the JavaScript, NodeJS, React(-Native), KotlinMultiPlatform, and company...

mzsdev1 commented 1 year ago

"will" is not correct, I think. the time is now,

The optimizations, and usage, go hand-in-hand,

Just the community is sleeping,

Once they wake up, and report their findings, and needs, like this issue.

Then the spark will become fire, which will just burn the JavaScript, NodeJS, React(-Native), KotlinMultiPlatform, and company...

Unfortunately the community only cares about flutter. But at least I expect a significant improvement after Dart 3.

fsoft72 commented 1 year ago

@regenin

So, can we think that Dart will have an important position on the server side in the future? Of course, I don't think it will be a competitor to Node js right away, but the current serverpod or shelf is not enough.

I think it could be a serious competitor to Node.js. The environment of JavaScript is changed a lot: in the early days, you used to write 'plain' javascript and have it served using node.js. Now, since the application complexity grew a lot, you can't rely on JS itself but everyone is moving to Typescript. Having a transpiler to your code, basically means you are compiling your code... so what would be the difference between "compiling" typescript to js and dart to exe? Without considering the npm hell that is becoming a real serious problem in these days (you can easily have 300/400 MB of javascript code in your node_modules directory without even knowing it).

Dart has many advantages over Typescript: the syntax is much clean, types are native to the language and so it is OOP... but to become a real competitor to the Node.js environments, Dart has to be really fast, or the community will stick to node.js for the server side anyway. At least, it has to become fast in the "common" functions / applications... and reading / writing JSON structures is the real fundamentals of any REST application.

mraleph commented 1 year ago

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 commented 1 year ago

(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 commented 1 year ago

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.)

aam commented 10 months ago

Related to https://github.com/dart-lang/sdk/issues/51619