Skip to content

Commit a29ecf0

Browse files
committed
better benchmark
1 parent 231b771 commit a29ecf0

3 files changed

Lines changed: 147 additions & 70 deletions

File tree

CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ set(STREAMVBYTE_LIB_SOVERSION "2" CACHE STRING "streamvbyte library soversion")
77
set(CMAKE_C_STANDARD 99)
88
set(CMAKE_C_STANDARD_REQUIRED ON)
99

10+
include(cmake/CPM.cmake)
11+
1012
include(CheckCCompilerFlag)
1113
include(GNUInstallDirs)
1214

@@ -171,10 +173,19 @@ endif()
171173
option(STREAMVBYTE_ENABLE_TESTS "Enable unit tests for streamvbyte" OFF)
172174
if(STREAMVBYTE_ENABLE_TESTS)
173175
if(NOT MSVC)
176+
CPMAddPackage(
177+
NAME sea_counters
178+
GITHUB_REPOSITORY lemire/sea_counters
179+
GIT_TAG main
180+
OPTIONS "SEA_COUNTERS_INSTALL OFF"
181+
)
182+
174183
# perf
175184
add_executable(perf ${PROJECT_SOURCE_DIR}/tests/perf.c)
185+
set_target_properties(perf PROPERTIES C_STANDARD 11 C_STANDARD_REQUIRED YES)
176186
target_link_libraries(perf streamvbyte)
177187
target_link_libraries(perf m)
188+
target_link_libraries(perf sea_counters::sea_counters)
178189
endif()
179190

180191
# writeseq

tests/perf.c

Lines changed: 135 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,156 @@
11
#include "streamvbyte.h"
2+
#include "counters/bench.h"
3+
24
#include <assert.h>
3-
#include <math.h>
5+
#include <stdint.h>
46
#include <stdio.h>
57
#include <stdlib.h>
6-
#include <sys/resource.h>
7-
#include <time.h>
8-
98
#include <string.h>
109

1110
#ifdef __clang__
1211
#pragma clang diagnostic ignored "-Wdeclaration-after-statement"
13-
#pragma clang diagnostic ignored "-Wunused-variable"
1412
#endif
1513

16-
static void punt(long long n, char *s) {
17-
int i = 127;
18-
int sign = 0;
19-
if (n < 0) {
20-
sign = 1;
21-
n = -n;
14+
#define N 500000U
15+
16+
typedef struct {
17+
const uint32_t *datain;
18+
uint8_t *compressedbuffer;
19+
uint32_t *recovdata;
20+
size_t compsize;
21+
size_t n;
22+
} svb_ctx;
23+
24+
static void bench_encode(void *ud) {
25+
svb_ctx *ctx = (svb_ctx *)ud;
26+
ctx->compsize = streamvbyte_encode(ctx->datain, (uint32_t)ctx->n, ctx->compressedbuffer);
27+
}
28+
29+
static void bench_decode(void *ud) {
30+
svb_ctx *ctx = (svb_ctx *)ud;
31+
size_t r = streamvbyte_decode(ctx->compressedbuffer, ctx->recovdata, (uint32_t)ctx->n);
32+
(void)r;
33+
}
34+
35+
static void print_aggregate(const char *label, size_t bytes_processed,
36+
const counters_event_aggregate *agg, int have_counters) {
37+
double ns = counters_event_aggregate_elapsed_ns(agg);
38+
double gbs = ns > 0.0 ? (double)bytes_processed / ns : 0.0;
39+
double uints_per_sec = ns > 0.0 ? (double)(bytes_processed / sizeof(uint32_t)) * 1e9 / ns : 0.0;
40+
41+
printf(" %-7s %8.0f ns/op %6.2f GB/s %12.0f uints/s iters=%d",
42+
label, ns, gbs, uints_per_sec,
43+
counters_event_aggregate_iteration_count(agg));
44+
45+
if (have_counters) {
46+
double instr = counters_event_aggregate_instructions(agg);
47+
double cycles = counters_event_aggregate_cycles(agg);
48+
double branches = counters_event_aggregate_branches(agg);
49+
double branch_misses = counters_event_aggregate_branch_misses(agg);
50+
double cache_misses = counters_event_aggregate_cache_misses(agg);
51+
double ipc = cycles > 0.0 ? instr / cycles : 0.0;
52+
size_t nints = bytes_processed / sizeof(uint32_t);
53+
double ins_per_int = nints > 0 ? instr / (double)nints : 0.0;
54+
double cyc_per_int = nints > 0 ? cycles / (double)nints : 0.0;
55+
printf(" ins=%.0f cyc=%.0f IPC=%.2f ins/int=%.2f cyc/int=%.2f"
56+
" br=%.0f brmiss=%.0f cmiss=%.0f",
57+
instr, cycles, ipc, ins_per_int, cyc_per_int,
58+
branches, branch_misses, cache_misses);
2259
}
23-
s[i--] = '\0'; // null terminated
24-
int digits = 0;
25-
do {
26-
s[i--] = n % 10 + '0';
27-
digits++;
28-
n /= 10;
29-
if (((digits % 3) == 0) && (n != 0))
30-
s[i--] = ',';
31-
32-
} while (n);
33-
if (sign)
34-
s[i--] = '-';
35-
memmove(s, s + i + 1, (size_t)(127 - i));
60+
printf("\n");
3661
}
3762

38-
int main(void) {
39-
#define N 500000U // Avoids VLA
40-
const uint32_t NTrials = 100U;
41-
struct rusage before;
42-
struct rusage after;
43-
double t;
44-
char s[128];
45-
char s1[128];
46-
char s2[128];
47-
48-
uint32_t datain[N];
49-
uint8_t compressedbuffer[N * 5];
50-
uint32_t recovdata[N];
51-
52-
for (uint32_t k = 0; k < N; ++k)
53-
datain[k] = (uint32_t)rand() >> ((uint32_t)31 & (uint32_t)rand());
54-
55-
size_t compsize = 0;
56-
57-
getrusage(RUSAGE_SELF, &before);
58-
59-
for (uint32_t i = 0; i < NTrials; i++)
60-
compsize = streamvbyte_encode(datain, N, compressedbuffer);
61-
62-
getrusage(RUSAGE_SELF, &after);
63-
64-
t = (after.ru_utime.tv_usec - before.ru_utime.tv_usec) / 1000000.0;
65-
punt((long long)(round((double)(N * NTrials) / t)), s);
66-
printf("encoding time = %f s, %s uints/sec\n", t, s);
67-
68-
size_t compsize2;
69-
getrusage(RUSAGE_SELF, &before);
70-
for (uint32_t i = 0; i < NTrials; i++)
71-
compsize2 = streamvbyte_decode(compressedbuffer, recovdata, N);
72-
getrusage(RUSAGE_SELF, &after);
73-
t = (after.ru_utime.tv_usec - before.ru_utime.tv_usec) / 1000000.0;
74-
punt((long long)(round((double)(N * NTrials) / t)), s);
75-
printf("decoding time = %f s, %s uints/sec\n", t, s);
63+
static void fill_log_uniform(uint32_t *data, size_t n) {
64+
for (size_t k = 0; k < n; ++k)
65+
data[k] = (uint32_t)rand() >> ((uint32_t)31 & (uint32_t)rand());
66+
}
67+
68+
static void fill_full_range(uint32_t *data, size_t n) {
69+
for (size_t k = 0; k < n; ++k) {
70+
/* rand() yields at most 31 bits on most platforms; combine two calls. */
71+
uint32_t hi = (uint32_t)rand();
72+
uint32_t lo = (uint32_t)rand();
73+
data[k] = (hi << 16) ^ lo;
74+
}
75+
}
76+
77+
static void fill_small(uint32_t *data, size_t n) {
78+
for (size_t k = 0; k < n; ++k)
79+
data[k] = (uint32_t)rand() & 0xFFu;
80+
}
81+
82+
static void run_benchmark(const char *title,
83+
void (*fill)(uint32_t *, size_t),
84+
uint32_t *datain, uint8_t *compressedbuffer,
85+
uint32_t *recovdata, int have_counters) {
86+
fill(datain, N);
87+
88+
svb_ctx ctx;
89+
ctx.datain = datain;
90+
ctx.compressedbuffer = compressedbuffer;
91+
ctx.recovdata = recovdata;
92+
ctx.compsize = 0;
93+
ctx.n = N;
94+
95+
counters_bench_parameter params = counters_bench_parameter_default();
96+
97+
printf("== %s ==\n", title);
98+
99+
counters_event_aggregate enc = counters_bench(bench_encode, &ctx, &params);
100+
print_aggregate("encode", N * sizeof(uint32_t), &enc, have_counters);
101+
102+
size_t compsize = ctx.compsize;
103+
104+
counters_event_aggregate dec = counters_bench(bench_decode, &ctx, &params);
105+
print_aggregate("decode", N * sizeof(uint32_t), &dec, have_counters);
106+
107+
size_t compsize2 = streamvbyte_decode(compressedbuffer, recovdata, N);
76108
if (compsize != compsize2)
77-
printf("compsize=%zu compsize2 = %zu\n", compsize, compsize2);
109+
printf(" compsize mismatch: %zu vs %zu\n", compsize, compsize2);
78110

79111
uint32_t k;
80112
for (k = 0; k < N && datain[k] == recovdata[k]; k++)
81113
;
82-
83114
if (k < N)
84-
printf("mismatch at %d before=%d after=%d\n", k, datain[k], recovdata[k]);
85-
115+
printf(" mismatch at %u before=%u after=%u\n", k, datain[k], recovdata[k]);
86116
assert(k >= N);
87-
punt(N * sizeof(uint32_t), s1);
88-
punt((long long)compsize, s2);
89-
printf("Compressed %s bytes down to %s bytes.\n", s1, s2);
90-
return 0;
117+
118+
double ratio = (double)compsize / (double)((size_t)N * sizeof(uint32_t));
119+
printf(" Compressed %zu bytes down to %zu bytes (ratio = %.3f, %.2f bits/int).\n\n",
120+
(size_t)N * sizeof(uint32_t), compsize, ratio,
121+
8.0 * (double)compsize / (double)N);
122+
}
123+
124+
int main(void) {
125+
uint32_t *datain = (uint32_t *)malloc(N * sizeof(uint32_t));
126+
uint8_t *compressedbuffer = (uint8_t *)malloc((size_t)N * 5);
127+
uint32_t *recovdata = (uint32_t *)malloc(N * sizeof(uint32_t));
128+
if (!datain || !compressedbuffer || !recovdata) {
129+
fprintf(stderr, "allocation failure\n");
130+
return EXIT_FAILURE;
131+
}
132+
133+
int have_counters = counters_has_performance_counters() ? 1 : 0;
134+
if (!have_counters) {
135+
fprintf(stderr,
136+
"Warning: hardware performance counters are unavailable.\n"
137+
"Timing will be reported, but instruction/cycle/branch/cache-miss\n"
138+
"counts will be omitted. To enable detailed counters:\n"
139+
" Linux: relax perf_event_paranoid, e.g.\n"
140+
" sudo sysctl -w kernel.perf_event_paranoid=0\n"
141+
" (or run this binary with sudo)\n"
142+
" macOS (Apple Silicon): run this binary with sudo to access kpc.\n\n");
143+
}
144+
145+
run_benchmark("log-uniform widths (mixed 1-4 byte)", fill_log_uniform,
146+
datain, compressedbuffer, recovdata, have_counters);
147+
run_benchmark("full-range uint32_t (mostly 4-byte)", fill_full_range,
148+
datain, compressedbuffer, recovdata, have_counters);
149+
run_benchmark("small values [0,256) (1-byte)", fill_small,
150+
datain, compressedbuffer, recovdata, have_counters);
151+
152+
free(datain);
153+
free(compressedbuffer);
154+
free(recovdata);
155+
return EXIT_SUCCESS;
91156
}

tests/writeseq.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ int main(void) {
4545
// here the result is stored in compressedbuffer using compsize bytes
4646
size_t compsize2 = streamvbyte_decode(compressedbuffer, recovdata,
4747
N); // decoding (fast)
48+
(void)compsize2;
4849
assert(compsize == compsize2);
4950
free(datain);
5051
free(compressedbuffer);

0 commit comments

Comments
 (0)