[njs] Refactored njs_benchmark.

Dmitry Volyntsev xeioex at nginx.com
Mon Jan 13 18:42:58 UTC 2020


details:   https://hg.nginx.org/njs/rev/6ff44c2312ac
branches:  
changeset: 1298:6ff44c2312ac
user:      Dmitry Volyntsev <xeioex at nginx.com>
date:      Mon Jan 13 21:35:15 2020 +0300
description:
Refactored njs_benchmark.

diffstat:

 auto/make                |    2 +-
 src/test/njs_benchmark.c |  447 ++++++++++++++++++++++++++++++++++------------
 2 files changed, 331 insertions(+), 118 deletions(-)

diffs (535 lines):

diff -r 16e786db8720 -r 6ff44c2312ac auto/make
--- a/auto/make	Thu Jan 09 15:55:05 2020 +0300
+++ b/auto/make	Mon Jan 13 21:35:15 2020 +0300
@@ -237,7 +237,7 @@ test: expect_test unit_test
 benchmark: $NJS_BUILD_DIR/njs_auto_config.h \\
 	$NJS_BUILD_DIR/njs_benchmark
 
-	$NJS_BUILD_DIR/njs_benchmark v
+	$NJS_BUILD_DIR/njs_benchmark
 
 dist:
 	NJS_VER=`grep NJS_VERSION src/njs.h | sed -e 's/.*"\(.*\)".*/\1/'`; \\
diff -r 16e786db8720 -r 6ff44c2312ac src/test/njs_benchmark.c
--- a/src/test/njs_benchmark.c	Thu Jan 09 15:55:05 2020 +0300
+++ b/src/test/njs_benchmark.c	Mon Jan 13 21:35:15 2020 +0300
@@ -4,7 +4,7 @@
  * Copyright (C) NGINX, Inc.
  */
 
-#include <njs.h>
+#include <njs_main.h>
 
 #include <string.h>
 #include <stdlib.h>
@@ -12,19 +12,38 @@
 #include <time.h>
 
 
+typedef struct {
+    const char  *name;
+    njs_str_t   script;
+    njs_str_t   result;
+    njs_uint_t  repeat;
+} njs_benchmark_test_t;
+
+
+typedef struct {
+    uint8_t     dump_report;
+    const char  *prefix;
+    const char  *previous;
+} njs_opts_t;
+
+
 static njs_int_t
-njs_unit_test_benchmark(njs_str_t *script, njs_str_t *result, const char *msg,
-    njs_uint_t n)
+njs_benchmark_test(njs_vm_t *parent, njs_opts_t *opts, njs_value_t *report,
+    njs_benchmark_test_t *test)
 {
-    u_char         *start;
-    njs_vm_t       *vm, *nvm;
-    uint64_t       us;
-    njs_int_t      ret;
-    njs_str_t      s;
-    njs_uint_t     i;
-    njs_bool_t     success;
-    njs_vm_opt_t   options;
-    struct rusage  usage;
+    u_char        *start;
+    njs_vm_t      *vm, *nvm;
+    uint64_t      us;
+    njs_int_t     ret;
+    njs_str_t     s, *expected;
+    njs_uint_t    i, n;
+    njs_bool_t    success;
+    njs_value_t   *result, name, usec, times;
+    njs_vm_opt_t  options;
+
+    static const njs_value_t  name_key = njs_string("name");
+    static const njs_value_t  usec_key = njs_string("usec");
+    static const njs_value_t  times_key = njs_string("times");
 
     njs_memzero(&options, sizeof(njs_vm_opt_t));
 
@@ -38,14 +57,19 @@ njs_unit_test_benchmark(njs_str_t *scrip
         goto done;
     }
 
-    start = script->start;
+    start = test->script.start;
 
-    ret = njs_vm_compile(vm, &start, start + script->length);
+    ret = njs_vm_compile(vm, &start, start + test->script.length);
     if (ret != NJS_OK) {
         njs_printf("njs_vm_compile() failed\n");
         goto done;
     }
 
+    n = test->repeat;
+    expected = &test->result;
+
+    us = njs_time() / 1000;
+
     for (i = 0; i < n; i++) {
 
         nvm = njs_vm_clone(vm, NULL);
@@ -61,10 +85,10 @@ njs_unit_test_benchmark(njs_str_t *scrip
             goto done;
         }
 
-        success = njs_strstr_eq(result, &s);
+        success = njs_strstr_eq(expected, &s);
 
         if (!success) {
-            njs_printf("failed: \"%V\" vs \"%V\"\n", result, &s);
+            njs_printf("failed: \"%V\" vs \"%V\"\n", expected, &s);
             goto done;
         }
 
@@ -72,17 +96,42 @@ njs_unit_test_benchmark(njs_str_t *scrip
         nvm = NULL;
     }
 
-    getrusage(RUSAGE_SELF, &usage);
+    us = njs_time() / 1000 - us;
+
+    if (!opts->dump_report) {
+        if (n == 1) {
+            njs_printf("%s%s: %.3fs\n", opts->previous ? "    " : "",
+                       test->name, (double) us / 1000000);
 
-    us = usage.ru_utime.tv_sec * 1000000 + usage.ru_utime.tv_usec
-         + usage.ru_stime.tv_sec * 1000000 + usage.ru_stime.tv_usec;
+        } else {
+            njs_printf("%s%s: %.3fµs, %d times/s\n",
+                       opts->previous ? "    " : "",
+                       test->name, (double) us / n,
+                       (int) ((uint64_t) n * 1000000 / us));
+        }
+    }
 
-    if (n == 1) {
-        njs_printf("%s: %.3fs\n", msg, (double) us / 1000000);
+    result = njs_vm_array_push(parent, report);
+    if (result == NULL) {
+        njs_printf("njs_vm_array_push() failed\n");
+        goto done;
+    }
 
-    } else {
-        njs_printf("%s: %.3fµs, %d times/s\n",
-                   msg, (double) us / n, (int) ((uint64_t) n * 1000000 / us));
+    ret = njs_vm_value_string_set(parent, &name, (u_char *) test->name,
+                                  njs_strlen(test->name));
+    if (ret != NJS_OK) {
+        njs_printf("njs_vm_value_string_set() failed\n");
+        goto done;
+    }
+
+    njs_value_number_set(&usec, us);
+    njs_value_number_set(&times, n);
+
+    ret = njs_vm_object_alloc(parent, result, &name_key, &name,
+                              &usec_key, &usec, &times_key, &times, NULL);
+    if (ret != NJS_OK) {
+        njs_printf("njs_vm_object_alloc() failed\n");
+        goto done;
     }
 
     ret = NJS_OK;
@@ -100,117 +149,281 @@ done:
     return ret;
 }
 
+static njs_benchmark_test_t  njs_test[] =
+{
+    { "nJSVM clone/destroy",
+      njs_str("null"),
+      njs_str("null"),
+      1000000 },
+
+    { "JSON.parse",
+      njs_str("JSON.parse('{\"a\":123, \"XXX\":[3,4,null]}').a"),
+      njs_str("123"),
+      1000000 },
+
+    { "for loop 100M",
+      njs_str("var i; for (i = 0; i < 100000000; i++); i"),
+      njs_str("100000000"),
+      1 },
+
+    { "while loop 100M",
+      njs_str("var i = 0; while (i < 100000000) { i++ }; i"),
+      njs_str("100000000"),
+      1 },
+
+    { "fibobench numbers",
+      njs_str("function fibo(n) {"
+              "    if (n > 1)"
+              "        return fibo(n - 1) + fibo(n - 2);"
+              "    return 1"
+              "}"
+              "fibo(32)"),
+      njs_str("3524578"),
+      1 },
+
+    { "fibobench ascii strings",
+      njs_str("function fibo(n) {"
+              "    if (n > 1)"
+              "        return fibo(n - 1) + fibo(n - 2);"
+              "    return '.'"
+              "}"
+              "fibo(32).length"),
+      njs_str("3524578"),
+      1 },
+
+    { "fibobench byte strings",
+      njs_str("var a = '\\x80'.toBytes();"
+              "function fibo(n) {"
+              "    if (n > 1)"
+              "        return fibo(n - 1) + fibo(n - 2);"
+              "    return 'a'"
+              "}"
+              "fibo(32).length"),
+      njs_str("3524578"),
+      1 },
+
+    { "fibobench utf8 strings",
+      njs_str("function fibo(n) {"
+              "    if (n > 1)"
+              "        return fibo(n - 1) + fibo(n - 2);"
+              "    return 'α'"
+              "}"
+              "fibo(32).length"),
+      njs_str("3524578"),
+      1 },
+
+    { "array 10M",
+      njs_str("var arr = new Array(10000000);"
+              "var count = 0, length = arr.length;"
+              "arr.fill(2);"
+              "for (var i = 0; i < length; i++) { count += arr[i]; }"
+              "count"),
+      njs_str("20000000"),
+      1 },
+
+    { "typed array 10M",
+      njs_str("var arr = new Uint8Array(10000000);"
+              "var count = 0, length = arr.length;"
+              "arr.fill(2);"
+              "for (var i = 0; i < length; i++) { count += arr[i]; }"
+              "count"),
+      njs_str("20000000"),
+      1 },
+};
+
+
+static njs_str_t  code = njs_str(
+    "import fs from 'fs';"
+    ""
+    "function compare(prev_fn, current) {"
+    "  var prev_report = JSON.parse(fs.readFileSync(prev_fn));"
+    "  var test, prev, diff, result = [`Diff with ${prev_fn}:`];"
+    "  for (var t in current) {"
+    "    test = current[t];"
+    "    prev = find(prev_report, test.name);"
+    "    diff = (test.usec - prev.usec) / prev.usec * 100;"
+    "    result.push(`    ${test.name}: ${diff.toFixed(2)}%`);"
+    "  }"
+    "  return result.join('\\n') + '\\n';"
+    "}"
+    ""
+    "function find(report, name) {"
+    "  for (var t in report) {"
+    "     if (report[t].name == name) { return report[t];}"
+    "  }"
+    "}");
+
 
 int njs_cdecl
 main(int argc, char **argv)
 {
-    static njs_str_t  script = njs_str("null");
-    static njs_str_t  result = njs_str("null");
-
-    static njs_str_t  fibo_number = njs_str(
-        "function fibo(n) {"
-        "    if (n > 1)"
-        "        return fibo(n - 1) + fibo(n - 2);"
-        "    return 1"
-        "}"
-        "fibo(32)");
-
-    static njs_str_t  fibo_ascii = njs_str(
-        "function fibo(n) {"
-        "    if (n > 1)"
-        "        return fibo(n - 1) + fibo(n - 2);"
-        "    return '.'"
-        "}"
-        "fibo(32).length");
-
-    static njs_str_t  fibo_bytes = njs_str(
-        "var a = '\\x80'.toBytes();"
-        "function fibo(n) {"
-        "    if (n > 1)"
-        "        return fibo(n - 1) + fibo(n - 2);"
-        "    return a"
-        "}"
-        "fibo(32).length");
-
-    static njs_str_t  fibo_utf8 = njs_str(
-        "function fibo(n) {"
-        "    if (n > 1)"
-        "        return fibo(n - 1) + fibo(n - 2);"
-        "    return 'α'"
-        "}"
-        "fibo(32).length");
-
-    static njs_str_t  json = njs_str(
-        "JSON.parse('{\"a\":123, \"XXX\":[3,4,null]}').a");
-
-    static njs_str_t  for_loop = njs_str(
-        "var i; for (i = 0; i < 100000000; i++); i");
+    char                  *p;
+    u_char                *start;
+    njs_vm_t              *vm;
+    njs_int_t             ret, k;
+    njs_str_t             out;
+    njs_uint_t            i;
+    njs_opts_t            opts;
+    njs_value_t           args[2], report;
+    njs_vm_opt_t          options;
+    njs_benchmark_test_t  *test;
 
-    static njs_str_t while_loop = njs_str(
-        "var i = 0; while (i < 100000000) { i++ }; i");
-
-    static njs_str_t  typed_array_10M = njs_str(
-        "var arr = new Uint8Array(10000000); var count = 0, length = arr.length;"
-        "arr.fill(2);"
-        "for (var i = 0; i < length; i++) { count += arr[i]; } count");
+    static const char  help[] =
+        "njs benchmark.\n"
+        "\n"
+        "njs_benchmark [OPTIONS]"
+        "\n"
+        "Options:\n"
+        "  -b <name_prefix>  specify the benchmarks to execute.\n"
+        "  -d                dump report as a JSON file.\n"
+        "  -c <report file>  compare with previous report.\n"
+        "  -h                this help.\n";
 
-    static njs_str_t  array_10M = njs_str(
-        "var arr = new Array(10000000); var count = 0, length = arr.length;"
-        "arr.fill(2);"
-        "for (var i = 0; i < length; i++) { count += arr[i]; } count");
-
-    static njs_str_t  fibo_result = njs_str("3524578");
-    static njs_str_t  json_result = njs_str("123");
-    static njs_str_t  loop_result = njs_str("100000000");
-    static njs_str_t  sum_result  = njs_str("20000000");
-
-
-    if (argc > 1) {
-        switch (argv[1][0]) {
+    static const njs_str_t  compare = njs_str("compare");
 
-        case 'v':
-            return njs_unit_test_benchmark(&script, &result,
-                                           "nJSVM clone/destroy", 1000000);
+    njs_memzero(&opts, sizeof(njs_opts_t));
+    opts.prefix = "";
 
-        case 'j':
-            return njs_unit_test_benchmark(&json, &json_result,
-                                           "JSON.parse", 1000000);
-
-        case 'f':
-            return njs_unit_test_benchmark(&for_loop, &loop_result,
-                                           "for loop 100M", 1);
+    for (k = 1; k < argc; k++) {
+        p = argv[k];
 
-        case 'w':
-            return njs_unit_test_benchmark(&while_loop, &loop_result,
-                                           "while loop 100M", 1);
+        if (p[0] != '-') {
+            goto invalid_options;
+        }
+
+        p++;
 
-        case 'n':
-            return njs_unit_test_benchmark(&fibo_number, &fibo_result,
-                                           "fibobench numbers", 1);
-
-        case 'a':
-            return njs_unit_test_benchmark(&fibo_ascii, &fibo_result,
-                                           "fibobench ascii strings", 1);
+        switch (*p) {
+        case '?':
+        case 'h':
+            njs_print(help, njs_length(help));
+            return EXIT_SUCCESS;
 
         case 'b':
-            return njs_unit_test_benchmark(&fibo_bytes, &fibo_result,
-                                           "fibobench byte strings", 1);
+            if (++k < argc) {
+                opts.prefix = argv[k];
+                break;
+            }
 
-        case 'u':
-            return njs_unit_test_benchmark(&fibo_utf8, &fibo_result,
-                                           "fibobench utf8 strings", 1);
+            njs_stderror("option \"-b\" requires argument\n");
+            return EXIT_FAILURE;
 
-        case 't':
-            return njs_unit_test_benchmark(&typed_array_10M, &sum_result,
-                                           "typed_array_10M", 1);
+        case 'c':
+            if (++k < argc) {
+                opts.previous = argv[k];
+                break;
+            }
 
-        case 'A':
-            return njs_unit_test_benchmark(&array_10M, &sum_result,
-                                           "array_10M", 1);
+            njs_stderror("option \"-c\" requires argument\n");
+            return EXIT_FAILURE;
+
+        case 'd':
+            opts.dump_report = 1;
+            break;
+
+        default:
+            goto invalid_options;
         }
     }
 
-    njs_printf("unknown agrument\n");
+    njs_memzero(&options, sizeof(njs_vm_opt_t));
+    options.init = 1;
+    options.argv = argv;
+    options.argc = argc;
+
+    vm = njs_vm_create(&options);
+    if (vm == NULL) {
+        njs_printf("njs_vm_create() failed\n");
+        return EXIT_FAILURE;
+    }
+
+    start = code.start;
+    ret = njs_vm_compile(vm, &start, start + code.length);
+    if (ret != NJS_OK) {
+        njs_printf("njs_vm_compile() failed\n");
+        goto done;
+    }
+
+    njs_vm_start(vm);
+
+    ret = EXIT_FAILURE;
+
+    ret = njs_vm_array_alloc(vm, &report, 8);
+    if (ret != NJS_OK) {
+        njs_printf("njs_vm_array_alloc() failed\n");
+        goto done;
+    }
+
+    if (opts.previous) {
+        njs_printf("Current:\n");
+    }
+
+    for (i = 0; i < njs_nitems(njs_test); i++) {
+        test = &njs_test[i];
+
+        if (strncmp(test->name, opts.prefix,
+                    njs_min(strlen(test->name), strlen(opts.prefix))) == 0)
+        {
+            ret = njs_benchmark_test(vm, &opts, &report, test);
+
+            if (ret != NJS_OK) {
+                goto done;
+            }
+        }
+    }
+
+    if (opts.previous) {
+        ret = njs_vm_value_string_set(vm, &args[0], (u_char *) opts.previous,
+                                      njs_strlen(opts.previous));
+        if (ret != NJS_OK) {
+            njs_printf("njs_vm_value_string_set() failed\n");
+            goto done;
+        }
+
+        args[1] = report;
+
+        njs_vm_call(vm, njs_vm_function(vm, &compare), njs_value_arg(&args), 2);
+
+        ret = njs_vm_value_dump(vm, &out, njs_vm_retval(vm), 1, 1);
+        if (ret != NJS_OK) {
+            njs_printf("njs_vm_retval_dump() failed\n");
+            goto done;
+        }
+
+        njs_print(out.start, out.length);
+
+        return EXIT_SUCCESS;
+    }
+
+    if (opts.dump_report) {
+        ret = njs_vm_json_stringify(vm, &report, 1);
+        if (ret != NJS_OK) {
+            njs_printf("njs_vm_json_stringify() failed\n");
+            goto done;
+        }
+
+        ret = njs_vm_value_dump(vm, &out, njs_vm_retval(vm), 1, 1);
+        if (ret != NJS_OK) {
+            njs_printf("njs_vm_retval_dump() failed\n");
+            goto done;
+        }
+
+        njs_print(out.start, out.length);
+    }
+
+    ret = EXIT_SUCCESS;
+
+done:
+
+    njs_vm_destroy(vm);
+
+    return ret;
+
+invalid_options:
+
+    njs_stderror("Unknown argument: \"%s\" "
+                 "try \"%s -h\" for available options\n", argv[k],
+                 argv[0]);
 
     return EXIT_FAILURE;
 }


More information about the nginx-devel mailing list