aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/041-batches/bfs.c
blob: 2c61226faf1a2cbd6de90ed85bba7be4dfaba386 (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
/* 033's breadth first search (graph.aup's route) as a batch executor could
 * run it, on the forms Pure gives its lists here (docs/design/021, 022): edges
 * packed (a column per field in key order, from * 2^20 + to; an index of
 * buckets of 2^20 keys: a node's edges from index[node] to index[node + 1]),
 * seen DENSE (keyed by node: a bit per key, the depth at the key, four
 * bytes), queue FLAT (appended to).
 *   forms  round by round, as the executor runs the loop
 *   batch  the rounds whose test (i below queue.count) holds when the batch
 *          opens (count only grows): each step for every lane (queue read,
 *          the depth read, the edges walked into lanes of their own, seen's
 *          bit read for each), then the adds and appends in order, each
 *          lane's bit read again first (an earlier lane may have added it)
 * Against natural.h's c_bfs (offsets per node: CSR) and c_bfs_ahead (that
 * read ahead), on the suite's graph and routes (100,000 nodes, 1,000,000
 * edges drawn, seed 121). Built cc -O2; with -DLANES_TEST the edges' lanes
 * are 16 (a node with more edges runs its round alone: the answers the same). */
#define _POSIX_C_SOURCE 200809L
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "natural.h"

static double now(void) {
    struct timespec t;
    clock_gettime(CLOCK_MONOTONIC, &t);
    return t.tv_sec * 1e3 + t.tv_nsec / 1e6;
}

#ifdef LANES_TEST
enum { B = 256, LANES = 16 };
#else
enum { B = 256, LANES = 8192 };
#endif

/* seen: list of Seen by node, DENSE */
typedef struct { uint64_t *bits; uint32_t *depth; size_t count; } Seen;
static void seen_start(Seen *s, uint32_t nodes) {
    s->bits = calloc((nodes + 63) / 64, 8), s->depth = malloc(4ull * nodes), s->count = 0;
}
static bool seen_has(const Seen *s, uint32_t node) { return s->bits[node / 64] >> (node % 64) & 1; }
static void seen_add(Seen *s, uint32_t node, uint32_t depth) {
    s->bits[node / 64] |= UINT64_C(1) << (node % 64), s->depth[node] = depth, ++s->count;
}

/* edges: list of Edge by link, packed */
static uint32_t *to_col, *index_col;

/* round by round, as the executor runs the loop */
static void forms_bfs(uint32_t from, uint32_t target, uint32_t nodes, double *out) {
    Seen seen;
    seen_start(&seen, nodes);
    uint32_t *queue = malloc(4ull * nodes), count = 0;
    queue[count++] = from;
    seen_add(&seen, from, 0);
    for (uint32_t i = 0; i < nodes; ++i) {
        if (i >= count) break;
        uint32_t node = queue[i];
        for (uint32_t k = index_col[node]; k < index_col[node + 1]; ++k) {
            uint32_t to = to_col[k];
            if (!seen_has(&seen, to)) seen_add(&seen, to, seen.depth[node] + 1), queue[count++] = to;
        }
    }
    out[0] = seen_has(&seen, target) ? seen.depth[target] : -1, out[1] = (double)seen.count;
    free(seen.bits), free(seen.depth), free(queue);
}

/* a batch of rounds at a time */
static void batch_bfs(uint32_t from, uint32_t target, uint32_t nodes, double *out) {
    static uint32_t node[B], depth[B], lo[B], hi[B], edge_to[LANES], edge_round[LANES];
    static bool has[LANES];
    Seen seen;
    seen_start(&seen, nodes);
    uint32_t *queue = malloc(4ull * nodes), count = 0;
    queue[count++] = from;
    seen_add(&seen, from, 0);
    for (uint32_t i = 0; i < nodes && i < count;) {
        unsigned m = count - i < B ? count - i : B; /* the rounds whose test holds now (count only grows) */
        if (m > nodes - i) m = nodes - i;
        for (unsigned l = 0; l < m; ++l) node[l] = queue[i + l];
        for (unsigned l = 0; l < m; ++l) lo[l] = index_col[node[l]], hi[l] = index_col[node[l] + 1];
        for (unsigned l = 0; l < m; ++l) depth[l] = seen.depth[node[l]] + 1; /* seen[q.node].depth + 1 */
        unsigned e = 0, rounds = 0; /* the edges into lanes, as many rounds as fit */
        for (; rounds < m && e + (hi[rounds] - lo[rounds]) <= LANES; ++rounds)
            for (uint32_t k = lo[rounds]; k < hi[rounds]; ++k) edge_to[e] = to_col[k], edge_round[e] = rounds, ++e;
        if (!rounds) { /* a node with more edges than the lanes: its round alone, round by round */
            for (uint32_t k = lo[0]; k < hi[0]; ++k)
                if (!seen_has(&seen, to_col[k])) seen_add(&seen, to_col[k], depth[0]), queue[count++] = to_col[k];
            ++i;
            continue;
        }
        for (unsigned q = 0; q < e; ++q) has[q] = seen_has(&seen, edge_to[q]); /* seen.has(e.to), each lane */
        for (unsigned q = 0; q < e; ++q) /* the adds and appends, in order */
            if (!has[q] && !seen_has(&seen, edge_to[q])) seen_add(&seen, edge_to[q], depth[edge_round[q]]), queue[count++] = edge_to[q];
        i += rounds;
    }
    out[0] = seen_has(&seen, target) ? seen.depth[target] : -1, out[1] = (double)seen.count;
    free(seen.bits), free(seen.depth), free(queue);
}

static int cmpd(const void *a, const void *b) { double x = *(const double *)a, y = *(const double *)b; return (x > y) - (x < y); }
static void (*volatile natural[2])(const uint32_t *, double *) = {c_bfs, c_bfs_ahead};
int main(void) {
    uint64_t *keys;
    size_t edge_count = graph_build(100000, 1000000, &keys);
    to_col = malloc(4 * edge_count), index_col = calloc(100001, 4);
    for (size_t k = 0; k < edge_count; ++k) to_col[k] = (uint32_t)(keys[k] & 0xFFFFF), ++index_col[(keys[k] >> 20) + 1];
    for (uint32_t v = 0; v < 100000; ++v) index_col[v + 1] += index_col[v];
    double tc[11], ta[11], tf[11], tb[11], oc[2], oa[2], of[2], ob[2];
    bool same = true;
    for (int r = 0; r < 11; ++r) {
        uint32_t in[2] = {(uint32_t)(graph_next() % 100000), (uint32_t)(graph_next() % 100000)};
        double t = now(); natural[0](in, oc); tc[r] = now() - t;
        t = now(); natural[1](in, oa); ta[r] = now() - t;
        t = now(); forms_bfs(in[0], in[1], 100000, of); tf[r] = now() - t;
        t = now(); batch_bfs(in[0], in[1], 100000, ob); tb[r] = now() - t;
        for (int k = 0; k < 2; ++k) same &= oc[k] == oa[k] && oc[k] == of[k] && oc[k] == ob[k];
    }
    qsort(tc, 11, 8, cmpd), qsort(ta, 11, 8, cmpd), qsort(tf, 11, 8, cmpd), qsort(tb, 11, 8, cmpd);
    printf("{\"work\":\"bfs\",\"c_ms\":%.2f,\"c_ahead_ms\":%.2f,\"forms_ms\":%.2f,\"batch_ms\":%.2f,\"reached\":%.0f,\"same\":%s}\n", tc[5], ta[5], tf[5], tb[5], ob[1],
           same ? "true" : "false");
}