aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/044-specs/json_time.c
blob: b401ece11e3d90566ba9c826bfbf84b445fba107 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/* Research 140, design 027 stage 2a: aup's examples/json.aup (run by Pure)
 * against the same validator in C, on the same document: both must say it is
 * JSON, then each is timed (the median of seven runs of rounds checks).
 *
 *   cc -O2 -std=c17 -I../../../core json_time.c ../../../build/libpure.a -lm -pthread -o json_time
 *   ./json_time json.pmod ROUNDS     (PURE_JIT and PURE_BATCH choose Pure's path)
 *
 * The document is json_check.py's: 2,000 objects of a key and an array of a
 * number, true, null and a string with an escape. One JSON line. */
#define _POSIX_C_SOURCE 200809L
#include "module.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

static int space(int b) { return b == 0x20 || b == 0x09 || b == 0x0a || b == 0x0d; }
static int digit(int b) { return b >= 0x30 && b <= 0x39; }
static int hex(int b) { return digit(b) || (b >= 0x61 && b <= 0x66) || (b >= 0x41 && b <= 0x46); }

/* json.aup's design in C (its UTF-8 check left out: the document is ASCII). */
static int valid(const unsigned char *d, size_t n, unsigned char *open) {
    size_t i = 0, depth = 0;
    int mode = 0, ok = 1;
    while (ok) {
        while (i < n && space(d[i])) ++i;
        if (i == n) return mode == 1 && !depth;
        int b = d[i];
        if (mode == 1) {
            if (!depth) ok = 0;
            else if (b == 0x2c) mode = open[depth - 1] == 0x5b ? 0 : 2;
            else if (b == open[depth - 1] + 2) --depth;
            else ok = 0;
            ++i;
        } else if ((mode == 3 && b == 0x7d) || (mode == 4 && b == 0x5d)) {
            --depth, mode = 1, ++i;
        } else if (mode == 2 || mode == 3) {
            ok = b == 0x22, mode = 5;
        } else if (b == 0x5b || b == 0x7b) {
            open[depth++] = (unsigned char)b, mode = b == 0x5b ? 4 : 3, ++i;
        } else if (b == 0x22 || mode == 5) {
            ++i;
            while (i < n && d[i] != 0x22 && ok) {
                if (d[i] < 0x20) ok = 0;
                else if (d[i] == 0x5c && i + 1 < n && d[i + 1] == 0x75) {
                    ok = i + 5 < n && hex(d[i + 2]) && hex(d[i + 3]) && hex(d[i + 4]) && hex(d[i + 5]);
                    i += 6;
                } else if (d[i] == 0x5c) {
                    ok = i + 1 < n && strchr("\"\\/bfnrt", d[i + 1]) && d[i + 1];
                    i += 2;
                } else ++i;
            }
            ok = ok && i < n, ++i;
            if (mode == 5) {
                while (i < n && space(d[i])) ++i;
                ok = ok && i < n && d[i] == 0x3a, ++i, mode = 0;
            } else mode = 1;
        } else if (b == 0x2d || digit(b)) {
            if (b == 0x2d) ++i;
            ok = i < n && digit(d[i]);
            if (ok && d[i] == 0x30) ++i;
            else while (i < n && digit(d[i])) ++i;
            if (ok && i < n && d[i] == 0x2e) {
                ++i, ok = i < n && digit(d[i]);
                while (i < n && digit(d[i])) ++i;
            }
            if (ok && i < n && (d[i] == 0x65 || d[i] == 0x45)) {
                ++i;
                if (i < n && (d[i] == 0x2b || d[i] == 0x2d)) ++i;
                ok = i < n && digit(d[i]);
                while (i < n && digit(d[i])) ++i;
            }
            mode = 1;
        } else {
            const char *word = b == 0x74 ? "true" : b == 0x66 ? "false" : "null";
            size_t length = strlen(word);
            ok = i + length <= n && !memcmp(d + i, word, length), i += length, mode = 1;
        }
    }
    return 0;
}

static double now_ns(void) {
    struct timespec t;
    clock_gettime(CLOCK_MONOTONIC, &t);
    return (double)t.tv_sec * 1e9 + (double)t.tv_nsec;
}

static int by_value(const void *a, const void *b) {
    double x = *(const double *)a, y = *(const double *)b;
    return (x > y) - (x < y);
}

int main(int argc, char **argv) {
    if (argc != 3) { fprintf(stderr, "usage: %s MODULE ROUNDS\n", argv[0]); return 2; }
    int rounds = atoi(argv[2]);
    static unsigned char doc[1 << 18], open[1 << 18];
    size_t n = 0;
    doc[n++] = '[';
    for (int k = 0; k < 2000; ++k)
        n += (size_t)sprintf((char *)doc + n, "%s{\"key%d\": [1.5e3, true, null, \"text \\u00e9 %d\"]}", k ? "," : "", k, k);
    doc[n++] = ']';
    PureModule module;
    char error[160];
    if (pure_module_read(argv[1], &module, error, sizeof(error)) != PURE_OK) { fprintf(stderr, "%s\n", error); return 1; }
    PureApp app;
    if (pure_app_start(&app, &module, (PureData){0}) != PURE_OK) return 1;
    int check = pure_module_input(&module, "check");
    if (pure_app_message(&app, (uint32_t)check, NULL, -1, (PureSpan){doc, n}) != PURE_OK) { fprintf(stderr, "event failed\n"); return 1; }
    uint64_t work = app.work;
    if (app.values[0].num != 1 || !valid(doc, n, open)) { fprintf(stderr, "not both valid\n"); return 1; }
    double c_runs[7], pure_runs[7];
    volatile int sink = 0;
    for (int r = 0; r < 7; ++r) {
        double start = now_ns();
        for (int i = 0; i < rounds; ++i) sink += valid(doc, n, open);
        c_runs[r] = (now_ns() - start) / rounds;
        start = now_ns();
        for (int i = 0; i < rounds; ++i)
            if (pure_app_message(&app, (uint32_t)check, NULL, -1, (PureSpan){doc, n}) != PURE_OK) return 1;
        pure_runs[r] = (now_ns() - start) / rounds;
    }
    qsort(c_runs, 7, sizeof(double), by_value);
    qsort(pure_runs, 7, sizeof(double), by_value);
    const char *jit = getenv("PURE_JIT"), *batch = getenv("PURE_BATCH");
    printf("{\"size\":%zu,\"jit\":\"%s\",\"batch\":\"%s\",\"c_ns_per_byte\":%.3f,\"pure_ns_per_byte\":%.3f,\"ratio\":%.1f,"
           "\"instructions\":%llu,\"instructions_per_byte\":%.1f}\n",
           n, jit ? jit : "", batch ? batch : "", c_runs[3] / (double)n, pure_runs[3] / (double)n, pure_runs[3] / c_runs[3],
           (unsigned long long)work, (double)work / (double)n);
    pure_app_stop(&app);
    return 0;
}