[v8-dev] performance benchmark of async-design-patterns - recursive-callbacks vs. promises vs. async/await
kai zhu
kaizhu256 at gmail.com
Mon Apr 30 18:53:01 UTC 2018
i ran the test-script on travis-ci against various v8/nodejs versions (including v8-6.6/node-10) [1] [2].
here are the raw data and visualization [3] of recursive-callback/promise/async-await vs v8/nodejs-versions. not-surprisingly, recursive-callbacks are consistently faster, but only a small margin (7-13%) than promises across historical nodejs versions on travis-ci.
of note:
- the standard deviation error-bars should be taken with a grain of salt, as the travis-ci rerun indicates higher inconsistency
- the low numbers from node-0.9 and node-0.10 are meaningless, as the sockets seem to constantly hang and timeout according to travis logs.
- note the sudden drop for the travis-ci rerun of node-9.11.1 (don’t have explanation, and the travis-logs shows the low numbers are quite consistent within the rerun)
- node-10.0.0 is slower than node-8.11.1, including the travis rerun
- async/await is only available on node-7 and higher
[1] travis-ci build logs #20
https://travis-ci.org/kaizhu256/node-performance-recursiveCallback-vs-promise-vs-asyncAwait/builds/372961240 <https://travis-ci.org/kaizhu256/node-performance-recursiveCallback-vs-promise-vs-asyncAwait/builds/372961240>
[2] travis-ci build logs #21 (rerun to verify consistency)
https://travis-ci.org/kaizhu256/node-performance-recursiveCallback-vs-promise-vs-asyncAwait/builds/373134575 <https://travis-ci.org/kaizhu256/node-performance-recursiveCallback-vs-promise-vs-asyncAwait/builds/373134575>
[3] visualisation
https://kaizhu256.github.io/node-performance-recursiveCallback-vs-promise-vs-asyncAwait/index.html <https://kaizhu256.github.io/node-performance-recursiveCallback-vs-promise-vs-asyncAwait/index.html>
```json
[
{
"version": "v8-3.14.5.8<br>node-0.9.12",
"clientHttpRequestWithRecursiveCallback": "456 (83 sigma)",
"recursiveCallbackVsPromiseRatio": null
},
{
"version": "v8-3.14.5.11<br>node-0.10.48",
"clientHttpRequestWithRecursiveCallback": "49 (1 sigma)",
"recursiveCallbackVsPromiseRatio": null
},
{
"version": "v8-3.28.73<br>node-0.11.16",
"clientHttpRequestWithRecursiveCallback": "1997 (41 sigma)",
"clientHttpRequestWithPromise": "1854 (67 sigma)",
"recursiveCallbackVsPromiseRatio": 1.0771305285868393
},
{
"version": "v8-3.28.71.20<br>node-0.12.18",
"clientHttpRequestWithRecursiveCallback": "1964 (14 sigma)",
"clientHttpRequestWithPromise": "1829 (15 sigma)",
"recursiveCallbackVsPromiseRatio": 1.0738108255877528
},
{
"version": "v8-4.5.103.53<br>node-4.9.1",
"clientHttpRequestWithRecursiveCallback": "2355 (31 sigma)",
"clientHttpRequestWithPromise": "2170 (22 sigma)",
"recursiveCallbackVsPromiseRatio": 1.0852534562211982
},
{
"version": "v8-4.6.85.32<br>node-5.12.0",
"clientHttpRequestWithRecursiveCallback": "2471 (69 sigma)",
"clientHttpRequestWithPromise": "2319 (76 sigma)",
"recursiveCallbackVsPromiseRatio": 1.0655454937473048
},
{
"version": "v8-5.1.281.111<br>node-6.14.1",
"clientHttpRequestWithRecursiveCallback": "2589 (37 sigma)",
"clientHttpRequestWithPromise": "2374 (38 sigma)",
"recursiveCallbackVsPromiseRatio": 1.0905644481887111
},
{
"version": "v8-5.5.372.43<br>node-7.10.1",
"clientHttpRequestWithRecursiveCallback": "3238 (53 sigma)",
"clientHttpRequestWithPromise": "2846 (35 sigma)",
"clientHttpRequestWithAsyncAwait": "2860 (31 sigma)",
"recursiveCallbackVsPromiseRatio": 1.1377371749824314
},
{
"version": "v8-6.2.414.50<br>node-8.11.1",
"clientHttpRequestWithRecursiveCallback": "4699 (74 sigma)",
"clientHttpRequestWithPromise": "4305 (88 sigma)",
"clientHttpRequestWithAsyncAwait": "4294 (52 sigma)",
"recursiveCallbackVsPromiseRatio": 1.091521486643438
},
{
"version": "v8-6.2.414.46-node.23<br>node-9.11.1",
"clientHttpRequestWithRecursiveCallback": "4809 (80 sigma)",
"clientHttpRequestWithPromise": "4423 (94 sigma)",
"clientHttpRequestWithAsyncAwait": "4404 (81 sigma)",
"recursiveCallbackVsPromiseRatio": 1.087271082975356
},
{
"version": "v8-6.6.346.24-node.5<br>node-10.0.0",
"clientHttpRequestWithRecursiveCallback": "4100 (69 sigma)",
"clientHttpRequestWithPromise": "3836 (98 sigma)",
"clientHttpRequestWithAsyncAwait": "3837 (46 sigma)",
"recursiveCallbackVsPromiseRatio": 1.0688216892596454
},
{
"version": "v8-3.14.5.8<br>node-0.9.12",
"clientHttpRequestWithRecursiveCallback-rerun": "477 (46 sigma)",
"recursiveCallbackVsPromiseRatio-rerun": null
},
{
"version": "v8-3.14.5.11<br>node-0.10.48",
"clientHttpRequestWithRecursiveCallback-rerun": "48 (2 sigma)",
"recursiveCallbackVsPromiseRatio-rerun": null
},
{
"version": "v8-3.28.73<br>node-0.11.16",
"clientHttpRequestWithRecursiveCallback-rerun": "1887 (25 sigma)",
"clientHttpRequestWithPromise-rerun": "1763 (25 sigma)",
"recursiveCallbackVsPromiseRatio-rerun": 1.0771305285868393
},
{
"version": "v8-3.28.71.20<br>node-0.12.18",
"clientHttpRequestWithRecursiveCallback-rerun": "2494 (23 sigma)",
"clientHttpRequestWithPromise-rerun": "2282 (35 sigma)",
"recursiveCallbackVsPromiseRatio-rerun": 1.0738108255877528
},
{
"version": "v8-4.5.103.53<br>node-4.9.1",
"clientHttpRequestWithRecursiveCallback-rerun": "3246 (99 sigma)",
"clientHttpRequestWithPromise-rerun": "3008 (91 sigma)",
"recursiveCallbackVsPromiseRatio-rerun": 1.0852534562211982
},
{
"version": "v8-4.6.85.32<br>node-5.12.0",
"clientHttpRequestWithRecursiveCallback-rerun": "2709 (26 sigma)",
"clientHttpRequestWithPromise-rerun": "2507 (30 sigma)",
"recursiveCallbackVsPromiseRatio-rerun": 1.0655454937473048
},
{
"version": "v8-5.1.281.111<br>node-6.14.1",
"clientHttpRequestWithRecursiveCallback-rerun": "2401 (38 sigma)",
"clientHttpRequestWithPromise-rerun": "2193 (55 sigma)",
"recursiveCallbackVsPromiseRatio-rerun": 1.0905644481887111
},
{
"version": "v8-5.5.372.43<br>node-7.10.1",
"clientHttpRequestWithRecursiveCallback-rerun": "4065 (83 sigma)",
"clientHttpRequestWithPromise-rerun": "3565 (91 sigma)",
"clientHttpRequestWithAsyncAwait-rerun": "3620 (89 sigma)",
"recursiveCallbackVsPromiseRatio-rerun": 1.1377371749824314
},
{
"version": "v8-6.2.414.50<br>node-8.11.1",
"clientHttpRequestWithRecursiveCallback-rerun": "4915 (56 sigma)",
"clientHttpRequestWithPromise-rerun": "4537 (49 sigma)",
"clientHttpRequestWithAsyncAwait-rerun": "4476 (44 sigma)",
"recursiveCallbackVsPromiseRatio-rerun": 1.091521486643438
},
{
"version": "v8-6.2.414.46-node.23<br>node-9.11.1",
"clientHttpRequestWithRecursiveCallback-rerun": "2141 (67 sigma)",
"clientHttpRequestWithPromise-rerun": "2011 (67 sigma)",
"clientHttpRequestWithAsyncAwait-rerun": "2019 (43 sigma)",
"recursiveCallbackVsPromiseRatio-rerun": 1.087271082975356
},
{
"version": "v8-6.6.346.24-node.5<br>node-10.0.0",
"clientHttpRequestWithRecursiveCallback-rerun": "3609 (84 sigma)",
"clientHttpRequestWithPromise-rerun": "3350 (47 sigma)",
"clientHttpRequestWithAsyncAwait-rerun": "3353 (65 sigma)",
"recursiveCallbackVsPromiseRatio-rerun": 1.0688216892596454
}
]
```
kai zhu
kaizhu256 at gmail.com
> On 30 Apr 2018, at 2:08 PM, Benedikt Meurer <bmeurer at chromium.org> wrote:
>
> Only 15% slower Promise (or async/await) based version sounds awesome. I'm not sure we can squeeze a lot more out of this. But having a benchmark setup on Node Infrastructure would be very much welcome.
>
> -- Benedikt
>
> On Sun, Apr 29, 2018 at 10:52 PM Caitlin Potter <caitp at chromium.org <mailto:caitp at chromium.org>> wrote:
> I’d be very interested to see how this runs in node 10.x or Canary, which have some major optimizations to Promises and RunMicrotasks.
>
> Hopefully the async/await and Promise numbers do a bit better than before.
> On Apr 29, 2018, at 4:24 PM, Michael J. Ryan <tracker1 at gmail.com <mailto:tracker1 at gmail.com>> wrote:
>
>> Nice... And not really surprising. I am slightly surprised async/await is so close to promises. Which means that improving promises performance should probably be a priority. I still feel the easier to reason with code is well worth it, given many apps now scale horizontally.
>>
>> On Sun, Apr 29, 2018, 10:31 kai zhu <kaizhu256 at gmail.com <mailto:kaizhu256 at gmail.com>> wrote:
>> fyi, here are some benchmark results of nodejs' client-based http-request throughput, employing various async-design-patterns (on a 4gb linode box). overall, recursive-callbacks seem to ~15% faster than both async/await and promises (~3000 vs ~2600 client-http-request/s).
>>
>> ```shell
>> $ REQUESTS_PER_TICK=10 node example.js
>>
>> state 1 - node (v9.11.1)
>> state 2 - http-server listening on port 3000
>> ...
>> state 3 - clientHttpRequestWithRecursiveCallback - flooding http-server with request "http://localhost:3000 <http://localhost:3000/>"
>> state 5 - clientHttpRequestWithRecursiveCallback - testRun #99
>> state 5 - clientHttpRequestWithRecursiveCallback - requestsTotal = 14690 (in 5009 ms)
>> state 5 - clientHttpRequestWithRecursiveCallback - requestsPassed = 7349
>> state 5 - clientHttpRequestWithRecursiveCallback - requestsFailed = 7341 ({
>> "statusCode - 500": true
>> })
>> state 5 - clientHttpRequestWithRecursiveCallback - 2933 requests / second
>> state 5 - mean requests / second = {
>> "clientHttpRequestWithRecursiveCallback": "3059 (156 sigma)",
>> "clientHttpRequestWithPromise": "2615 (106 sigma)",
>> "clientHttpRequestWithAsyncAwait": "2591 (71 sigma)"
>> }
>> ```
>>
>>
>> you can reproduce the benchmark-results by running this zero-dependency/zero-config, standalone nodejs script below:
>>
>>
>> ```js
>> /*
>> * example.js
>> *
>> * this zero-dependency example will benchmark nodejs' client-based http-requests throughput,
>> * using recursive-callback/promise/async-await design-patterns.
>> *
>> * the program will make 100 test-runs (randomly picking a design-pattern per test-run),
>> * measuring client-based http-requests/seconde over a 5000 ms interval.
>> * it will save the 16 most recent test-runs for each design-pattern,
>> * and print the mean and standard deviation.
>> * any test-run with unusual errors (timeouts, econnreset, etc),
>> * will be discarded and not used in calculations
>> *
>> * the script accepts one env variable $REQUESTS_PER_TICK, which defaults to 10
>> * (you can try increasing it if you have a high-performance machine)
>> *
>> *
>> *
>> * example usage:
>> * $ REQUESTS_PER_TICK=10 node example.js
>> *
>> * example output:
>> *
>> * state 1 - node (v9.11.1)
>> * state 2 - http-server listening on port 3000
>> * ...
>> * state 3 - clientHttpRequestWithRecursiveCallback - flooding http-server with request "http://localhost:3000 <http://localhost:3000/>"
>> * state 5 - clientHttpRequestWithRecursiveCallback - testRun #99
>> * state 5 - clientHttpRequestWithRecursiveCallback - requestsTotal = 14690 (in 5009 ms)
>> * state 5 - clientHttpRequestWithRecursiveCallback - requestsPassed = 7349
>> * state 5 - clientHttpRequestWithRecursiveCallback - requestsFailed = 7341 ({
>> * "statusCode - 500": true
>> * })
>> * state 5 - clientHttpRequestWithRecursiveCallback - 2933 requests / second
>> * state 5 - mean requests / second = {
>> * "clientHttpRequestWithRecursiveCallback": "3059 (156 sigma)",
>> * "clientHttpRequestWithPromise": "2615 (106 sigma)",
>> * "clientHttpRequestWithAsyncAwait": "2591 (71 sigma)"
>> * }
>> *
>> * state 6 - process.exit(0)
>> */
>>
>> /*jslint
>> bitwise: true,
>> browser: true,
>> maxerr: 4,
>> maxlen: 100,
>> node: true,
>> nomen: true,
>> regexp: true,
>> stupid: true
>> */
>>
>> (function () {
>> 'use strict';
>> var local;
>> local = {};
>>
>> // require modules
>> local.http = require('http');
>> local.url = require('url');
>>
>> /* jslint-ignore-begin */
>> local.clientHttpRequestWithAsyncAwait = async function (url, onError) {
>> /*
>> * this function will make an http-request using async/await design-pattern
>> */
>> var request, response, timerTimeout;
>> try {
>> response = await new Promise(function (resolve, reject) {
>> // init timeout
>> timerTimeout = setTimeout(function () {
>> reject(new Error('timeout - 2000 ms'));
>> }, 2000);
>> request = local.http.request(local.url.parse(url), resolve);
>> request.on('error', reject);
>> request.end();
>> });
>> await new Promise(function (resolve, reject) {
>> // ignore stream-data
>> response.on('data', local.nop);
>> if (response.statusCode >= 400) {
>> reject(new Error('statusCode - ' + response.statusCode));
>> return;
>> }
>> response.on('end', resolve);
>> response.on('error', reject);
>> });
>> } catch (error) {
>> // cleanup timerTimeout
>> clearTimeout(timerTimeout);
>> // cleanup request and response
>> if (request) {
>> request.destroy();
>> }
>> if (response) {
>> response.destroy();
>> }
>> onError(error);
>> return;
>> }
>> onError();
>> };
>> /* jslint-ignore-end */
>>
>> local.clientHttpRequestWithPromise = function (url, onError) {
>> /*
>> * this function will make an http-request using promise design-pattern
>> */
>> var request, response, timerTimeout;
>> new Promise(function (resolve, reject) {
>> // init timeout
>> timerTimeout = setTimeout(function () {
>> reject(new Error('timeout - 2000 ms'));
>> }, 2000);
>> request = local.http.request(local.url.parse(url), resolve);
>> request.on('error', reject);
>> request.end();
>> }).then(function (result) {
>> return new Promise(function (resolve, reject) {
>> response = result;
>> // ignore stream-data
>> response.on('data', local.nop);
>> if (response.statusCode >= 400) {
>> reject(new Error('statusCode - ' + response.statusCode));
>> return;
>> }
>> response.on('end', resolve);
>> response.on('error', reject);
>> });
>> }).then(onError).catch(function (error) {
>> // cleanup timerTimeout
>> clearTimeout(timerTimeout);
>> // cleanup request and response
>> if (request) {
>> request.destroy();
>> }
>> if (response) {
>> response.destroy();
>> }
>> onError(error);
>> });
>> };
>>
>> local.clientHttpRequestWithRecursiveCallback = function (url, onError) {
>> /*
>> * this function will make an http-request using recursive-callback design-pattern
>> */
>> var isDone, modeNext, request, response, onNext, timerTimeout;
>> onNext = function (error) {
>> modeNext += error instanceof Error
>> ? Infinity
>> : 1;
>> switch (modeNext) {
>> case 1:
>> // init timeout
>> timerTimeout = setTimeout(function () {
>> onNext(new Error('timeout - 2000 ms'));
>> }, 2000);
>> request = local.http.request(local.url.parse(url), onNext);
>> request.on('error', onNext);
>> request.end();
>> break;
>> case 2:
>> response = error;
>> // ignore stream-data
>> response.on('data', local.nop);
>> if (response.statusCode >= 400) {
>> onNext(new Error('statusCode - ' + response.statusCode));
>> }
>> response.on('end', onNext);
>> response.on('error', onNext);
>> break;
>> default:
>> if (isDone) {
>> return;
>> }
>> // cleanup timerTimeout
>> clearTimeout(timerTimeout);
>> // cleanup request and response
>> if (request) {
>> request.destroy();
>> }
>> if (response) {
>> response.destroy();
>> }
>> isDone = true;
>> onError(error);
>> }
>> };
>> modeNext = 0;
>> onNext();
>> };
>>
>> local.clientHttpRequestOnError = function (error) {
>> /*
>> * this function is the callback for clientHttpRequest
>> */
>> if (error) {
>> local.errorDict[error.message] = true;
>> local.requestsFailed += 1;
>> } else {
>> local.requestsPassed += 1;
>> }
>> if (local.timeElapsed >= 5000 &&
>> (local.requestsFailed + local.requestsPassed) === local.requestsTotal) {
>> local.main();
>> }
>> };
>>
>> local.nop = function () {
>> /*
>> * this function will do nothing
>> */
>> return;
>> };
>>
>> local.templateRenderAndPrint = function (template) {
>> /*
>> * this function render simple double-mustache templates with the local dict,
>> * and print to stderr
>> */
>> console.error(template.replace((/\{\{.*?\}\}/g), function (match0) {
>> return local[match0.slice(2, -2)];
>> }));
>> };
>>
>> local.main = function (error) {
>> /*
>> * this function will fun the main-loop
>> */
>> local.state += error
>> ? Infinity
>> : 1;
>> switch (local.state) {
>> case 1:
>> // init local var
>> local.clientHttpRequestUrl = 'http://localhost:3000 <http://localhost:3000/>';
>> local.version = process.version;
>> local.templateRenderAndPrint('state {{state}} - node ({{version}})');
>> // create simple http-server that responds with random 200 or 500 statusCode
>> local.http.createServer(function (request, response) {
>> request
>> // ignore stream-data
>> .on('data', local.nop)
>> .on('error', console.error);
>> // respond randomly with either 200 or 500 statusCode
>> response.statusCode = Math.random() < 0.5
>> ? 200
>> : 500;
>> response
>> .on('error', console.error)
>> .end();
>> // listen on port 3000
>> }).listen(3000, local.main);
>> break;
>> case 2:
>> local.templateRenderAndPrint('state {{state}} - http-server listening on port 3000');
>> local.main();
>> break;
>> case 3:
>> local.clientHttpRequestState = local.clientHttpRequestState || 0;
>> local.clientHttpRequestState += 1;
>> if (local.clientHttpRequestState < 100) {
>> switch (Math.floor(Math.random() * 3)) {
>> case 0:
>> local.clientHttpRequest = 'clientHttpRequestWithAsyncAwait';
>> break;
>> case 1:
>> local.clientHttpRequest = 'clientHttpRequestWithPromise';
>> break;
>> case 2:
>> local.clientHttpRequest = 'clientHttpRequestWithRecursiveCallback';
>> break;
>> }
>> } else {
>> local.state += 2;
>> local.main();
>> return;
>> }
>> local.templateRenderAndPrint('\nstate {{state}} - {{clientHttpRequest}} - ' +
>> 'flooding http-server with request "{{clientHttpRequestUrl}}"');
>> local.errorDict = {};
>> local.requestsFailed = 0;
>> local.requestsPassed = 0;
>> local.requestsTotal = 0;
>> local.timeElapsed = 0;
>> local.timeStart = Date.now();
>> local.main();
>> break;
>> case 4:
>> setTimeout(function () {
>> for (local.ii = 0;
>> // configurable REQUESTS_PER_TICK
>> local.ii < (Number(process.env.REQUESTS_PER_TICK) || 10);
>> local.ii += 1) {
>> local.requestsTotal += 1;
>> local[local.clientHttpRequest](
>> local.clientHttpRequestUrl,
>> local.clientHttpRequestOnError
>> );
>> }
>> // recurse / repeat this step for 5000 ms
>> local.timeElapsed = Date.now() - local.timeStart;
>> if (local.timeElapsed < 5000) {
>> local.state -= 1;
>> local.main();
>> }
>> });
>> break;
>> case 5:
>> local.timeElapsed = Date.now() - local.timeStart;
>> local.requestsPerSecond = Math.round(1000 * local.requestsTotal / local.timeElapsed);
>> local.errorDictJson = JSON.stringify(local.errorDict, null, 4);
>> local.resultList = local.resultList || {};
>> local.resultMean = local.resultMean || {};
>> // only save result if no unusual errors occurred
>> if (Object.keys(local.errorDict).length <= 1) {
>> local.resultList[local.clientHttpRequest] =
>> local.resultList[local.clientHttpRequest] || [];
>> local.resultList[local.clientHttpRequest].push(local.requestsPerSecond);
>> // remove old data
>> if (local.resultList[local.clientHttpRequest].length > 16) {
>> local.resultList[local.clientHttpRequest].shift();
>> }
>> // calculate mean
>> local.resultMean[local.clientHttpRequest] = Math.round(
>> local.resultList[local.clientHttpRequest].reduce(function (aa, bb) {
>> return aa + (bb || 0);
>> }, 0) / local.resultList[local.clientHttpRequest].length
>> );
>> // calculate sigma
>> local.resultMean[local.clientHttpRequest] += ' (' + Math.round(Math.sqrt(
>> local.resultList[local.clientHttpRequest].reduce(function (aa, bb) {
>> return aa + Math.pow(
>> (bb || 0) - local.resultMean[local.clientHttpRequest],
>> 2
>> );
>> }, 0) / (local.resultList[local.clientHttpRequest].length - 1)
>> )) + ' sigma)';
>> }
>> local.resultJson = JSON.stringify(local.resultMean, null, 4);
>> local.templateRenderAndPrint(
>> /* jslint-ignore-begin */
>> '\
>> state {{state}} - {{clientHttpRequest}} - testRun #{{clientHttpRequestState}}\n\
>> state {{state}} - {{clientHttpRequest}} - requestsTotal = {{requestsTotal}} (in {{timeElapsed}} ms)\n\
>> state {{state}} - {{clientHttpRequest}} - requestsPassed = {{requestsPassed}}\n\
>> state {{state}} - {{clientHttpRequest}} - requestsFailed = {{requestsFailed}} ({{errorDictJson}})\n\
>> state {{state}} - {{clientHttpRequest}} - {{requestsPerSecond}} requests / second\n\
>> state {{state}} - mean requests / second = {{resultJson}}\n\
>> ',
>> /* jslint-ignore-end */
>> );
>> // repeat test with other design-patterns
>> local.state -= 3;
>> local.main();
>> break;
>> default:
>> if (error) {
>> console.error(error);
>> }
>> local.exitCode = Number(!!error);
>> local.templateRenderAndPrint('state {{state}} - process.exit({{exitCode}})');
>> process.exit(local.exitCode);
>> }
>> };
>> // run main-loop
>> local.state = 0;
>> local.main();
>> }());
>> ```
>>
>> kai zhu
>> kaizhu256 at gmail.com <mailto:kaizhu256 at gmail.com>
>>
>>
>>
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org <mailto:es-discuss at mozilla.org>
>> https://mail.mozilla.org/listinfo/es-discuss <https://mail.mozilla.org/listinfo/es-discuss>
>>
>> --
>> --
>> v8-dev mailing list
>> v8-dev at googlegroups.com <mailto:v8-dev at googlegroups.com>
>> http://groups.google.com/group/v8-dev <http://groups.google.com/group/v8-dev>
>> ---
>> You received this message because you are subscribed to the Google Groups "v8-dev" group.
>> To unsubscribe from this group and stop receiving emails from it, send an email to v8-dev+unsubscribe at googlegroups.com <mailto:v8-dev+unsubscribe at googlegroups.com>.
>> For more options, visit https://groups.google.com/d/optout <https://groups.google.com/d/optout>.
>
>
> --
> --
> v8-dev mailing list
> v8-dev at googlegroups.com <mailto:v8-dev at googlegroups.com>
> http://groups.google.com/group/v8-dev <http://groups.google.com/group/v8-dev>
> ---
> You received this message because you are subscribed to the Google Groups "v8-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to v8-dev+unsubscribe at googlegroups.com <mailto:v8-dev+unsubscribe at googlegroups.com>.
> For more options, visit https://groups.google.com/d/optout <https://groups.google.com/d/optout>.
> --
>
>
>
>
> • Benedikt Meurer
> • Google Germany GmbH
> • Erika-Mann-Str. 33 <https://maps.google.com/?q=Erika-Mann-Str.+33*+**%C2%A0%E2%80%A2+%C2%A0*80636+Munich&entry=gmail&source=g>
> • 80636 Munich <https://maps.google.com/?q=Erika-Mann-Str.+33*+**%C2%A0%E2%80%A2+%C2%A0*80636+Munich&entry=gmail&source=g>
> • bmeurer at google.com <mailto:bmeurer at google.com>
>
> Geschäftsführer: Paul Manicle, Halimah DeLaine Prado
> Registergericht und -nummer: Hamburg, HRB 86891 Sitz der Gesellschaft: Hamburg
> Diese E-Mail ist vertraulich. Wenn Sie nicht der richtige Adressat sind, leiten Sie diese bitte nicht weiter, informieren Sie den Absender und löschen Sie die E-Mail und alle Anhänge. Vielen Dank. This e-mail is confidential. If you are not the right addressee please do not forward it, please inform the sender, and please erase this e-mail including any attachments. Thanks.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20180501/d780a794/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Screen Shot 2018-05-01 at 2.19.30 AM copy.jpg
Type: image/jpeg
Size: 54128 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20180501/d780a794/attachment-0001.jpg>
More information about the es-discuss
mailing list