File size: 5,341 Bytes
0ba037b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
 * cpu_bench: the host CPU doing the work of the card's range operations, for
 * comparison with `bitacc_pcie bench` and `make perf`.
 *
 *   make -C pcie/host cpu_bench && ./pcie/host/cpu_bench [threads]
 *
 * Bit store: 16384 64-bit words (128 KiB), the LiteFury default geometry.
 *   count   COUNT over the whole store (popcount)
 *   xor     BULK XOR dry run: count of (lower half ^ upper half)
 *   match   MATCH count of a 16-bit pattern and of a 64-bit pattern at every
 *           bit position (shift-outer, word-inner loop, which compilers
 *           vectorize; the naive per-position loop is checked against it)
 * Each kind runs on 1 thread and on [threads] threads (default: all online
 * CPUs), each thread on its own copy of the store. Build with -O3
 * -march=native for the machine's vector units.
 */
#define _GNU_SOURCE
#include <inttypes.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#define WORDS 16384

static double now(void)
{
    struct timespec t;
    clock_gettime(CLOCK_MONOTONIC, &t);
    return (double)t.tv_sec + (double)t.tv_nsec * 1e-9;
}

static uint64_t count_ones(const uint64_t *m)
{
    uint64_t c = 0;
    for (int i = 0; i < WORDS; i++) c += (uint64_t)__builtin_popcountll(m[i]);
    return c;
}

static uint64_t xor_count(const uint64_t *m)
{
    uint64_t c = 0;
    for (int i = 0; i < WORDS / 2; i++) c += (uint64_t)__builtin_popcountll(m[i] ^ m[WORDS / 2 + i]);
    return c;
}

/* Positions s in [0, WORDS*64 - 64]: ((bits s..s+63) ^ pat) & mask == 0. */
static uint64_t match_count(const uint64_t *restrict m, uint64_t pat, uint64_t mask)
{
    uint64_t c = 0;
    for (int w = 0; w + 1 < WORDS; w++) c += ((m[w] ^ pat) & mask) == 0;
    for (int p = 1; p < 64; p++) {
        uint64_t cp = 0;
        for (int w = 0; w + 1 < WORDS; w++) {
            uint64_t win = (m[w] >> p) | (m[w + 1] << (64 - p));
            cp += ((win ^ pat) & mask) == 0;
        }
        c += cp;
    }
    return c;
}

static uint64_t match_naive(const uint64_t *m, uint64_t pat, uint64_t mask)
{
    uint64_t c = 0;
    for (int w = 0; w + 1 < WORDS; w++)
        for (int p = 0; p < 64; p++) {
            uint64_t win = p ? (m[w] >> p) | (m[w + 1] << (64 - p)) : m[w];
            c += ((win ^ pat) & mask) == 0;
        }
    return c;
}

enum { K_COUNT, K_XOR, K_MATCH16, K_MATCH64, K_N };
static const char *names[K_N] = {"count (1 Mbit)", "xor (2 x 64 KiB)", "match 16-bit (1 M positions)",
                                 "match 64-bit (1 M positions)"};
static const double units[K_N] = {WORDS * 64.0, WORDS * 32.0, (WORDS - 1) * 64.0, (WORDS - 1) * 64.0};

struct job {
    int kind, reps;
    uint64_t *m;
    uint64_t sink;
};

static void *worker(void *arg)
{
    struct job *j = arg;
    uint64_t s = 0;
    for (int r = 0; r < j->reps; r++) {
        j->m[r & (WORDS - 1)] ^= 1;    /* keep each repetition from being hoisted */
        switch (j->kind) {
        case K_COUNT:   s += count_ones(j->m); break;
        case K_XOR:     s += xor_count(j->m); break;
        case K_MATCH16: s += match_count(j->m, j->m[7] & 0xFFFF, 0xFFFF); break;
        default:        s += match_count(j->m, j->m[7], ~0ull); break;
        }
    }
    j->sink = s;
    return NULL;
}

/* Seconds per operation per thread, with n threads running at once. */
static double run(int kind, int n, int reps)
{
    pthread_t t[256];
    struct job j[256];
    for (int i = 0; i < n; i++) {
        j[i].kind = kind; j[i].reps = reps;
        j[i].m = aligned_alloc(64, WORDS * sizeof(uint64_t));
        uint64_t x = 88172645463325252ull + (uint64_t)i;
        for (int w = 0; w < WORDS; w++) { x ^= x << 13; x ^= x >> 7; x ^= x << 17; j[i].m[w] = x; }
    }
    double t0 = now();
    for (int i = 0; i < n; i++) pthread_create(&t[i], NULL, worker, &j[i]);
    for (int i = 0; i < n; i++) pthread_join(t[i], NULL);
    double dt = now() - t0;
    for (int i = 0; i < n; i++) free(j[i].m);
    return dt / reps;
}

int main(int argc, char **argv)
{
    int threads = argc > 1 ? atoi(argv[1]) : (int)sysconf(_SC_NPROCESSORS_ONLN);
    if (threads < 1) threads = 1;
    if (threads > 256) threads = 256;

    /* The vectorized MATCH must agree with the naive one. */
    uint64_t *m = aligned_alloc(64, WORDS * sizeof(uint64_t)), x = 1;
    for (int w = 0; w < WORDS; w++) { x ^= x << 13; x ^= x >> 7; x ^= x << 17; m[w] = x; }
    for (int k = 0; k < 4; k++) {
        uint64_t mask = k == 3 ? ~0ull : (1ull << (8 << k)) - 1, pat = m[11 * k + 3] & mask;
        if (match_count(m, pat, mask) != match_naive(m, pat, mask)) {
            fprintf(stderr, "cpu_bench: vectorized and naive MATCH disagree\n");
            return 1;
        }
    }
    free(m);

    printf("128 KiB bit store; time per operation; rates per thread and for all threads\n");
    for (int k = 0; k < K_N; k++) {
        int reps = k >= K_MATCH16 ? 40 : 4000;
        double t1 = run(k, 1, reps), tn = threads > 1 ? run(k, threads, reps) : t1;
        printf("%-30s 1 thread: %9.1f us (%6.1f G/s)   %d threads: %9.1f us each (%6.1f G/s total)\n",
               names[k], t1 * 1e6, units[k] / t1 / 1e9, threads, tn * 1e6, units[k] * threads / tn / 1e9);
    }
    printf("G/s: Gbit/s for count and xor, G positions/s for match.\n");
    return 0;
}