From c35ec1451ff28307e2c311c8c8281a82774b1e2f Mon Sep 17 00:00:00 2001 From: Owen Rummage Date: Mon, 20 Jul 2026 14:51:56 -0500 Subject: [PATCH 1/3] Fix a few things that were throwing off benchmark scores Two of the workloads (Native Integer Math and Memory Bandwidth) were plain C rather than assembly, so the compiler's auto-vectorizer got to decide how fast they ran. Newer Clang basically tripled the integer number and blew up the STREAM triad by ~3x, which meant the same chip landed all over the place depending on how it was built. Pinned both to scalar codegen so GCC and Clang line up again. Also stopped run_test from keeping only the fastest repeat -- it was rewarding one lucky pass and hiding the normal run-to-run wobble. It averages the repeats now. And core detection was just wrong on a couple platforms. Windows was handing back logical processors as physical cores (so an 8c/16t part showed up as 16 cores), and Linux boxes where /proc/cpuinfo doesn't carry topology (PowerPC, ARM) fell back to the thread count too -- a 176-thread POWER8 claimed 176 cores. Windows now counts real cores via GetLogicalProcessorInformationEx, and Linux falls back to sysfs thread-sibling groups, which also handles Apple Silicon's per-cluster core_id numbering. Co-Authored-By: Claude Opus 4.8 --- src/app/benchmark.c | 45 ++++++++++++++++++---- src/app/hw_detect.c | 92 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 8 deletions(-) diff --git a/src/app/benchmark.c b/src/app/benchmark.c index 3a00361..70c6154 100644 --- a/src/app/benchmark.c +++ b/src/app/benchmark.c @@ -40,6 +40,27 @@ #endif #define FB_VERSION "0.3.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" # define D_FP "double: fmadd, fdiv, fsqrt" @@ -448,12 +469,14 @@ static uint64_t run_int(uint64_t n, struct workspace *ws) return 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; @@ -516,6 +539,7 @@ static uint64_t run_chase(uint64_t n, struct workspace *ws) return 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 +549,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 +671,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 +695,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 +713,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; } diff --git a/src/app/hw_detect.c b/src/app/hw_detect.c index b52c6e3..571963c 100644 --- a/src/app/hw_detect.c +++ b/src/app/hw_detect.c @@ -17,6 +17,9 @@ #endif #if defined(_WIN32) # define WIN32_LEAN_AND_MEAN +# ifndef _WIN32_WINNT +# define _WIN32_WINNT 0x0601 /* GetLogicalProcessorInformationEx (Win7+). */ +# endif # include # if defined(__i386__) || defined(__x86_64__) # include @@ -251,6 +254,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 +443,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__) @@ -479,6 +566,11 @@ void hw_detect_system(struct system_info *info) RegCloseKey(key); } } + { + long cores = win_physical_cores(); + if (cores > 0) + info->cpu_cores = cores; + } { MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); From 801fe88fbd1816ce4b15adb6ac6be0302782e992 Mon Sep 17 00:00:00 2001 From: Owen Rummage Date: Mon, 20 Jul 2026 18:15:02 -0500 Subject: [PATCH 2/3] weee --- Makefile | 20 ++--- README.md | 4 +- src/app/benchmark.c | 85 +++++++++++++----- src/app/hw_detect.c | 114 ++++++++++++++++++++++++ src/app/hw_detect.h | 3 + src/app/upload.c | 84 +++++++++++------- src/kernels/fossbench-i386.c | 145 +++++++++++++++++++++++++++++++ src/kernels/fossbench-portable.c | 19 ++++ 8 files changed, 409 insertions(+), 65 deletions(-) create mode 100644 src/kernels/fossbench-i386.c create mode 100644 src/kernels/fossbench-portable.c diff --git a/Makefile b/Makefile index 70eaef7..e487925 100644 --- a/Makefile +++ b/Makefile @@ -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. diff --git a/README.md b/README.md index 8d153ad..8ac8650 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/app/benchmark.c b/src/app/benchmark.c index 70c6154..76bd393 100644 --- a/src/app/benchmark.c +++ b/src/app/benchmark.c @@ -38,7 +38,7 @@ #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* @@ -70,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" @@ -132,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. */ @@ -320,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. */ @@ -466,7 +479,7 @@ 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 @@ -492,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) { @@ -530,13 +548,15 @@ 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 @@ -751,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); @@ -763,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; @@ -795,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); @@ -845,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 } } diff --git a/src/app/hw_detect.c b/src/app/hw_detect.c index 571963c..684ba84 100644 --- a/src/app/hw_detect.c +++ b/src/app/hw_detect.c @@ -92,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[] = { @@ -505,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)) { @@ -540,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); @@ -550,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] = ""; @@ -571,6 +643,19 @@ void hw_detect_system(struct system_info *info) 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); @@ -617,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; } diff --git a/src/app/hw_detect.h b/src/app/hw_detect.h index b07c040..8c59f58 100644 --- a/src/app/hw_detect.h +++ b/src/app/hw_detect.h @@ -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); diff --git a/src/app/upload.c b/src/app/upload.c index 34bd3a4..4e2e01c 100644 --- a/src/app/upload.c +++ b/src/app/upload.c @@ -120,18 +120,52 @@ done: } #endif +static int append_result_tests(char *payload, size_t cap, size_t *used, + const struct result *multi, + 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 *multi, - const struct result *single, uint64_t duration_ms, + 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; - used += (size_t)n; - } - if (used + 3 >= sizeof(payload)) 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 (!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); } diff --git a/src/kernels/fossbench-i386.c b/src/kernels/fossbench-i386.c new file mode 100644 index 0000000..094d8bd --- /dev/null +++ b/src/kernels/fossbench-i386.c @@ -0,0 +1,145 @@ +/* Portable scalar backend for the baseline 32-bit x86 target. */ +#include +#include +#include +#include + +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>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>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=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=end)return; if(c+1a[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>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 Date: Mon, 20 Jul 2026 19:10:07 -0500 Subject: [PATCH 3/3] Reduce benchmark test duration to 0.4 seconds --- src/app/benchmark.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/benchmark.c b/src/app/benchmark.c index 76bd393..7b1992f 100644 --- a/src/app/benchmark.c +++ b/src/app/benchmark.c @@ -167,7 +167,7 @@ extern uint64_t fb_c_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. */