diff options
| author | eunhhu <[email protected]> | 2026-10-05 20:12:48 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-05 20:12:48 +0100 |
| commit | 81c8c5130894826d70b57a97926cc70b8a2ec689 (patch) | |
| tree | 52dbb86485aeddf11bbadb2a325c2813f80257f0 /research/experiments/041-batches/join.c | |
| parent | a099d63e2322f5ae17e8fbdc51d6cc020638a5dc (diff) | |
| download | pure-81c8c5130894826d70b57a97926cc70b8a2ec689.tar.gz pure-81c8c5130894826d70b57a97926cc70b8a2ec689.zip | |
041 batches: loops run a batch of rounds at a time, each operation a C loop made at build time (no code made at run time), on Pure's forms and checks, against C natural and C given the same restructuring: roots 6.79 ms (C 6.86), knapsack fused four-byte 2.70 (C 6.47, C in the batch's blocks 1.94), matmul lanes over j 6.67 (C 7.10, C swapped and vector 4.88), join 37 / 46 with its recurrence interpreted (C 30), BFS 10.3 (C 10.0, C read ahead 7.0); unfused interpreted operations 11.2 and 17.7-22.1
Diffstat (limited to 'research/experiments/041-batches/join.c')
| -rw-r--r-- | research/experiments/041-batches/join.c | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/research/experiments/041-batches/join.c b/research/experiments/041-batches/join.c new file mode 100644 index 0000000..35b8462 --- /dev/null +++ b/research/experiments/041-batches/join.c @@ -0,0 +1,135 @@ +/* join as a batch executor could run it, on Pure's own forms: all (FLAT, ids as + * u32 cells), byid (HASHED: key bits in a table of slots 4/5 full at most, the + * region at its key's slot), totals (DENSE, 16 sums). Both loops a batch of + * rounds at a time: the MINSTD recurrence in order (in C, or interpreted: + * the executor's part), then each step of the round for every lane (the + * setup: the appends, the adds in order; the orders: ids gathered, slots + * probed, regions gathered, the sums added in order). Against natural.h's + * c_join (its own hash table), both timed from nothing to the answer, the + * sizes read at run time. */ +#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; } +static const uint64_t VACANT = 0x7FF8000000000001ull; +static uint64_t key_bits(double k) { if (k == 0) k = 0; uint64_t b; memcpy(&b, &k, 8); return b; } +static size_t slot_of(uint64_t bits, size_t mask) { bits ^= bits >> 33; bits *= 0xff51afd7ed558ccdull; bits ^= bits >> 33; return bits & mask; } +enum { B = 256 }; +/* A section that cannot run as a batch (each round needs the last's x) run + * round by round by an interpreter of the same operations (computed goto, + * Pure's checks): x = x * 48271 - floor(x * 48271 / 2147483647) * 2147483647, + * each round's x into its lane. */ +enum { S_MULK, S_FLOORDIVK, S_SUB, S_PUT, S_END }; +typedef struct { uint8_t op, d, a, b; int64_t k; uint64_t magic; unsigned shift; } SOp; /* magic, shift: k's reciprocal, made with the program */ +static SOp minstd_ops[] = { + {S_MULK, 1, 0, 0, 48271}, /* t = x * 48271 */ + {S_FLOORDIVK, 2, 1, 0, 2147483647}, /* q = floor(t / 2147483647) */ + {S_MULK, 2, 2, 0, 2147483647}, /* q * 2147483647 */ + {S_SUB, 0, 1, 2, 0}, /* x = t - that */ + {S_PUT, 0, 0, 0, 0}, /* the lane's x */ + {S_END, 0, 0, 0, 0}, +}; +static void rounds_prepare(SOp *prog, bool magic) { /* a divisor's reciprocal, k not a power of two: q = (x m) >> (64 + s), 2^s < k, exact for x below 2^53 */ + for (; prog->op != S_END; ++prog) + if (prog->op == S_FLOORDIVK && prog->k > 2 && (prog->k & (prog->k - 1)) && magic) { + unsigned s = 0; + while ((2ull << s) < (uint64_t)prog->k) ++s; + prog->magic = (uint64_t)((((unsigned __int128)1 << (64 + s)) / (uint64_t)prog->k) + 1), prog->shift = s; + } else prog->magic = 0; +} +__attribute__((noinline)) static bool rounds_run(const SOp *prog, int64_t *r, uint64_t *lanes, unsigned m) { + static void *const at[] = {&&mulk, &&floordivk, &&sub, &&put, &&end}; + int64_t over = 0; + for (unsigned l = 0; l < m; ++l) { + const SOp *o = prog; + goto *at[o->op]; + mulk: { int64_t x = r[o->a] * o->k; over |= (x >> 53) ^ (x >> 63); r[o->d] = x; ++o; goto *at[o->op]; } + floordivk: { + int64_t x = r[o->a], q; + if (x >= 0 && o->magic) q = (int64_t)(((unsigned __int128)(uint64_t)x * o->magic) >> 64 >> o->shift); /* below 2^53 */ + else q = x / o->k, q -= (x % o->k != 0) & ((x < 0) != (o->k < 0)); + r[o->d] = q; ++o; goto *at[o->op]; + } + sub: { int64_t x = r[o->a] - r[o->b]; over |= (x >> 53) ^ (x >> 63); r[o->d] = x; ++o; goto *at[o->op]; } + put: lanes[l] = (uint64_t)r[o->a]; ++o; goto *at[o->op]; + end: ; + } + return over != 0; +} +static void pure_join(uint32_t customers, uint32_t orders, int interpreted, double *out) { + rounds_prepare(minstd_ops, interpreted == 2); + uint32_t *ids = malloc(4 * (size_t)customers); + size_t cap = 16; + while (5 * (size_t)customers > 4 * cap) cap *= 2; + uint64_t *members = malloc(8 * cap); + uint32_t *region = malloc(4 * cap); + for (size_t i = 0; i < cap; ++i) members[i] = VACANT; + uint64_t x = 1; + int64_t totals[16] = {0}; + static uint64_t X[B], C[B], BITS[B], SLOT[B]; + static uint32_t R[B]; + bool bad = false; + for (uint32_t i0 = 0; i0 < customers; i0 += B) { /* the setup: the recurrence, then the batch's appends, then its adds in order */ + unsigned m = customers - i0 < B ? customers - i0 : B; + if (interpreted) { + int64_t r[3] = {(int64_t)x}; + bad |= rounds_run(minstd_ops, r, X, m); + x = (uint64_t)r[0]; + } else + for (unsigned l = 0; l < m; ++l) x = x * 48271 % 2147483647, X[l] = x; + for (unsigned l = 0; l < m; ++l) ids[i0 + l] = (uint32_t)X[l]; + for (unsigned l = 0; l < m; ++l) { + uint64_t bits = key_bits((double)X[l]); + size_t at = slot_of(bits, cap - 1); + while (members[at] != VACANT) at = (at + 1) & (cap - 1); + members[at] = bits, region[at] = (i0 + l) % 16; + } + } + for (uint32_t o0 = 0; o0 < orders; o0 += B) { + unsigned m = orders - o0 < B ? orders - o0 : B; + if (interpreted) { /* the recurrence, in order */ + int64_t r[3] = {(int64_t)x}; + bad |= rounds_run(minstd_ops, r, X, m); + x = (uint64_t)r[0]; + } else + for (unsigned l = 0; l < m; ++l) x = x * 48271 % 2147483647, X[l] = x; + for (unsigned l = 0; l < m; ++l) C[l] = X[l] % customers; /* first + (x - floor(x / n) n) */ + for (unsigned l = 0; l < m; ++l) BITS[l] = key_bits((double)ids[C[l]]); /* c.id, gathered */ + for (unsigned l = 0; l < m; ++l) { /* byid[c.id]: each lane's slot */ + size_t at = slot_of(BITS[l], cap - 1); + while (members[at] != BITS[l]) { + if (members[at] == VACANT) { bad = true; break; } + at = (at + 1) & (cap - 1); + } + SLOT[l] = at; + } + for (unsigned l = 0; l < m; ++l) R[l] = region[SLOT[l]]; /* .region, gathered */ + for (unsigned l = 0; l < m; ++l) totals[R[l]] += (int64_t)(X[l] % 1000); /* the sums, in order */ + } + int64_t most = 0, sum = 0; + for (int r = 0; r < 16; ++r) most = totals[r] > most ? totals[r] : most, sum += totals[r]; + out[0] = bad ? -1 : (double)most, out[1] = (double)sum; + free(ids), free(members), free(region); +} +static int cmpd(const void *a, const void *b) { double x = *(const double *)a, y = *(const double *)b; return (x > y) - (x < y); } +static volatile uint32_t sizes[2] = {100000, 1000000}; /* read at run time */ +static void (*volatile natural)(const uint32_t *, double *) = c_join; +int main(void) { + double tc[11], tp[11], ti[11], tm[11], oc[2], op[2], oi[2], om[2]; + uint32_t in[2] = {sizes[0], sizes[1]}; + for (int r = 0; r < 11; ++r) { + double t = now(); natural(in, oc); tc[r] = now() - t; + t = now(); pure_join(in[0], in[1], 0, op); tp[r] = now() - t; + t = now(); pure_join(in[0], in[1], 1, oi); ti[r] = now() - t; + t = now(); pure_join(in[0], in[1], 2, om); tm[r] = now() - t; + } + qsort(tc, 11, 8, cmpd), qsort(tp, 11, 8, cmpd), qsort(ti, 11, 8, cmpd), qsort(tm, 11, 8, cmpd); + printf("{\"work\":\"join\",\"c_ms\":%.2f,\"batch_ms\":%.2f,\"interpreted_ms\":%.2f,\"reciprocal_ms\":%.2f,\"same\":%s}\n", tc[5], tp[5], ti[5], tm[5], + oc[0] == op[0] && oc[1] == op[1] && oc[0] == oi[0] && oc[1] == oi[1] && oc[0] == om[0] && oc[1] == om[1] ? "true" : "false"); +} |
