Merge v0.4.0 workload changes

This commit is contained in:
2026-07-20 19:10:17 -05:00
8 changed files with 539 additions and 74 deletions
+10 -10
View File
@@ -2,17 +2,17 @@
# Use make for the current computer, or a named target for another one.
CC ?= cc
CFLAGS ?= -O2 -Wall -Wextra
CFLAGS ?= -O3 -flto -funroll-loops -Wall -Wextra
LDLIBS ?= -lm
# Needed for the worker threads.
PTHREAD := -pthread
DIST := dist
DRIVER := src/main.c src/app/benchmark.c src/app/hw_detect.c
DRIVER := src/main.c src/app/benchmark.c src/app/hw_detect.c src/kernels/fossbench-portable.c
DRIVER_DEPS := src/app/benchmark.h src/app/hw_detect.h src/app/upload.c
ASM_ARM64 := src/kernels/fossbench-arm64.S
ASM_AMD64 := src/kernels/fossbench-amd64.S
ASM_I386 := src/kernels/fossbench-i386.S
SRC_I386 := src/kernels/fossbench-i386.c
ASM_PPC32 := src/kernels/fossbench-ppc32be.S
SRC_PPC32 := src/kernels/fossbench-ppc32be-chacha.c
ASM_PPC64 := src/kernels/fossbench-ppc64be.S
@@ -29,7 +29,7 @@ else ifneq (,$(filter x86_64 amd64,$(HOST_ARCH)))
HOST_KERNEL := $(ASM_AMD64)
else ifneq (,$(filter i386 i486 i586 i686 x86,$(HOST_ARCH)))
HOST_ARCHNAME := i386
HOST_KERNEL := $(ASM_I386)
HOST_KERNEL := $(SRC_I386)
else ifneq (,$(filter ppc64le powerpc64le,$(HOST_ARCH)))
HOST_ARCHNAME := ppc64le
HOST_KERNEL := $(ASM_PPC64LE)
@@ -47,8 +47,8 @@ else
$(error unsupported host architecture '$(HOST_ARCH)')
endif
ifeq ($(HOST_ARCHNAME),i386)
# Keep the old i386 target simple and non-PIE.
CFLAGS += -march=pentium4 -fno-pie
# Keep the i386 target compatible with the original 80386 ISA.
CFLAGS += -march=i386 -fno-pie
LDFLAGS += -no-pie
endif
ifeq ($(HOST_ARCHNAME),ppc64be)
@@ -157,8 +157,8 @@ $(DIST)/fossbench-linux-amd64: $(DRIVER) $(DRIVER_DEPS) $(ASM_AMD64) | $(DIST)
$(CC_AMD64) $(CFLAGS) $(PTHREAD) $(LDFLAGS) -o $@ $(DRIVER) $(ASM_AMD64) $(LDLIBS)
@echo "built $@"
$(DIST)/fossbench-linux-i386: $(DRIVER) $(DRIVER_DEPS) $(ASM_I386) | $(DIST)
$(CC_I386) -m32 -march=pentium4 -fno-pie -no-pie $(CFLAGS) $(PTHREAD) $(LDFLAGS) -o $@ $(DRIVER) $(ASM_I386) $(LDLIBS)
$(DIST)/fossbench-linux-i386: $(DRIVER) $(DRIVER_DEPS) $(SRC_I386) | $(DIST)
$(CC_I386) -m32 -march=i386 -fno-pie -no-pie $(CFLAGS) $(PTHREAD) $(LDFLAGS) -o $@ $(DRIVER) $(SRC_I386) $(LDLIBS)
@echo "built $@"
$(DIST)/fossbench-linux-ppc32be: $(DRIVER) $(DRIVER_DEPS) $(ASM_PPC32) $(SRC_PPC32) | $(DIST)
@@ -190,8 +190,8 @@ $(DIST)/fossbench-windows-amd64.exe: $(DRIVER) $(DRIVER_DEPS) $(ASM_AMD64) | $(D
$(CC_WINDOWS_AMD64) $(CFLAGS) $(PTHREAD) -static -o $@ $(DRIVER) $(ASM_AMD64) -lm -lwinhttp -ladvapi32
@echo "built $@"
$(DIST)/fossbench-windows-i386.exe: $(DRIVER) $(DRIVER_DEPS) $(ASM_I386) | $(DIST)
$(CC_WINDOWS_I386) -march=pentium4 $(WINDOWS_I386_XP_CFLAGS) $(CFLAGS) -static $(WINDOWS_I386_XP_LDFLAGS) -o $@ $(DRIVER) $(ASM_I386) -lm -lwinhttp -ladvapi32
$(DIST)/fossbench-windows-i386.exe: $(DRIVER) $(DRIVER_DEPS) $(SRC_I386) | $(DIST)
$(CC_WINDOWS_I386) -march=i386 $(WINDOWS_I386_XP_CFLAGS) $(CFLAGS) -static $(WINDOWS_I386_XP_LDFLAGS) -o $@ $(DRIVER) $(SRC_I386) -lm -lwinhttp -ladvapi32
@echo "built $@"
# Add a native rule if one was not already made above.
+2 -2
View File
@@ -26,7 +26,7 @@ score weights. Version 0.1 and 0.2 scores are not comparable.
|---|---|
| ARM64 | ARMv8-A with NEON |
| x86-64 | x86-64 with SSE2 |
| x86 32-bit | i386 with SSE2 |
| x86 32-bit | original i386 ISA (scalar fallback) |
| PowerPC 32-bit, big-endian | scalar fallback with runtime-selected extensions |
| PowerPC 64-bit, big-endian | PowerPC 970 with AltiVec |
| PowerPC 32-bit, little-endian | scalar fallback with runtime-selected extensions |
@@ -171,7 +171,7 @@ src/app/benchmark.h interface used by main.c
src/app/upload.c API payload and network transport
src/kernels/fossbench-arm64.S ARM64 kernels
src/kernels/fossbench-amd64.S x86-64 kernels
src/kernels/fossbench-i386.S i386 kernels
src/kernels/fossbench-i386.c portable scalar i386 kernels
src/kernels/fossbench-ppc32be.S 32-bit big-endian PowerPC kernels
src/kernels/fossbench-ppc64be.S 64-bit big-endian PowerPC kernels
src/kernels/fossbench-ppc32le.S 32-bit little-endian PowerPC kernels
+103 -29
View File
@@ -38,7 +38,28 @@
#ifndef FB_API_BASE_URL
# define FB_API_BASE_URL "http://fossbench.net"
#endif
#define FB_VERSION "0.3.0"
#define FB_VERSION "0.4.0"
/*
* Native Integer Math and Memory Bandwidth are the only two *measured*
* workloads written in C instead of a per-architecture assembly kernel. Because
* they were compiled with whatever optimizer the build used, auto-vectorization
* made their results depend on the compiler rather than the CPU: newer Clang
* roughly tripled the integer result and inflated the STREAM triad by ~3x,
* so the same machine scored very differently across builds. Pin both to
* deterministic scalar code so every compiler measures the same work. These two
* carry weight in web/src/scoring.js, so their stability matters to the score.
*/
#if defined(__GNUC__) && !defined(__clang__)
# define FB_SCALAR_KERNEL __attribute__((optimize("O2", "no-tree-vectorize", "no-tree-slp-vectorize")))
#else
# define FB_SCALAR_KERNEL
#endif
#if defined(__clang__)
# define FB_SCALAR_LOOP _Pragma("clang loop vectorize(disable) interleave(disable)")
#else
# define FB_SCALAR_LOOP
#endif
#if defined(__aarch64__) || defined(__arm64__) || defined(_M_ARM64)
# define D_INT "64-bit ALU: madd, umulh, udiv, bitops"
@@ -49,9 +70,9 @@
# define D_FP "double: mulsd/addsd, divsd, sqrtsd"
# define D_SIMD "SSE2: 128-bit integer + float"
#elif defined(__i386__) || defined(_M_IX86)
# define D_INT "Pentium 4 integer ALU and software 64-bit arithmetic"
# define D_INT "i386 integer ALU and software 64-bit arithmetic"
# define D_FP "x87 scalar double-precision floating point"
# define D_SIMD "SSE2: 128-bit integer vectors"
# define D_SIMD "scalar extended-instruction fallback"
#elif defined(__powerpc64__)
# define D_INT "64-bit PowerPC integer ALU"
# define D_FP "PowerPC scalar double-precision floating point"
@@ -111,6 +132,18 @@ extern uint64_t fb_physics(double *bodies, uint64_t n, uint64_t steps);
extern uint64_t fb_sort(uint32_t *a, uint64_t n);
extern uint64_t fb_chase(void **ptrs, uint64_t steps);
/* GCC/Clang optimised C copies used for the REAL score. */
extern uint64_t fb_c_int_math(uint64_t iters);
extern uint64_t fb_c_fp_math(uint64_t iters);
extern uint64_t fb_c_primes(uint64_t limit, uint8_t *sieve);
extern uint64_t fb_c_simd(uint64_t iters, void *buf);
extern uint64_t fb_c_compress(const uint8_t *src, uint64_t len, uint32_t *ht);
extern uint64_t fb_c_chacha20(uint8_t *buf, uint64_t len,
const uint8_t key[32], uint64_t rounds);
extern uint64_t fb_c_physics(double *bodies, uint64_t n, uint64_t steps);
extern uint64_t fb_c_sort(uint32_t *a, uint64_t n);
extern uint64_t fb_c_chase(void **ptrs, uint64_t steps);
/* Test sizes and timing settings. */
#define PRIME_LIMIT (2u * 1000u * 1000u) /* Prime test size. */
@@ -134,7 +167,7 @@ extern uint64_t fb_chase(void **ptrs, uint64_t steps);
#endif
#ifndef MIN_SECONDS
# define MIN_SECONDS 2.0 /* Minimum run time. */
# define MIN_SECONDS 0.4 /* Minimum run time. */
#endif
#ifndef REPEATS
# define REPEATS 3 /* Number of tries. */
@@ -299,6 +332,7 @@ static uint8_t *g_cipher_src; /* pristine plaintext, copied per-core. */
static uint8_t *g_simd_src; /* pristine NEON seed, copied per-core. */
static double *g_bodies_src; /* pristine initial conditions. */
static uint32_t *g_sort_src; /* pristine unsorted data. */
static int g_c_backend; /* zero = RAW assembly, one = REAL C. */
/* Synthesise a compressible corpus. */
@@ -445,15 +479,17 @@ struct test {
static uint64_t run_int(uint64_t n, struct workspace *ws)
{
(void)ws;
return fb_int_math(n * 100000);
return g_c_backend ? fb_c_int_math(n * 100000) : fb_int_math(n * 100000);
}
FB_SCALAR_KERNEL
static uint64_t run_int32(uint64_t n, struct workspace *ws)
{
uint32_t a = 0x9e3779b9u, b = 0xbf58476du;
uint32_t c = 0x94d049bbu, d = 0x2545f491u;
uint64_t i, iters = n * 100000;
(void)ws;
FB_SCALAR_LOOP
for (i = 0; i < iters; i++) {
a = a * 0xdeadbeefu + b;
b = b * 0xdeadbeefu + c;
@@ -469,37 +505,42 @@ static uint64_t run_int32(uint64_t n, struct workspace *ws)
static uint64_t run_fp(uint64_t n, struct workspace *ws)
{
(void)ws;
return fb_fp_math(n * 100000);
return g_c_backend ? fb_c_fp_math(n * 100000) : fb_fp_math(n * 100000);
}
static uint64_t run_primes(uint64_t n, struct workspace *ws)
{
uint64_t c = 0;
for (uint64_t i = 0; i < n; i++)
c += fb_primes(PRIME_LIMIT, ws->sieve);
c += g_c_backend ? fb_c_primes(PRIME_LIMIT, ws->sieve) :
fb_primes(PRIME_LIMIT, ws->sieve);
return c;
}
static uint64_t run_simd(uint64_t n, struct workspace *ws)
{
/* Reset the SIMD buffer before running. */
memcpy(ws->simd_buf, g_simd_src, SIMD_BUF);
return fb_simd(n * 100000, ws->simd_buf);
return g_c_backend ? fb_c_simd(n * 100000, ws->simd_buf) :
fb_simd(n * 100000, ws->simd_buf);
}
static uint64_t run_compress(uint64_t n, struct workspace *ws)
{
uint64_t c = 0;
for (uint64_t i = 0; i < n; i++)
c += fb_compress(g_corpus, COMPRESS_LEN, ws->ht);
c += g_c_backend ? fb_c_compress(g_corpus, COMPRESS_LEN, ws->ht) :
fb_compress(g_corpus, COMPRESS_LEN, ws->ht);
return c;
}
static uint64_t run_crypto(uint64_t n, struct workspace *ws)
{
return fb_chacha20(ws->cipher_buf, CIPHER_LEN, g_key, n);
return g_c_backend ? fb_c_chacha20(ws->cipher_buf, CIPHER_LEN, g_key, n) :
fb_chacha20(ws->cipher_buf, CIPHER_LEN, g_key, n);
}
static uint64_t run_physics(uint64_t n, struct workspace *ws)
{
/* Reset the physics data before running. */
memcpy(ws->bodies, g_bodies_src, NBODY_N * 8 * sizeof(double));
return fb_physics(ws->bodies, NBODY_N, n);
return g_c_backend ? fb_c_physics(ws->bodies, NBODY_N, n) :
fb_physics(ws->bodies, NBODY_N, n);
}
static uint64_t run_sort(uint64_t n, struct workspace *ws)
{
@@ -507,15 +548,18 @@ static uint64_t run_sort(uint64_t n, struct workspace *ws)
for (uint64_t i = 0; i < n; i++) {
/* Reset the sort data before running. */
memcpy(ws->sort_work, g_sort_src, SORT_N * sizeof(uint32_t));
c ^= fb_sort(ws->sort_work, SORT_N);
c ^= g_c_backend ? fb_c_sort(ws->sort_work, SORT_N) :
fb_sort(ws->sort_work, SORT_N);
}
return c;
}
static uint64_t run_chase(uint64_t n, struct workspace *ws)
{
return fb_chase(ws->chase, n * 1000000);
return g_c_backend ? fb_c_chase(ws->chase, n * 1000000) :
fb_chase(ws->chase, n * 1000000);
}
FB_SCALAR_KERNEL
static uint64_t run_stream(uint64_t n, struct workspace *ws)
{
uint64_t pass, checksum = 0;
@@ -525,6 +569,7 @@ static uint64_t run_stream(uint64_t n, struct workspace *ws)
float *restrict a = ws->stream_a;
float *restrict b = ws->stream_b;
float *restrict c = ws->stream_c;
FB_SCALAR_LOOP
for (i = 0; i < STREAM_N; i++)
c[i] = a[i] + scale * b[i];
}
@@ -646,9 +691,9 @@ static struct result run_test(const struct test *t, int threads)
{
struct result r;
uint64_t n = t->start_n;
double elapsed = 0.0, best = 0.0;
double elapsed = 0.0, total = 0.0, average;
uint64_t checksum = 0;
int i;
int i, samples;
/* Increase the work until it runs long enough. */
for (;;) {
@@ -670,8 +715,11 @@ static struct result run_test(const struct test *t, int threads)
}
}
/* Keep the fastest run. */
best = elapsed;
/* Average every measured run. Keeping only the fastest rewarded a single
* lucky sample and hid the run-to-run variation that real machines show;
* the mean is a more representative and reproducible throughput. */
total = elapsed;
samples = 1;
for (i = 1; i < REPEATS; i++) {
double t0 = now_seconds();
uint64_t c = dispatch(t->run, n, threads);
@@ -685,16 +733,17 @@ static struct result run_test(const struct test *t, int threads)
(unsigned long long)checksum);
exit(2);
}
if (e < best)
best = e;
total += e;
samples++;
}
average = total / samples;
r.seconds = best;
r.seconds = average;
r.iters = n;
r.checksum = checksum;
r.threads = threads;
/* Calculate the total speed. */
r.rate = ((double)threads * (double)n * t->work_per_n) / best / 1e6;
r.rate = ((double)threads * (double)n * t->work_per_n) / average / 1e6;
return r;
}
@@ -722,6 +771,8 @@ static void print_header(const struct system_info *info)
printf(" model: %s\n", info->model[0] ? info->model : "unknown");
printf(" cores: %ld physical / %ld threads\n", info->cpu_cores, info->cpu_threads);
printf(" memory: %ld MB\n", info->memory_mb);
printf(" caches: L1 %ld KB / L2 %ld KB / L3 %ld KB\n",
info->l1_cache_kb, info->l2_cache_kb, info->l3_cache_kb);
printf(" OS: %s (%s)\n", info->operating_system, hw_arch_name());
printf(" kernel: %s\n", info->kernel[0] ? info->kernel : "unknown");
printf(" compiler: %s\n", info->compiler);
@@ -734,7 +785,8 @@ static void print_header(const struct system_info *info)
int fossbench_run(int verbose, int upload_mode, int system_check)
{
struct result multi[NTESTS], single[NTESTS];
struct result raw_multi[NTESTS], raw_single[NTESTS];
struct result real_multi[NTESTS], real_single[NTESTS];
struct system_info system_info;
struct background_metrics background;
double benchmark_started;
@@ -766,26 +818,47 @@ int fossbench_run(int verbose, int upload_mode, int system_check)
print_header(&system_info);
printf(" RAW score suite (architecture assembly)\n");
g_c_backend = 0;
for (i = 0; i < NTESTS; i++) {
printf(" %-24s", tests[i].name);
fflush(stdout);
/* Run every test with all cores and one core. */
multi[i] = run_test(&tests[i], (int)g_ncores);
single[i] = run_test(&tests[i], 1);
raw_multi[i] = run_test(&tests[i], (int)g_ncores);
raw_single[i] = run_test(&tests[i], 1);
printf(" %12.1f %-11s %7.2fs\n",
display_metric(&tests[i], &multi[i]), tests[i].unit,
multi[i].seconds);
display_metric(&tests[i], &raw_multi[i]), tests[i].unit,
raw_multi[i].seconds);
if (verbose)
printf(" %-24s %s\n"
" %-24s 1-core: %.1f %s %ld-core: %.1f %s\n",
"", tests[i].detail, "",
display_metric(&tests[i], &single[i]), tests[i].unit,
g_ncores, display_metric(&tests[i], &multi[i]), tests[i].unit);
display_metric(&tests[i], &raw_single[i]), tests[i].unit,
g_ncores, display_metric(&tests[i], &raw_multi[i]), tests[i].unit);
fflush(stdout);
}
printf(" --------------------------------------------------------------------------\n");
printf(" REAL score suite (optimised portable C)\n");
g_c_backend = 1;
for (i = 0; i < NTESTS; i++) {
printf(" %-24s", tests[i].name);
fflush(stdout);
real_multi[i] = run_test(&tests[i], (int)g_ncores);
real_single[i] = run_test(&tests[i], 1);
printf(" %12.1f %-11s %7.2fs\n",
display_metric(&tests[i], &real_multi[i]), tests[i].unit,
real_multi[i].seconds);
if (verbose)
printf(" %-24s %s\n"
" %-24s 1-core: %.1f %s %ld-core: %.1f %s\n",
"", tests[i].detail, "",
display_metric(&tests[i], &real_single[i]), tests[i].unit,
g_ncores, display_metric(&tests[i], &real_multi[i]), tests[i].unit);
}
printf(" --------------------------------------------------------------------------\n");
duration_ms = (uint64_t)((now_seconds() - benchmark_started) * 1000.0);
@@ -816,7 +889,8 @@ int fossbench_run(int verbose, int upload_mode, int system_check)
#if defined(FB_NO_UPLOAD)
fprintf(stderr, " Upload support is disabled in this build.\n");
#else
upload_results(&system_info, multi, single, duration_ms, &background);
upload_results(&system_info, raw_multi, raw_single,
real_multi, real_single, duration_ms, &background);
#endif
}
}
+206
View File
@@ -17,6 +17,9 @@
#endif
#if defined(_WIN32)
# define WIN32_LEAN_AND_MEAN
# ifndef _WIN32_WINNT
# define _WIN32_WINNT 0x0601 /* GetLogicalProcessorInformationEx (Win7+). */
# endif
# include <windows.h>
# if defined(__i386__) || defined(__x86_64__)
# include <cpuid.h>
@@ -89,6 +92,39 @@ static int apple_m_name(const char *text, char *dst, size_t cap)
/* Linux device trees identify Apple Silicon by its SoC code. */
#if defined(__linux__)
static int read_first_property(const char *path, char *dst, size_t cap);
static long cache_size_kb(const char *text)
{
char *end;
long value = strtol(text, &end, 10);
if (value <= 0) return 0;
while (*end && isspace((unsigned char)*end)) end++;
if (*end == 'M' || *end == 'm') value *= 1024;
return value;
}
static void linux_detect_caches(struct system_info *info)
{
int index;
for (index = 0; index < 32; index++) {
char path[160], level_text[32], size_text[32];
long level, kb;
snprintf(path, sizeof path,
"/sys/devices/system/cpu/cpu0/cache/index%d/level", index);
if (!read_first_property(path, level_text, sizeof level_text)) continue;
snprintf(path, sizeof path,
"/sys/devices/system/cpu/cpu0/cache/index%d/size", index);
if (!read_first_property(path, size_text, sizeof size_text)) continue;
level = strtol(level_text, NULL, 10);
kb = cache_size_kb(size_text);
/* L1 instruction and data caches are separate and should be added. */
if (level == 1) info->l1_cache_kb += kb;
else if (level == 2 && kb > info->l2_cache_kb) info->l2_cache_kb = kb;
else if (level == 3 && kb > info->l3_cache_kb) info->l3_cache_kb = kb;
}
}
static int apple_m_name_from_soc(const char *text, char *dst, size_t cap)
{
static const struct { const char *soc, *name; } chips[] = {
@@ -251,6 +287,83 @@ static int read_first_property(const char *path, char *dst, size_t cap)
}
#endif
#if defined(_WIN32)
/* Count physical cores. Windows only exposes logical processors through
* GetSystemInfo, so with SMT/HyperThreading every count was wrong (an 8-core /
* 16-thread part reported 16 cores). Each RelationProcessorCore record returned
* by GetLogicalProcessorInformationEx describes exactly one physical core. */
static long win_physical_cores(void)
{
DWORD length = 0;
BYTE *buffer, *p;
long cores = 0;
if (GetLogicalProcessorInformationEx(RelationProcessorCore, NULL, &length))
return 0;
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || length == 0)
return 0;
buffer = malloc(length);
if (buffer == NULL)
return 0;
if (GetLogicalProcessorInformationEx(RelationProcessorCore,
(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)buffer, &length)) {
for (p = buffer; p < buffer + length; ) {
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX record =
(PSYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX)p;
if (record->Size == 0)
break; /* Guard against a malformed run. */
if (record->Relationship == RelationProcessorCore)
cores++;
p += record->Size;
}
}
free(buffer);
return cores;
}
#endif
#if defined(__linux__)
/* Count physical cores from sysfs topology. PowerPC (and some ARM) kernels omit
* the "physical id" / "core id" fields from /proc/cpuinfo, so those hosts fell
* back to the thread count (a POWER8 with SMT8 reported 176 cores instead of
* 22). Every thread of a physical core shares one thread-sibling group, and the
* lowest thread id in that group uniquely identifies the core -- counting the
* distinct groups is correct with SMT, without it, and across clusters whose
* core_id numbering restarts (e.g. Apple Silicon under Asahi). */
static long linux_topology_cores(long max_threads)
{
long seen[4096];
int nseen = 0;
long cpu, cores = 0;
for (cpu = 0; cpu < max_threads && cpu < 4096; cpu++) {
char path[192], value[256];
long first;
int i, duplicate = 0;
snprintf(path, sizeof path,
"/sys/devices/system/cpu/cpu%ld/topology/thread_siblings_list", cpu);
if (!read_first_property(path, value, sizeof value)) {
snprintf(path, sizeof path,
"/sys/devices/system/cpu/cpu%ld/topology/core_cpus_list", cpu);
if (!read_first_property(path, value, sizeof value))
continue;
}
first = strtol(value, NULL, 0); /* Lowest thread id of this core. */
for (i = 0; i < nseen; i++)
if (seen[i] == first) {
duplicate = 1;
break;
}
if (!duplicate && nseen < 4096) {
seen[nseen++] = first;
cores++;
}
}
return cores;
}
#endif
void hw_detect_system(struct system_info *info)
{
long detected_threads;
@@ -363,6 +476,13 @@ void hw_detect_system(struct system_info *info)
if (npairs > 0) info->cpu_cores = npairs;
}
}
/* When /proc/cpuinfo did not distinguish cores from threads (PowerPC, ARM),
* recover the physical core count from sysfs topology. */
if (info->cpu_cores == info->cpu_threads) {
long cores = linux_topology_cores(info->cpu_threads);
if (cores > 0)
info->cpu_cores = cores;
}
if (apple_soc[0])
snprintf(info->cpu, sizeof info->cpu, "%s", apple_soc);
#if defined(__aarch64__) || defined(__arm__)
@@ -418,6 +538,7 @@ void hw_detect_system(struct system_info *info)
fclose(f);
}
}
linux_detect_caches(info);
{
FILE *f = fopen("/etc/os-release", "r"); char line[512];
if (f) { while (fgets(line, sizeof(line), f)) if (!strncmp(line, "PRETTY_NAME=", 12)) {
@@ -453,6 +574,20 @@ void hw_detect_system(struct system_info *info)
sysctlbyname("hw.model", info->model, &model_n, NULL, 0);
if (sysctlbyname("hw.physicalcpu", &cores, &cn, NULL, 0) == 0) info->cpu_cores = cores;
if (sysctlbyname("hw.memsize", &mem, &mn, NULL, 0) == 0) info->memory_mb = (long)(mem / 1024 / 1024);
{
uint64_t bytes = 0; size_t bytes_n = sizeof bytes;
if (sysctlbyname("hw.l1dcachesize", &bytes, &bytes_n, NULL, 0) == 0)
info->l1_cache_kb += (long)(bytes / 1024);
bytes = 0; bytes_n = sizeof bytes;
if (sysctlbyname("hw.l1icachesize", &bytes, &bytes_n, NULL, 0) == 0)
info->l1_cache_kb += (long)(bytes / 1024);
bytes = 0; bytes_n = sizeof bytes;
if (sysctlbyname("hw.l2cachesize", &bytes, &bytes_n, NULL, 0) == 0)
info->l2_cache_kb = (long)(bytes / 1024);
bytes = 0; bytes_n = sizeof bytes;
if (sysctlbyname("hw.l3cachesize", &bytes, &bytes_n, NULL, 0) == 0)
info->l3_cache_kb = (long)(bytes / 1024);
}
}
{
char product[64] = ""; size_t pn = sizeof(product);
@@ -463,6 +598,30 @@ void hw_detect_system(struct system_info *info)
snprintf(info->kernel, sizeof(info->kernel), "%.62s %.64s", u.sysname, u.release);
}
#elif defined(_WIN32)
{
DWORD bytes = 0;
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION entries = NULL;
if (!GetLogicalProcessorInformation(NULL, &bytes) &&
GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
entries = (PSYSTEM_LOGICAL_PROCESSOR_INFORMATION)malloc(bytes);
if (entries && GetLogicalProcessorInformation(entries, &bytes)) {
DWORD i, count = bytes / sizeof *entries;
long cores = 0;
for (i = 0; i < count; i++) {
if (entries[i].Relationship == RelationProcessorCore) cores++;
else if (entries[i].Relationship == RelationCache) {
CACHE_DESCRIPTOR c = entries[i].Cache;
long kb = (long)(c.Size / 1024);
if (c.Level == 1 && kb > info->l1_cache_kb) info->l1_cache_kb = kb;
else if (c.Level == 2 && kb > info->l2_cache_kb) info->l2_cache_kb = kb;
else if (c.Level == 3 && kb > info->l3_cache_kb) info->l3_cache_kb = kb;
}
}
if (cores > 0) info->cpu_cores = cores;
}
free(entries);
}
}
{
HKEY key;
char brand[sizeof info->cpu] = "";
@@ -479,6 +638,24 @@ void hw_detect_system(struct system_info *info)
RegCloseKey(key);
}
}
{
long cores = win_physical_cores();
if (cores > 0)
info->cpu_cores = cores;
}
{
HKEY key; char product[sizeof info->model] = "";
DWORD type = 0, bytes = sizeof product;
if (RegOpenKeyExA(HKEY_LOCAL_MACHINE,
"HARDWARE\\DESCRIPTION\\System\\BIOS", 0, KEY_QUERY_VALUE, &key) == ERROR_SUCCESS) {
if (RegQueryValueExA(key, "SystemProductName", NULL, &type,
(BYTE *)product, &bytes) == ERROR_SUCCESS && type == REG_SZ) {
product[sizeof product - 1] = '\0'; trim(product);
if (product[0]) snprintf(info->model, sizeof info->model, "%s", product);
}
RegCloseKey(key);
}
}
{
MEMORYSTATUSEX ms;
ms.dwLength = sizeof(ms);
@@ -525,4 +702,33 @@ void hw_detect_system(struct system_info *info)
}
#endif
#endif
/* Portable POSIX fallbacks cover BSDs and other Unix systems where the
* platform-specific branches above are unavailable. */
#if !defined(_WIN32) && !defined(__linux__) && !defined(__APPLE__)
{
long pages = sysconf(_SC_PHYS_PAGES), page_size = sysconf(_SC_PAGESIZE);
if (pages > 0 && page_size > 0) info->memory_mb = (pages / 1024) * (page_size / 1024);
}
# if defined(_SC_LEVEL1_DCACHE_SIZE)
{ long v = sysconf(_SC_LEVEL1_DCACHE_SIZE); if (v > 0) info->l1_cache_kb += v / 1024; }
# endif
# if defined(_SC_LEVEL1_ICACHE_SIZE)
{ long v = sysconf(_SC_LEVEL1_ICACHE_SIZE); if (v > 0) info->l1_cache_kb += v / 1024; }
# endif
# if defined(_SC_LEVEL2_CACHE_SIZE)
{ long v = sysconf(_SC_LEVEL2_CACHE_SIZE); if (v > 0) info->l2_cache_kb = v / 1024; }
# endif
# if defined(_SC_LEVEL3_CACHE_SIZE)
{ long v = sysconf(_SC_LEVEL3_CACHE_SIZE); if (v > 0) info->l3_cache_kb = v / 1024; }
# endif
#endif
/* Never emit empty identity fields: these values are used by the website
* for processor matching and display on platforms with sparse APIs. */
if (!info->cpu[0]) snprintf(info->cpu, sizeof info->cpu, "%s", HW_ARCH);
if (!info->model[0]) snprintf(info->model, sizeof info->model, "%s", info->cpu);
if (!info->kernel[0]) snprintf(info->kernel, sizeof info->kernel, "%s", info->operating_system);
if (info->memory_mb < 1) info->memory_mb = 1;
if (info->l1_cache_kb < 1) info->l1_cache_kb = 1;
if (info->l2_cache_kb < 1) info->l2_cache_kb = info->l1_cache_kb;
if (info->l3_cache_kb < 1) info->l3_cache_kb = info->l2_cache_kb;
}
+3
View File
@@ -10,6 +10,9 @@ struct system_info {
long cpu_cores;
long cpu_threads;
long memory_mb;
long l1_cache_kb;
long l2_cache_kb;
long l3_cache_kb;
};
const char *hw_arch_name(void);
+50 -32
View File
@@ -120,18 +120,52 @@ done:
}
#endif
static int upload_results(const struct system_info *info,
static int append_result_tests(char *payload, size_t cap, size_t *used,
const struct result *multi,
const struct result *single, uint64_t duration_ms,
const struct result *single)
{
static const char *ids[] = {
"native_integer", "wide_integer", "floating_point", "primes",
"extended_instructions", "compression", "encryption", "physics",
"sorting", "memory_latency", "memory_bandwidth"
};
size_t i;
for (i = 0; i < NTESTS; i++) {
int n = snprintf(payload + *used, cap - *used,
"%s{\"id\":\"%s\",\"name\":\"%s\",\"detail\":\"%s\",\"unit\":\"%s\","
"\"start_iterations\":%llu,\"work_per_iteration\":%.17g,"
"\"multicore\":{\"display_metric\":%.17g,\"rate\":%.17g,"
"\"seconds\":%.17g,\"iterations\":%llu,\"threads\":%d,\"checksum\":\"%llu\"},"
"\"singlecore\":{\"display_metric\":%.17g,\"rate\":%.17g,"
"\"seconds\":%.17g,\"iterations\":%llu,\"threads\":%d,\"checksum\":\"%llu\"}}",
i ? "," : "", ids[i], tests[i].name, tests[i].detail, tests[i].unit,
(unsigned long long)tests[i].start_n, tests[i].work_per_n,
display_metric(&tests[i], &multi[i]), multi[i].rate, multi[i].seconds,
(unsigned long long)multi[i].iters, multi[i].threads,
(unsigned long long)multi[i].checksum,
display_metric(&tests[i], &single[i]), single[i].rate, single[i].seconds,
(unsigned long long)single[i].iters, single[i].threads,
(unsigned long long)single[i].checksum);
if (n < 0 || (size_t)n >= cap - *used) return 0;
*used += (size_t)n;
}
return 1;
}
static int upload_results(const struct system_info *info,
const struct result *raw_multi,
const struct result *raw_single,
const struct result *real_multi,
const struct result *real_single, uint64_t duration_ms,
const struct background_metrics *background)
{
char host[256], port[16], path[512], payload[16384];
char host[256], port[16], path[512], payload[32768];
char auth_header[600], response_body[2048], claim_url[1024], result_url[1024];
char cpu[512], model[512], os[512], compiler[256], kernel[256];
const char *base = FB_API_BASE_URL, *p, *slash, *colon;
int status = 0, payload_len;
#if !defined(_WIN32)
char request[20000], response[4096];
char request[40000], response[4096];
struct addrinfo hints, *addresses = NULL, *a;
int fd = -1, request_len;
#endif
@@ -167,46 +201,30 @@ static int upload_results(const struct system_info *info,
/* The server still calls this field fossmark_version. */
payload_len = snprintf(payload, sizeof(payload),
"{\"cpu\":\"%s\",\"model\":\"%s\",\"cpu_cores\":%ld,\"cpu_threads\":%ld,"
"\"architecture\":\"%s\",\"l1_cache_kb\":%ld,\"l2_cache_kb\":%ld,\"l3_cache_kb\":%ld,"
"\"memory_mb\":%ld,\"operating_system\":\"%s\",\"compiler\":\"%s\","
"\"fossmark_version\":\"%s\",\"workload_suite\":\"fossbench-cpu-v1\","
"\"fossmark_version\":\"%s\",\"workload_suite\":\"fossbench-cpu-v2\","
"\"duration_ms\":%llu,\"score_details\":{"
"\"minimum_test_seconds\":%.17g,\"repeats\":%d,"
"\"system_environment\":{\"kernel\":\"%s\",\"sample_seconds\":%d,"
"\"background_cpu_average_percent\":%.17g,\"background_cpu_peak_percent\":%.17g,"
"\"available_memory_mb\":%ld,\"process_count\":%ld},\"tests\":[",
cpu, model, info->cpu_cores, info->cpu_threads, info->memory_mb, os, compiler,
"\"available_memory_mb\":%ld,\"process_count\":%ld},\"raw_tests\":[",
cpu, model, info->cpu_cores, info->cpu_threads, hw_arch_name(),
info->l1_cache_kb, info->l2_cache_kb, info->l3_cache_kb,
info->memory_mb, os, compiler,
FB_VERSION, (unsigned long long)duration_ms, MIN_SECONDS, REPEATS, kernel, background->samples,
background->average_cpu_percent, background->peak_cpu_percent,
background->available_memory_mb, background->process_count);
if (payload_len < 0 || (size_t)payload_len >= sizeof(payload)) return 0;
{
size_t used = (size_t)payload_len;
size_t i;
for (i = 0; i < NTESTS; i++) {
static const char *ids[] = {
"native_integer", "wide_integer", "floating_point", "primes",
"extended_instructions", "compression", "encryption", "physics",
"sorting", "memory_latency", "memory_bandwidth"
};
int n = snprintf(payload + used, sizeof(payload) - used,
"%s{\"id\":\"%s\",\"name\":\"%s\",\"detail\":\"%s\",\"unit\":\"%s\","
"\"start_iterations\":%llu,\"work_per_iteration\":%.17g,"
"\"multicore\":{\"display_metric\":%.17g,\"rate\":%.17g,"
"\"seconds\":%.17g,\"iterations\":%llu,\"threads\":%d,\"checksum\":\"%llu\"},"
"\"singlecore\":{\"display_metric\":%.17g,\"rate\":%.17g,"
"\"seconds\":%.17g,\"iterations\":%llu,\"threads\":%d,\"checksum\":\"%llu\"}}",
i ? "," : "", ids[i], tests[i].name, tests[i].detail, tests[i].unit,
(unsigned long long)tests[i].start_n, tests[i].work_per_n,
display_metric(&tests[i], &multi[i]), multi[i].rate,
multi[i].seconds, (unsigned long long)multi[i].iters, multi[i].threads,
(unsigned long long)multi[i].checksum,
display_metric(&tests[i], &single[i]), single[i].rate,
single[i].seconds, (unsigned long long)single[i].iters, single[i].threads,
(unsigned long long)single[i].checksum);
if (n < 0 || (size_t)n >= sizeof(payload) - used) return 0;
int n;
if (!append_result_tests(payload, sizeof payload, &used, raw_multi, raw_single)) return 0;
n = snprintf(payload + used, sizeof payload - used, "],\"real_tests\":[");
if (n < 0 || (size_t)n >= sizeof payload - used) return 0;
used += (size_t)n;
}
if (used + 3 >= sizeof(payload)) return 0;
if (!append_result_tests(payload, sizeof payload, &used, real_multi, real_single)) return 0;
if (used + 3 >= sizeof payload) return 0;
memcpy(payload + used, "]}}", 4);
payload_len = (int)(used + 3);
}
+145
View File
@@ -0,0 +1,145 @@
/* Portable scalar backend for the baseline 32-bit x86 target. */
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
static uint32_t rotl32(uint32_t x, unsigned n)
{
return (x << n) | (x >> (32 - n));
}
uint64_t fb_int_math(uint64_t iters)
{
uint64_t a=0x9e3779b97f4a7c15ULL,b=0xbf58476d1ce4e5b9ULL;
uint64_t c=0x94d049bb133111ebULL,d=0x2545f4914f6cdd1dULL,i;
if (!iters) return 0;
for (i=0;i<iters;i++) {
a=a*0xdeadbeefU+b; b=b*0xdeadbeefU+c;
c=c*0xdeadbeefU+d; d=d*0xdeadbeefU+a;
a^=c>>29; b^=d<<17; c^=(a>>31)|(a<<33); d^=b>>7;
a+=c/0xdeadbeefU; b+=d/0xdeadbeefU;
}
return a^b^c^d;
}
uint64_t fb_fp_math(uint64_t iters)
{
double a=1.5,b=2.5,c=3.5,d=.5,out; uint64_t bits,i;
if (!iters) return 0;
for (i=0;i<iters;i++) {
a=fmin(a*1.0625+.0009765625,2.0);
b=fmin(b*1.0625+.0009765625,2.0);
c=fmin(c*1.0625+.0009765625,2.0)+sqrt(a);
d=fmax(fabs(fmin(d*1.0625+.0009765625,2.0)+sqrt(b)),1.0);
a+=1.0/(c+1.0); b+=1.0/(d+1.0);
}
out=a+b+c+d; memcpy(&bits,&out,sizeof bits); return bits;
}
uint64_t fb_primes(uint64_t limit, uint8_t *sieve)
{
uint64_t i,j,count=0;
if (limit<2) return 0;
memset(sieve,0,(size_t)limit); sieve[0]=sieve[1]=1;
for (i=2;i<=(limit-1)/i;i++)
if (!sieve[i]) for (j=i*i;j<limit;j+=i) sieve[j]=1;
for (i=2;i<limit;i++) count+=!sieve[i];
return count;
}
uint64_t fb_simd(uint64_t iters, void *memory)
{
uint32_t *v=(uint32_t *)memory,a[8],sum=0; uint64_t i; unsigned j;
if (!iters) return 0;
memcpy(a,v,sizeof a);
for (i=0;i<iters;i++)
for (j=0;j<8;j++) a[j]=rotl32(a[j]+a[(j+1)&7]*(j+3),(j+5)&31);
for (j=0;j<8;j++) sum^=a[j];
memcpy(v,a,sizeof a); return sum;
}
static uint32_t load32_native(const uint8_t *p)
{
uint32_t v; memcpy(&v,p,sizeof v); return v;
}
uint64_t fb_compress(const uint8_t *src, uint64_t len, uint32_t *ht)
{
uint64_t ip=0,anchor=0,out=0,ref,ml,lit;
memset(ht,0,(size_t)(1U<<16)*sizeof *ht);
if (len<16) return len+1;
while (ip<len-12) {
uint32_t seq=load32_native(src+ip);
uint32_t h=(uint32_t)(seq*2654435761U)>>16;
ref=ht[h]; ht[h]=(uint32_t)ip;
if (ref>=ip||ip-ref>=65536||load32_native(src+ref)!=seq) { ip++; continue; }
for (ml=4;ip+ml<len&&src[ip+ml]==src[ref+ml];ml++) {}
lit=ip-anchor; out+=lit+3+(lit>=15)+(ml>=19); ip+=ml; anchor=ip;
}
return out+(len-anchor)+1;
}
static uint32_t load32le(const uint8_t *p)
{
return (uint32_t)p[0]|(uint32_t)p[1]<<8|(uint32_t)p[2]<<16|(uint32_t)p[3]<<24;
}
static void store32le(uint8_t *p,uint32_t v)
{
p[0]=(uint8_t)v; p[1]=(uint8_t)(v>>8); p[2]=(uint8_t)(v>>16); p[3]=(uint8_t)(v>>24);
}
#define QR(a,b,c,d) do { a+=b; d=rotl32(d^a,16); c+=d; b=rotl32(b^c,12); a+=b; d=rotl32(d^a,8); c+=d; b=rotl32(b^c,7); } while (0)
uint64_t fb_chacha20(uint8_t *buf,uint64_t len,const uint8_t key[32],uint64_t passes)
{
static const uint32_t sigma[4]={0x61707865,0x3320646e,0x79622d32,0x6b206574};
uint32_t base[16],x[16],counter=0,checksum=0; uint64_t pass,off; int i,r;
len&=~(uint64_t)63; if (!len||!passes) return 0;
memcpy(base,sigma,16); for(i=0;i<8;i++) base[4+i]=load32le(key+4*i);
base[13]=base[14]=base[15]=0;
for(pass=0;pass<passes;pass++) for(off=0;off<len;off+=64) {
base[12]=counter++; memcpy(x,base,sizeof x);
for(r=0;r<10;r++) {
QR(x[0],x[4],x[8],x[12]); QR(x[1],x[5],x[9],x[13]);
QR(x[2],x[6],x[10],x[14]); QR(x[3],x[7],x[11],x[15]);
QR(x[0],x[5],x[10],x[15]); QR(x[1],x[6],x[11],x[12]);
QR(x[2],x[7],x[8],x[13]); QR(x[3],x[4],x[9],x[14]);
}
for(i=0;i<16;i++) { uint32_t k=x[i]+base[i]; uint8_t t[4];
store32le(t,k); buf[off+4*i]^=t[0]; buf[off+4*i+1]^=t[1];
buf[off+4*i+2]^=t[2]; buf[off+4*i+3]^=t[3]; checksum^=k; }
}
return checksum;
}
#undef QR
uint64_t fb_physics(double *b,uint64_t n,uint64_t steps)
{
uint64_t s,i,j,bits; double sum=0;
if (!n||!steps) return 0;
for(s=0;s<steps;s++) {
for(i=0;i<n;i++) { double ax=0,ay=0,az=0;
for(j=0;j<n;j++) { double dx=b[8*j]-b[8*i],dy=b[8*j+1]-b[8*i+1],dz=b[8*j+2]-b[8*i+2]; double q=1.0/sqrt(dx*dx+dy*dy+dz*dz+.0625); q=q*q*q*b[8*j+3]; ax+=dx*q; ay+=dy*q; az+=dz*q; }
b[8*i+4]+=ax*.0078125; b[8*i+5]+=ay*.0078125; b[8*i+6]+=az*.0078125;
}
for(i=0;i<n;i++) { b[8*i]+=b[8*i+4]*.0078125; b[8*i+1]+=b[8*i+5]*.0078125; b[8*i+2]+=b[8*i+6]*.0078125; }
}
for(i=0;i<n;i++) sum+=b[8*i+4]+b[8*i+5]+b[8*i+6];
memcpy(&bits,&sum,sizeof bits); return bits;
}
static void sift(uint32_t *a,uint64_t root,uint64_t end)
{
for (;;) { uint64_t c=root*2+1; uint32_t t; if(c>=end)return; if(c+1<end&&a[c+1]>a[c])c++; if(a[root]>=a[c])return; t=a[root];a[root]=a[c];a[c]=t;root=c; }
}
uint64_t fb_sort(uint32_t *a,uint64_t n)
{
uint64_t i,end,sum=0; uint32_t t; if(n<2)return n?a[0]:0;
for(i=n/2;i;i--) sift(a,i-1,n);
for(end=n-1;end;end--) { t=a[0];a[0]=a[end];a[end]=t;sift(a,0,end); }
for(i=0;i<n;i++){sum=(sum>>7)|(sum<<57);sum^=a[i];sum+=a[i];} return sum;
}
uint64_t fb_chase(void **ptrs,uint64_t steps)
{
void **p=ptrs; uint64_t i; if(!steps)return 0; for(i=0;i<steps;i++)p=(void **)*p; return (uint64_t)((uintptr_t)p-(uintptr_t)ptrs);
}
+19
View File
@@ -0,0 +1,19 @@
/*
* Compiler-optimised C implementation of the benchmark kernels.
*
* Keep this translation unit separate from the assembly backend so release
* builds can apply their strongest safe optimisation settings to it. The
* implementation is shared with the portable i386 backend; symbol renaming
* lets both backends live in the same executable.
*/
#define fb_int_math fb_c_int_math
#define fb_fp_math fb_c_fp_math
#define fb_primes fb_c_primes
#define fb_simd fb_c_simd
#define fb_compress fb_c_compress
#define fb_chacha20 fb_c_chacha20
#define fb_physics fb_c_physics
#define fb_sort fb_c_sort
#define fb_chase fb_c_chase
#include "fossbench-i386.c"