Add benchmark
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
test
|
||||
hackers_bench
|
||||
@@ -0,0 +1,22 @@
|
||||
CC = cc
|
||||
CFLAGS = -O2 -std=c11 -D_POSIX_C_SOURCE=199309L -I. -I/usr/include -Wall -Wextra
|
||||
LDFLAGS = -lupb -lutf8_range
|
||||
|
||||
SRCS = hackers_bench.c hackers.upb.c hackers.upb_minitable.c
|
||||
TARGET = hackers_bench
|
||||
|
||||
.PHONY: all clean regen
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(SRCS)
|
||||
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
# Re-generate upb files from proto/hackers.proto
|
||||
regen:
|
||||
protoc -I ../proto ../proto/hackers.proto \
|
||||
--upb_out=. \
|
||||
--upb_minitable_out=.
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET)
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,175 @@
|
||||
/* This file was generated by upb_generator from the input file:
|
||||
*
|
||||
* hackers.proto
|
||||
*
|
||||
* Do not edit -- your changes will be discarded when the file is
|
||||
* regenerated.
|
||||
* NO CHECKED-IN PROTOBUF GENCODE */
|
||||
|
||||
#include <stddef.h>
|
||||
#include "upb/generated_code_support.h"
|
||||
#include "hackers.upb_minitable.h"
|
||||
|
||||
// Must be last.
|
||||
#include "upb/port/def.inc"
|
||||
|
||||
extern const struct upb_MiniTable UPB_PRIVATE(_kUpb_MiniTable_StaticallyTreeShaken);
|
||||
static const upb_MiniTableField Tool__fields[5] = {
|
||||
{1, 16, 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||
{2, UPB_SIZE(24, 32), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||
{3, UPB_SIZE(32, 48), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||
{4, 8, 0, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)},
|
||||
{5, 12, 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)},
|
||||
};
|
||||
|
||||
const upb_MiniTable Tool_msg_init = {
|
||||
NULL,
|
||||
&Tool__fields[0],
|
||||
UPB_SIZE(40, 64), 5, kUpb_ExtMode_NonExtendable, 5, UPB_FASTTABLE_MASK(255), 0,
|
||||
#ifdef UPB_TRACING_ENABLED
|
||||
"Tool",
|
||||
#endif
|
||||
};
|
||||
|
||||
const upb_MiniTable* Tool_msg_init_ptr = &Tool_msg_init;
|
||||
static const upb_MiniTableField Connection__fields[5] = {
|
||||
{1, 16, 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||
{2, 12, 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)},
|
||||
{3, 8, 0, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)},
|
||||
{4, UPB_SIZE(32, 48), 0, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)},
|
||||
{5, UPB_SIZE(24, 32), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||
};
|
||||
|
||||
const upb_MiniTable Connection_msg_init = {
|
||||
NULL,
|
||||
&Connection__fields[0],
|
||||
UPB_SIZE(40, 56), 5, kUpb_ExtMode_NonExtendable, 5, UPB_FASTTABLE_MASK(255), 0,
|
||||
#ifdef UPB_TRACING_ENABLED
|
||||
"Connection",
|
||||
#endif
|
||||
};
|
||||
|
||||
const upb_MiniTable* Connection_msg_init_ptr = &Connection_msg_init;
|
||||
static const upb_MiniTableSubInternal Hacker__submsgs[2] = {
|
||||
{.UPB_PRIVATE(submsg) = &Tool_msg_init_ptr},
|
||||
{.UPB_PRIVATE(submsg) = &Connection_msg_init_ptr},
|
||||
};
|
||||
|
||||
static const upb_MiniTableField Hacker__fields[9] = {
|
||||
{1, UPB_SIZE(32, 24), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||
{2, 40, 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||
{3, 12, 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)},
|
||||
{4, 16, 0, kUpb_NoSub, 2, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)},
|
||||
{5, 9, 0, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)},
|
||||
{6, UPB_SIZE(48, 56), 0, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)},
|
||||
{7, UPB_SIZE(20, 64), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||
{8, UPB_SIZE(24, 72), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||
{9, UPB_SIZE(28, 80), 64, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||
};
|
||||
|
||||
const upb_MiniTable Hacker_msg_init = {
|
||||
&Hacker__submsgs[0],
|
||||
&Hacker__fields[0],
|
||||
UPB_SIZE(56, 88), 9, kUpb_ExtMode_NonExtendable, 9, UPB_FASTTABLE_MASK(56), 0,
|
||||
#ifdef UPB_TRACING_ENABLED
|
||||
"Hacker",
|
||||
#endif
|
||||
UPB_FASTTABLE_INIT({
|
||||
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||
{0x001000003f000025, &upb_DecodeFast_Fixed32_Scalar_Tag1Byte},
|
||||
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||
{0x0000000000000000, &_upb_FastDecoder_DecodeGeneric},
|
||||
})
|
||||
};
|
||||
|
||||
const upb_MiniTable* Hacker_msg_init_ptr = &Hacker_msg_init;
|
||||
static const upb_MiniTableField Worm__fields[6] = {
|
||||
{1, UPB_SIZE(20, 16), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||
{2, 12, 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)},
|
||||
{3, UPB_SIZE(40, 48), 0, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)},
|
||||
{4, UPB_SIZE(28, 32), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||
{5, 8, 0, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)},
|
||||
{6, UPB_SIZE(16, 56), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||
};
|
||||
|
||||
const upb_MiniTable Worm_msg_init = {
|
||||
NULL,
|
||||
&Worm__fields[0],
|
||||
UPB_SIZE(48, 64), 6, kUpb_ExtMode_NonExtendable, 6, UPB_FASTTABLE_MASK(255), 0,
|
||||
#ifdef UPB_TRACING_ENABLED
|
||||
"Worm",
|
||||
#endif
|
||||
};
|
||||
|
||||
const upb_MiniTable* Worm_msg_init_ptr = &Worm_msg_init;
|
||||
static const upb_MiniTableSubInternal Operation__submsgs[2] = {
|
||||
{.UPB_PRIVATE(submsg) = &Hacker_msg_init_ptr},
|
||||
{.UPB_PRIVATE(submsg) = &Worm_msg_init_ptr},
|
||||
};
|
||||
|
||||
static const upb_MiniTableField Operation__fields[9] = {
|
||||
{1, UPB_SIZE(28, 16), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||
{2, UPB_SIZE(36, 32), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||
{3, UPB_SIZE(56, 64), 0, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)},
|
||||
{4, 9, 0, kUpb_NoSub, 8, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_1Byte << kUpb_FieldRep_Shift)},
|
||||
{5, UPB_SIZE(44, 48), 0, kUpb_NoSub, 12, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||
{6, UPB_SIZE(12, 72), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||
{7, UPB_SIZE(16, 80), 64, 1, 11, (int)kUpb_FieldMode_Scalar | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||
{8, UPB_SIZE(20, 88), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||
{9, UPB_SIZE(24, 12), 0, kUpb_NoSub, 5, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_4Byte << kUpb_FieldRep_Shift)},
|
||||
};
|
||||
|
||||
const upb_MiniTable Operation_msg_init = {
|
||||
&Operation__submsgs[0],
|
||||
&Operation__fields[0],
|
||||
UPB_SIZE(64, 96), 9, kUpb_ExtMode_NonExtendable, 9, UPB_FASTTABLE_MASK(255), 0,
|
||||
#ifdef UPB_TRACING_ENABLED
|
||||
"Operation",
|
||||
#endif
|
||||
};
|
||||
|
||||
const upb_MiniTable* Operation_msg_init_ptr = &Operation_msg_init;
|
||||
static const upb_MiniTableSubInternal Campaign__submsgs[1] = {
|
||||
{.UPB_PRIVATE(submsg) = &Operation_msg_init_ptr},
|
||||
};
|
||||
|
||||
static const upb_MiniTableField Campaign__fields[3] = {
|
||||
{1, UPB_SIZE(12, 8), 0, kUpb_NoSub, 9, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_StringView << kUpb_FieldRep_Shift)},
|
||||
{2, UPB_SIZE(8, 24), 0, 0, 11, (int)kUpb_FieldMode_Array | ((int)UPB_SIZE(kUpb_FieldRep_4Byte, kUpb_FieldRep_8Byte) << kUpb_FieldRep_Shift)},
|
||||
{3, UPB_SIZE(24, 32), 0, kUpb_NoSub, 3, (int)kUpb_FieldMode_Scalar | ((int)kUpb_FieldRep_8Byte << kUpb_FieldRep_Shift)},
|
||||
};
|
||||
|
||||
const upb_MiniTable Campaign_msg_init = {
|
||||
&Campaign__submsgs[0],
|
||||
&Campaign__fields[0],
|
||||
UPB_SIZE(32, 40), 3, kUpb_ExtMode_NonExtendable, 3, UPB_FASTTABLE_MASK(255), 0,
|
||||
#ifdef UPB_TRACING_ENABLED
|
||||
"Campaign",
|
||||
#endif
|
||||
};
|
||||
|
||||
const upb_MiniTable* Campaign_msg_init_ptr = &Campaign_msg_init;
|
||||
static const upb_MiniTable *messages_layout[6] = {
|
||||
&Tool_msg_init,
|
||||
&Connection_msg_init,
|
||||
&Hacker_msg_init,
|
||||
&Worm_msg_init,
|
||||
&Operation_msg_init,
|
||||
&Campaign_msg_init,
|
||||
};
|
||||
|
||||
const upb_MiniTableFile hackers_proto_upb_file_layout = {
|
||||
messages_layout,
|
||||
NULL,
|
||||
NULL,
|
||||
6,
|
||||
0,
|
||||
0,
|
||||
};
|
||||
|
||||
#include "upb/port/undef.inc"
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/* This file was generated by upb_generator from the input file:
|
||||
*
|
||||
* hackers.proto
|
||||
*
|
||||
* Do not edit -- your changes will be discarded when the file is
|
||||
* regenerated.
|
||||
* NO CHECKED-IN PROTOBUF GENCODE */
|
||||
|
||||
#ifndef HACKERS_PROTO_UPB_H__UPB_MINITABLE_H_
|
||||
#define HACKERS_PROTO_UPB_H__UPB_MINITABLE_H_
|
||||
|
||||
#include "upb/generated_code_support.h"
|
||||
|
||||
// Must be last.
|
||||
#include "upb/port/def.inc"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern const upb_MiniTable Tool_msg_init;
|
||||
extern const upb_MiniTable* Tool_msg_init_ptr;
|
||||
extern const upb_MiniTable Connection_msg_init;
|
||||
extern const upb_MiniTable* Connection_msg_init_ptr;
|
||||
extern const upb_MiniTable Hacker_msg_init;
|
||||
extern const upb_MiniTable* Hacker_msg_init_ptr;
|
||||
extern const upb_MiniTable Worm_msg_init;
|
||||
extern const upb_MiniTable* Worm_msg_init_ptr;
|
||||
extern const upb_MiniTable Operation_msg_init;
|
||||
extern const upb_MiniTable* Operation_msg_init_ptr;
|
||||
extern const upb_MiniTable Campaign_msg_init;
|
||||
extern const upb_MiniTable* Campaign_msg_init_ptr;
|
||||
|
||||
extern const upb_MiniTableFile hackers_proto_upb_file_layout;
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#include "upb/port/undef.inc"
|
||||
|
||||
#endif /* HACKERS_PROTO_UPB_H__UPB_MINITABLE_H_ */
|
||||
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* hackers_bench.c — C/upb benchmark mirroring benches/hackers_bench.rs
|
||||
*
|
||||
* Proto: proto/hackers.proto
|
||||
* Generated files: hackers.upb.h / .c, hackers.upb_minitable.h / .c
|
||||
*
|
||||
* Build: make
|
||||
* Run: ./hackers_bench
|
||||
*
|
||||
* Data files are read from ../data/bench/<name>.pb — the same files
|
||||
* produced by `cargo run --release --bin gen_bench_data -- --preset <name>`.
|
||||
*
|
||||
* The four benchmark groups match the Rust/criterion groups exactly:
|
||||
*
|
||||
* shallow_parse — Campaign_parse() + Arena_Free() per iteration.
|
||||
* upb fully decodes the message; roto merely scans
|
||||
* for field offsets. This is the most important
|
||||
* comparison: total cost to "be ready to read".
|
||||
*
|
||||
* deep_parse — parse + walk Campaign → Operations → every Hacker,
|
||||
* touching each Hacker's handle field.
|
||||
*
|
||||
* field_access — message pre-parsed once outside the loop; each
|
||||
* micro-benchmark times a single field read.
|
||||
* upb: direct struct lookup. roto: decode at offset.
|
||||
*
|
||||
* iterate — count_operations: parse + count top-level repeated.
|
||||
* count_all_crew: parse + count nested repeated.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "hackers.upb.h"
|
||||
#include "hackers.upb_minitable.h"
|
||||
|
||||
/* ==========================================================================
|
||||
* Timing
|
||||
* ========================================================================== */
|
||||
|
||||
static uint64_t now_ns(void) {
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
* Black-box sink — prevents the compiler from optimising away benchmark work.
|
||||
* We write the result of every meaningful computation here.
|
||||
* ========================================================================== */
|
||||
|
||||
static volatile uintptr_t g_sink;
|
||||
|
||||
/* ==========================================================================
|
||||
* File I/O
|
||||
* ========================================================================== */
|
||||
|
||||
typedef struct {
|
||||
uint8_t *data;
|
||||
size_t len;
|
||||
char path[256];
|
||||
} BenchData;
|
||||
|
||||
static bool load_bench_data(BenchData *out, const char *name) {
|
||||
snprintf(out->path, sizeof(out->path), "../data/bench/%s.pb", name);
|
||||
FILE *f = fopen(out->path, "rb");
|
||||
if (!f) {
|
||||
printf("[skip] %s not found — "
|
||||
"run `cargo run --release --bin gen_bench_data -- --preset %s` first\n",
|
||||
out->path, name);
|
||||
out->data = NULL;
|
||||
out->len = 0;
|
||||
return false;
|
||||
}
|
||||
fseek(f, 0, SEEK_END);
|
||||
out->len = (size_t)ftell(f);
|
||||
rewind(f);
|
||||
out->data = malloc(out->len);
|
||||
if (!out->data) { fclose(f); return false; }
|
||||
fread(out->data, 1, out->len, f);
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void free_bench_data(BenchData *d) {
|
||||
free(d->data);
|
||||
d->data = NULL;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
* Benchmark runner
|
||||
*
|
||||
* Finds a batch size such that one batch takes ≥1 ms, then runs batches
|
||||
* until at least BENCH_MIN_SECS of wall time has elapsed. Reports the
|
||||
* mean ns/iter and, if bytes > 0, the MB/s throughput.
|
||||
* ========================================================================== */
|
||||
|
||||
#define BENCH_MIN_SECS 0.5
|
||||
|
||||
typedef void (*bench_fn)(void *state);
|
||||
|
||||
static void run_bench(bench_fn fn, void *state, size_t bytes, const char *label) {
|
||||
/* warmup */
|
||||
for (int i = 0; i < 5; i++) fn(state);
|
||||
|
||||
/* calibrate: find batch size so one batch ≥ 1 ms */
|
||||
uint64_t batch = 1;
|
||||
while (batch < 10000000ULL) {
|
||||
uint64_t t0 = now_ns();
|
||||
for (uint64_t i = 0; i < batch; i++) fn(state);
|
||||
if (now_ns() - t0 >= 1000000ULL) break; /* 1 ms */
|
||||
batch *= 4;
|
||||
}
|
||||
|
||||
/* measure */
|
||||
uint64_t target_ns = (uint64_t)(BENCH_MIN_SECS * 1e9);
|
||||
uint64_t total_ns = 0;
|
||||
uint64_t total_its = 0;
|
||||
while (total_ns < target_ns) {
|
||||
uint64_t t0 = now_ns();
|
||||
for (uint64_t i = 0; i < batch; i++) fn(state);
|
||||
total_ns += now_ns() - t0;
|
||||
total_its += batch;
|
||||
}
|
||||
|
||||
double ns_per_iter = (double)total_ns / (double)total_its;
|
||||
if (bytes > 0) {
|
||||
double mb_per_sec = (double)bytes / ns_per_iter * 1000.0;
|
||||
printf(" %-46s %9.2f ns/iter %8.2f MB/s\n",
|
||||
label, ns_per_iter, mb_per_sec);
|
||||
} else {
|
||||
printf(" %-46s %9.2f ns/iter\n", label, ns_per_iter);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
* shallow_parse — Campaign_parse() + upb_Arena_Free() per iteration
|
||||
*
|
||||
* Measures the full cost of becoming "ready to access any field", matching
|
||||
* the Rust `Campaign::new()` benchmark. upb fully decodes; roto only scans.
|
||||
* ========================================================================== */
|
||||
|
||||
static void fn_shallow_parse(void *state) {
|
||||
BenchData *d = state;
|
||||
upb_Arena *arena = upb_Arena_New();
|
||||
Campaign *c = Campaign_parse((const char *)d->data, d->len, arena);
|
||||
g_sink = (uintptr_t)c;
|
||||
upb_Arena_Free(arena);
|
||||
}
|
||||
|
||||
static void bench_shallow_parse(void) {
|
||||
const char *sizes[] = {"tiny", "small", "medium", "large", NULL};
|
||||
printf("\n=== shallow_parse ===\n");
|
||||
for (int i = 0; sizes[i]; i++) {
|
||||
BenchData d;
|
||||
if (!load_bench_data(&d, sizes[i])) continue;
|
||||
char label[80];
|
||||
snprintf(label, sizeof(label), "Campaign_parse/%s [%zu B]", sizes[i], d.len);
|
||||
run_bench(fn_shallow_parse, &d, d.len, label);
|
||||
free_bench_data(&d);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
* deep_parse — parse + walk Campaign → Operations → Hackers
|
||||
*
|
||||
* After Campaign_parse(), upb has already decoded everything. The "deep"
|
||||
* walk is pointer-chasing through the decoded tree. In roto each level
|
||||
* calls ::new(), paying another linear scan over that sub-message's bytes.
|
||||
* ========================================================================== */
|
||||
|
||||
static void fn_deep_parse(void *state) {
|
||||
BenchData *d = state;
|
||||
upb_Arena *arena = upb_Arena_New();
|
||||
Campaign *c = Campaign_parse((const char *)d->data, d->len, arena);
|
||||
|
||||
size_t n_ops;
|
||||
const Operation * const *ops = Campaign_operations(c, &n_ops);
|
||||
size_t hacker_count = 0;
|
||||
for (size_t i = 0; i < n_ops; i++) {
|
||||
size_t n_crew;
|
||||
const Hacker * const *crew = Operation_crew(ops[i], &n_crew);
|
||||
for (size_t j = 0; j < n_crew; j++) {
|
||||
upb_StringView handle = Hacker_handle(crew[j]);
|
||||
g_sink = (uintptr_t)handle.data;
|
||||
hacker_count++;
|
||||
}
|
||||
}
|
||||
g_sink = hacker_count;
|
||||
upb_Arena_Free(arena);
|
||||
}
|
||||
|
||||
static void bench_deep_parse(void) {
|
||||
const char *sizes[] = {"tiny", "small", "medium", NULL};
|
||||
printf("\n=== deep_parse ===\n");
|
||||
for (int i = 0; sizes[i]; i++) {
|
||||
BenchData d;
|
||||
if (!load_bench_data(&d, sizes[i])) continue;
|
||||
char label[80];
|
||||
snprintf(label, sizeof(label), "Campaign+Ops+Hackers/%s [%zu B]", sizes[i], d.len);
|
||||
run_bench(fn_deep_parse, &d, d.len, label);
|
||||
free_bench_data(&d);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
* field_access — individual field reads on a pre-parsed message
|
||||
*
|
||||
* Parse once outside the loop; each micro-benchmark measures the accessor
|
||||
* call itself. upb: a struct-field read with a MiniTable lookup.
|
||||
* roto: decode the value at a pre-recorded byte offset.
|
||||
* ========================================================================== */
|
||||
|
||||
typedef struct {
|
||||
upb_Arena *arena;
|
||||
Campaign *campaign;
|
||||
Operation *op;
|
||||
Hacker *hacker;
|
||||
Worm *worm;
|
||||
} FieldState;
|
||||
|
||||
static void fn_field_campaign_name(void *s) {
|
||||
upb_StringView v = Campaign_name(((FieldState *)s)->campaign);
|
||||
g_sink = (uintptr_t)v.data;
|
||||
}
|
||||
static void fn_field_total_bytes_stolen(void *s) {
|
||||
g_sink = (uintptr_t)(uint64_t)Campaign_total_bytes_stolen(((FieldState *)s)->campaign);
|
||||
}
|
||||
static void fn_field_op_codename(void *s) {
|
||||
upb_StringView v = Operation_codename(((FieldState *)s)->op);
|
||||
g_sink = (uintptr_t)v.data;
|
||||
}
|
||||
static void fn_field_op_timestamp(void *s) {
|
||||
g_sink = (uintptr_t)(uint64_t)Operation_timestamp(((FieldState *)s)->op);
|
||||
}
|
||||
static void fn_field_op_successful(void *s) {
|
||||
g_sink = (uintptr_t)Operation_successful(((FieldState *)s)->op);
|
||||
}
|
||||
static void fn_field_hacker_handle(void *s) {
|
||||
upb_StringView v = Hacker_handle(((FieldState *)s)->hacker);
|
||||
g_sink = (uintptr_t)v.data;
|
||||
}
|
||||
static void fn_field_hacker_skill_level(void *s) {
|
||||
/* store float bits to avoid FPU → int conversion costs */
|
||||
float f = Hacker_skill_level(((FieldState *)s)->hacker);
|
||||
uint32_t bits; memcpy(&bits, &f, 4);
|
||||
g_sink = bits;
|
||||
}
|
||||
static void fn_field_hacker_is_elite(void *s) {
|
||||
g_sink = (uintptr_t)Hacker_is_elite(((FieldState *)s)->hacker);
|
||||
}
|
||||
static void fn_field_worm_polymorphic(void *s) {
|
||||
g_sink = (uintptr_t)Worm_polymorphic(((FieldState *)s)->worm);
|
||||
}
|
||||
static void fn_field_worm_payload(void *s) {
|
||||
upb_StringView v = Worm_payload(((FieldState *)s)->worm);
|
||||
g_sink = (uintptr_t)v.data;
|
||||
}
|
||||
|
||||
static void bench_field_access(void) {
|
||||
BenchData d;
|
||||
if (!load_bench_data(&d, "small")) return;
|
||||
|
||||
upb_Arena *arena = upb_Arena_New();
|
||||
Campaign *campaign = Campaign_parse((const char *)d.data, d.len, arena);
|
||||
if (!campaign) { fprintf(stderr, "parse failed\n"); return; }
|
||||
|
||||
size_t n_ops;
|
||||
const Operation * const *ops = Campaign_operations(campaign, &n_ops);
|
||||
if (n_ops == 0) { fprintf(stderr, "no operations\n"); return; }
|
||||
Operation *op = (Operation *)ops[0]; /* cast away const for state */
|
||||
|
||||
size_t n_crew;
|
||||
const Hacker * const *crew = Operation_crew(op, &n_crew);
|
||||
if (n_crew == 0) { fprintf(stderr, "no crew\n"); return; }
|
||||
Hacker *hacker = (Hacker *)crew[0];
|
||||
|
||||
const Worm *worm = Operation_worm(op);
|
||||
if (!worm) { fprintf(stderr, "no worm\n"); return; }
|
||||
|
||||
FieldState state = {
|
||||
.arena = arena,
|
||||
.campaign = campaign,
|
||||
.op = op,
|
||||
.hacker = hacker,
|
||||
.worm = (Worm *)worm,
|
||||
};
|
||||
|
||||
printf("\n=== field_access ===\n");
|
||||
run_bench(fn_field_campaign_name, &state, 0, "campaign::name");
|
||||
run_bench(fn_field_total_bytes_stolen, &state, 0, "campaign::total_bytes_stolen");
|
||||
run_bench(fn_field_op_codename, &state, 0, "operation::codename");
|
||||
run_bench(fn_field_op_timestamp, &state, 0, "operation::timestamp");
|
||||
run_bench(fn_field_op_successful, &state, 0, "operation::successful");
|
||||
run_bench(fn_field_hacker_handle, &state, 0, "hacker::handle");
|
||||
run_bench(fn_field_hacker_skill_level, &state, 0, "hacker::skill_level (f32)");
|
||||
run_bench(fn_field_hacker_is_elite, &state, 0, "hacker::is_elite (bool)");
|
||||
run_bench(fn_field_worm_polymorphic, &state, 0, "worm::polymorphic (bool)");
|
||||
run_bench(fn_field_worm_payload, &state, 0, "worm::payload (bytes)");
|
||||
|
||||
upb_Arena_Free(arena);
|
||||
free_bench_data(&d);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
* iterate — count repeated fields at different depths
|
||||
*
|
||||
* count_operations: after parsing, Campaign_operations() returns pointer+count
|
||||
* in O(1) — upb already decoded the array.
|
||||
* roto's Campaign::new() scan IS the counting work.
|
||||
*
|
||||
* count_all_crew: parse + walk ops + sum crew sizes.
|
||||
* ========================================================================== */
|
||||
|
||||
static void fn_count_operations(void *state) {
|
||||
BenchData *d = state;
|
||||
upb_Arena *arena = upb_Arena_New();
|
||||
Campaign *c = Campaign_parse((const char *)d->data, d->len, arena);
|
||||
size_t n;
|
||||
Campaign_operations(c, &n);
|
||||
g_sink = n;
|
||||
upb_Arena_Free(arena);
|
||||
}
|
||||
|
||||
static void fn_count_all_crew(void *state) {
|
||||
BenchData *d = state;
|
||||
upb_Arena *arena = upb_Arena_New();
|
||||
Campaign *c = Campaign_parse((const char *)d->data, d->len, arena);
|
||||
size_t n_ops;
|
||||
const Operation * const *ops = Campaign_operations(c, &n_ops);
|
||||
size_t total = 0;
|
||||
for (size_t i = 0; i < n_ops; i++) {
|
||||
size_t n_crew;
|
||||
Operation_crew(ops[i], &n_crew);
|
||||
total += n_crew;
|
||||
}
|
||||
g_sink = total;
|
||||
upb_Arena_Free(arena);
|
||||
}
|
||||
|
||||
static void bench_iterate(void) {
|
||||
const char *sizes[] = {"tiny", "small", "medium", NULL};
|
||||
printf("\n=== iterate ===\n");
|
||||
for (int i = 0; sizes[i]; i++) {
|
||||
BenchData d;
|
||||
if (!load_bench_data(&d, sizes[i])) continue;
|
||||
|
||||
char label[80];
|
||||
|
||||
snprintf(label, sizeof(label), "count_operations/%s [%zu B]", sizes[i], d.len);
|
||||
run_bench(fn_count_operations, &d, d.len, label);
|
||||
|
||||
snprintf(label, sizeof(label), "count_all_crew/%s [%zu B]", sizes[i], d.len);
|
||||
run_bench(fn_count_all_crew, &d, d.len, label);
|
||||
|
||||
free_bench_data(&d);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
* main
|
||||
* ========================================================================== */
|
||||
|
||||
int main(void) {
|
||||
printf("hackers_bench (upb / protobuf %s)\n", "33.1");
|
||||
printf("Data files: ../data/bench/<name>.pb\n");
|
||||
printf("Run `cargo run --release --bin gen_bench_data -- --preset <name>` to generate.\n");
|
||||
|
||||
bench_shallow_parse();
|
||||
bench_deep_parse();
|
||||
bench_field_access();
|
||||
bench_iterate();
|
||||
|
||||
printf("\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "hackers.upb.h"
|
||||
#include "hackers.upb_minitable.h"
|
||||
|
||||
int main(void) {
|
||||
upb_Arena *arena = upb_Arena_New();
|
||||
Campaign *c = Campaign_new(arena);
|
||||
(void)c;
|
||||
printf("name: %.*s\n", (int)Campaign_name(c).size, Campaign_name(c).data);
|
||||
upb_Arena_Free(arena);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user