UNPKG

5.29 kBMarkdownView Raw
1# Comparing Node.js Asynchronous Alternatives
2
3
4
5### The Sample Functions
6The `largest`, `countFiles`, and `fibonacci` folders each contain a sample function implemented in six different ways:
7
8* **async**: using the [`async`](https://github.com/caolan/async) library.
9* **asyncawait**: using this `asyncawait` library.
10* **bluebird**: using the [`bluebird`](https://github.com/petkaantonov/bluebird) library.
11* **callbacks**: using plain callbacks.
12* **co**: using the [`co`](https://github.com/visionmedia/co) library (requires node >= 0.11.2 with the `--harmony` flag).
13* **synchronous**: using plain blocking code (just for comparison).
14
15This gives a good indication of the trade-offs between the different coding styles. For the remainer of this document, we'll focus on the most complex sample function, the `largest()` function.
16
17
18
19### The `largest()` Function
20The `largest()` sample function is designed to be of moderate complexity, like a real-world problem.
21
22`largest(dir, options)` finds the largest file in the given directory, optionally performing a recursive search. `dir` is the path of the directory to search. `options`, if provided, is a hash with the following two keys, both optional:
23
24* `recurse` (`boolean`, defaults to `false`): if true, `largest()` will recursively search all subdirectories.
25* `preview` (`boolean`, defaults to `false`): if true, `largest()` will include a small preview of the largest file's content in it's results.
26
27The requirements of `largest()` may be summarised as:
28
291. Find the largest file in the given directory (recursively searching subdirectories if the option is selected).
302. Keep track of how many files/directories have been processed.
313. Get a preview of the file contents (first several characters) if the option is selected.
324. Return the details about the largest file and the number of files/directories searched.
335. Exploit concurrency wherever possible.
346. Don't block Node's event loop anywhere.
35
36The last two requirements are obviously violated by the 'synchronous' variant, but it is worth including for comparison.
37
38
39
40### Metrics for Comparison
41Some interesting metrics with which to compare the six variants are:
42
43* **Lines of code (SLOC)**: Shorter code that does the same thing is usually a good thing.
44* **Levels of Indenting**: Each indent represents a context-shift and therefore higher complexity.
45* **Anachrony**: Asynchronous code may execute in an order very different from its visual representation, which may make it harder to read and reason about in some cases.
46* **Speed**: Node.js is built for speed and throughput, so any loss of speed imposed by a variant may count against it
47
48
49# Comparison Summary
50The following metrics are for the `largest()` example function:
51
52| Variant | SLOC <sup>[1]</sup> | Indents <sup>[2]</sup> | Anachrony <sup>[3]</sup> | Ops/sec <sup>[4]</sup> |
53| :------------ | -------: | ----------: | ------------: | ----------: |
54| async | 67 | 7 | 5 | ~65 |
55| asyncawait | 23 | 2 | - | ~79 |
56| bluebird | 44 | 3 | 8 | ~89 |
57| callbacks | 84 | 6 | 9 | ~100 |
58| co | 23 | 2 | - | ~68 <sup>[5]</sup> |
59| synchronous | 23 | 2 | - | ~63 <sup>[6]</sup> |
60
61###### Footnotes:
62
63<sup>[1]</sup> Includes only lines in the function body; excludes blank lines and comment lines.
64
65<sup>[2]</sup> Maximum indentation from the outermost statements in the function body.
66
67<sup>[3]</sup> Count of times in the function body when visually lower statements execute before visually higher statements due to asynchronous callbacks.
68
69<sup>[4]</sup> Scaled (callbacks = 100), higher is better. Using [benchmark.js](./benchmark.js) on my laptop. All benchmarks run in Node v0.10.25 except for `co` - see [5] below.
70
71<sup>[5]</sup> `co` benchmark run in Node v0.11.12 with the `--harmony` flag.
72
73<sup>[6]</sup> Not strictly comparable because it blocks Node's event loop.
74
75
76
77# Observations
78The following observations are based on the above results and obviously may differ substantially with other code and/or on other machines. **YMMV**. Having said that, at least in this case:
79
80* Plain callbacks are the speed king.
81* All other asynchronous variants achieve at least 65% of the speed of plain callbacks.
82* Bluebird achieves almost 90% of plain callback speed, living up to its reputation of being extremely well optimised.
83* `asyncawait` is third-fastest in this benchmark, achieving almost 80% of the performance of plain callbacks.
84* The source code of `co`, `asyncawait`, and `synchronous` are virtually identical, with purely mechanical syntax differences.
85* `co` and `asyncawait`, each using different coroutine technology, are very similar on these metrics. In a choice between these two, the biggest deciding factor may be whether you can use ES6.
86* The synchronous approach is actually the slowest, which perhaps makes sense since it can't exploit concurrency.
87* `async` looks relatively unfavourable compared to the other asynchronous options on these metrics.